This commit is contained in:
Isaac 2024-11-26 10:08:16 +04:00
parent c954a55d2b
commit c9fd9f2a16
12 changed files with 1186 additions and 181 deletions

View File

@ -812,6 +812,9 @@ public protocol CollectibleItemInfoScreenInitialData: AnyObject {
public protocol BusinessLinksSetupScreenInitialData: AnyObject {
}
public protocol AffiliateProgramSetupScreenInitialData: AnyObject {
}
public enum CollectibleItemInfoScreenSubject {
case phoneNumber(String)
case username(String)
@ -1052,6 +1055,9 @@ public protocol SharedAccountContext: AnyObject {
func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?)
func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id) -> Signal<AffiliateProgramSetupScreenInitialData, NoError>
func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController
func makeDebugSettingsController(context: AccountContext?) -> ViewController?
func navigateToCurrentCall()

View File

@ -1,15 +1,22 @@
import Foundation
import UIKit
public enum HStackAlignment {
case left
case alternatingLeftRight
}
public final class HStack<ChildEnvironment: Equatable>: CombinedComponent {
public typealias EnvironmentType = ChildEnvironment
private let items: [AnyComponentWithIdentity<ChildEnvironment>]
private let spacing: CGFloat
private let alignment: HStackAlignment
public init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], spacing: CGFloat) {
public init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], spacing: CGFloat, alignment: HStackAlignment = .left) {
self.items = items
self.spacing = spacing
self.alignment = alignment
}
public static func ==(lhs: HStack<ChildEnvironment>, rhs: HStack<ChildEnvironment>) -> Bool {
@ -19,6 +26,9 @@ public final class HStack<ChildEnvironment: Equatable>: CombinedComponent {
if lhs.spacing != rhs.spacing {
return false
}
if lhs.alignment != rhs.alignment {
return false
}
return true
}
@ -42,21 +52,51 @@ public final class HStack<ChildEnvironment: Equatable>: CombinedComponent {
}
var size = CGSize(width: 0.0, height: 0.0)
for child in updatedChildren {
size.width += child.size.width
size.height = max(size.height, child.size.height)
}
size.width += context.component.spacing * CGFloat(updatedChildren.count - 1)
var nextX = 0.0
for child in updatedChildren {
context.add(child
.position(child.size.centered(in: CGRect(origin: CGPoint(x: nextX, y: floor((size.height - child.size.height) * 0.5)), size: child.size)).center)
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
)
nextX += child.size.width
nextX += context.component.spacing
switch context.component.alignment {
case .left:
for child in updatedChildren {
size.width += child.size.width
size.height = max(size.height, child.size.height)
}
size.width += context.component.spacing * CGFloat(updatedChildren.count - 1)
var nextX = 0.0
for child in updatedChildren {
context.add(child
.position(child.size.centered(in: CGRect(origin: CGPoint(x: nextX, y: floor((size.height - child.size.height) * 0.5)), size: child.size)).center)
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
)
nextX += child.size.width
nextX += context.component.spacing
}
case .alternatingLeftRight:
size.width = context.availableSize.width
for child in updatedChildren {
size.height = max(size.height, child.size.height)
}
var nextLeftX = 0.0
var nextRightX = size.width
for i in 0 ..< updatedChildren.count {
let child = updatedChildren[i]
let childFrame: CGRect
if i % 2 == 0 {
childFrame = CGRect(origin: CGPoint(x: nextLeftX, y: floor((size.height - child.size.height) * 0.5)), size: child.size)
nextLeftX += child.size.width
nextLeftX += context.component.spacing
} else {
childFrame = CGRect(origin: CGPoint(x: nextRightX - child.size.width, y: floor((size.height - child.size.height) * 0.5)), size: child.size)
nextRightX -= child.size.width
nextRightX -= context.component.spacing
}
context.add(child
.position(child.size.centered(in: childFrame).center)
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
)
}
}
return size

View File

@ -462,6 +462,7 @@ swift_library(
"//submodules/TelegramUI/Components/Stars/StarsIntroScreen",
"//submodules/TelegramUI/Components/Gifts/GiftOptionsScreen",
"//submodules/TelegramUI/Components/ContentReportScreen",
"//submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen",
] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
"//build-system:ios_sim_arm64": [],

View File

@ -1363,42 +1363,44 @@ private final class ChatSendStarsScreenComponent: Component {
let sliderSize = self.slider.update(
transition: transition,
component: AnyComponent(SliderComponent(
valueCount: self.amount.maxSliderValue + 1,
value: self.amount.sliderValue,
markPositions: false,
content: .discrete(SliderComponent.Discrete(
valueCount: self.amount.maxSliderValue + 1,
value: self.amount.sliderValue,
markPositions: false,
valueUpdated: { [weak self] value in
guard let self, let component = self.component else {
return
}
self.amount = self.amount.withSliderValue(value)
self.didChangeAmount = true
self.state?.updated(transition: ComponentTransition(animation: .none).withUserData(IsAdjustingAmountHint()))
let sliderValue = Float(value) / Float(component.maxAmount)
let currentTimestamp = CACurrentMediaTime()
if let previousTimestamp {
let deltaTime = currentTimestamp - previousTimestamp
let delta = sliderValue - self.previousSliderValue
let deltaValue = abs(sliderValue - self.previousSliderValue)
let speed = deltaValue / Float(deltaTime)
let newSpeed = max(0, min(65.0, speed * 70.0))
if newSpeed < 0.01 && deltaValue < 0.001 {
} else {
self.badgeStars.update(speed: newSpeed, delta: delta)
}
}
self.previousSliderValue = sliderValue
self.previousTimestamp = currentTimestamp
}
)),
trackBackgroundColor: .clear,
trackForegroundColor: .clear,
knobSize: 26.0,
knobColor: .white,
valueUpdated: { [weak self] value in
guard let self, let component = self.component else {
return
}
self.amount = self.amount.withSliderValue(value)
self.didChangeAmount = true
self.state?.updated(transition: ComponentTransition(animation: .none).withUserData(IsAdjustingAmountHint()))
let sliderValue = Float(value) / Float(component.maxAmount)
let currentTimestamp = CACurrentMediaTime()
if let previousTimestamp {
let deltaTime = currentTimestamp - previousTimestamp
let delta = sliderValue - self.previousSliderValue
let deltaValue = abs(sliderValue - self.previousSliderValue)
let speed = deltaValue / Float(deltaTime)
let newSpeed = max(0, min(65.0, speed * 70.0))
if newSpeed < 0.01 && deltaValue < 0.001 {
} else {
self.badgeStars.update(speed: newSpeed, delta: delta)
}
}
self.previousSliderValue = sliderValue
self.previousTimestamp = currentTimestamp
},
isTrackingUpdated: { [weak self] isTracking in
guard let self else {
return

View File

@ -9,43 +9,91 @@ import ListSectionComponent
import SliderComponent
public final class ListItemSliderSelectorComponent: Component {
public final class Discrete: Equatable {
public let values: [String]
public let markPositions: Bool
public let selectedIndex: Int
public let title: String?
public let selectedIndexUpdated: (Int) -> Void
public init(values: [String], markPositions: Bool, selectedIndex: Int, title: String?, selectedIndexUpdated: @escaping (Int) -> Void) {
self.values = values
self.markPositions = markPositions
self.selectedIndex = selectedIndex
self.title = title
self.selectedIndexUpdated = selectedIndexUpdated
}
public static func ==(lhs: Discrete, rhs: Discrete) -> Bool {
if lhs.values != rhs.values {
return false
}
if lhs.markPositions != rhs.markPositions {
return false
}
if lhs.selectedIndex != rhs.selectedIndex {
return false
}
if lhs.title != rhs.title {
return false
}
return true
}
}
public final class Continuous: Equatable {
public let value: CGFloat
public let lowerBoundTitle: String
public let upperBoundTitle: String
public let title: String
public let valueUpdated: (CGFloat) -> Void
public init(value: CGFloat, lowerBoundTitle: String, upperBoundTitle: String, title: String, valueUpdated: @escaping (CGFloat) -> Void) {
self.value = value
self.lowerBoundTitle = lowerBoundTitle
self.upperBoundTitle = upperBoundTitle
self.title = title
self.valueUpdated = valueUpdated
}
public static func ==(lhs: Continuous, rhs: Continuous) -> Bool {
if lhs.value != rhs.value {
return false
}
if lhs.lowerBoundTitle != rhs.lowerBoundTitle {
return false
}
if lhs.upperBoundTitle != rhs.upperBoundTitle {
return false
}
if lhs.title != rhs.title {
return false
}
return true
}
}
public enum Content: Equatable {
case discrete(Discrete)
case continuous(Continuous)
}
public let theme: PresentationTheme
public let values: [String]
public let markPositions: Bool
public let selectedIndex: Int
public let title: String?
public let selectedIndexUpdated: (Int) -> Void
public let content: Content
public init(
theme: PresentationTheme,
values: [String],
markPositions: Bool,
selectedIndex: Int,
title: String?,
selectedIndexUpdated: @escaping (Int) -> Void
content: Content
) {
self.theme = theme
self.values = values
self.markPositions = markPositions
self.selectedIndex = selectedIndex
self.title = title
self.selectedIndexUpdated = selectedIndexUpdated
self.content = content
}
public static func ==(lhs: ListItemSliderSelectorComponent, rhs: ListItemSliderSelectorComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.values != rhs.values {
return false
}
if lhs.markPositions != rhs.markPositions {
return false
}
if lhs.selectedIndex != rhs.selectedIndex {
return false
}
if lhs.title != rhs.title {
if lhs.content != rhs.content {
return false
}
return true
@ -81,48 +129,90 @@ public final class ListItemSliderSelectorComponent: Component {
let titleAreaWidth: CGFloat = availableSize.width - titleSideInset * 2.0
var validIds: [Int] = []
for i in 0 ..< component.values.count {
if component.title != nil {
if i != 0 && i != component.values.count - 1 {
continue
var mainTitleValue: String?
switch component.content {
case let .discrete(discrete):
mainTitleValue = discrete.title
for i in 0 ..< discrete.values.count {
if discrete.title != nil {
if i != 0 && i != discrete.values.count - 1 {
continue
}
}
validIds.append(i)
var titleTransition = transition
let title: ComponentView<Empty>
if let current = self.titles[i] {
title = current
} else {
titleTransition = titleTransition.withAnimation(.none)
title = ComponentView()
self.titles[i] = title
}
let titleSize = title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: discrete.values[i], font: Font.regular(13.0), textColor: component.theme.list.itemSecondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
var titleFrame = CGRect(origin: CGPoint(x: titleSideInset - floor(titleSize.width * 0.5), y: 14.0), size: titleSize)
if discrete.values.count > 1 {
titleFrame.origin.x += floor(CGFloat(i) / CGFloat(discrete.values.count - 1) * titleAreaWidth)
}
if titleFrame.minX < titleClippingSideInset {
titleFrame.origin.x = titleSideInset
}
if titleFrame.maxX > availableSize.width - titleClippingSideInset {
titleFrame.origin.x = availableSize.width - titleClippingSideInset - titleSize.width
}
if let titleView = title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
titleTransition.setPosition(view: titleView, position: titleFrame.center)
}
}
case let .continuous(continuous):
mainTitleValue = continuous.title
validIds.append(i)
var titleTransition = transition
let title: ComponentView<Empty>
if let current = self.titles[i] {
title = current
} else {
titleTransition = titleTransition.withAnimation(.none)
title = ComponentView()
self.titles[i] = title
}
let titleSize = title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.values[i], font: Font.regular(13.0), textColor: component.theme.list.itemSecondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
var titleFrame = CGRect(origin: CGPoint(x: titleSideInset - floor(titleSize.width * 0.5), y: 14.0), size: titleSize)
if component.values.count > 1 {
titleFrame.origin.x += floor(CGFloat(i) / CGFloat(component.values.count - 1) * titleAreaWidth)
}
if titleFrame.minX < titleClippingSideInset {
titleFrame.origin.x = titleSideInset
}
if titleFrame.maxX > availableSize.width - titleClippingSideInset {
titleFrame.origin.x = availableSize.width - titleClippingSideInset - titleSize.width
}
if let titleView = title.view {
if titleView.superview == nil {
self.addSubview(titleView)
for i in 0 ..< 2 {
validIds.append(i)
var titleTransition = transition
let title: ComponentView<Empty>
if let current = self.titles[i] {
title = current
} else {
titleTransition = titleTransition.withAnimation(.none)
title = ComponentView()
self.titles[i] = title
}
let titleSize = title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: i == 0 ? continuous.lowerBoundTitle : continuous.upperBoundTitle, font: Font.regular(13.0), textColor: component.theme.list.itemSecondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
var titleFrame = CGRect(origin: CGPoint(x: titleSideInset, y: 14.0), size: titleSize)
if i == 1 {
titleFrame.origin.x = availableSize.width - titleClippingSideInset - titleSize.width
}
if let titleView = title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
titleTransition.setPosition(view: titleView, position: titleFrame.center)
}
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
titleTransition.setPosition(view: titleView, position: titleFrame.center)
}
}
var removeIds: [Int] = []
@ -136,7 +226,7 @@ public final class ListItemSliderSelectorComponent: Component {
self.titles.removeValue(forKey: id)
}
if let title = component.title {
if let title = mainTitleValue {
let mainTitle: ComponentView<Empty>
var mainTitleTransition = transition
if let current = self.mainTitle {
@ -169,24 +259,50 @@ public final class ListItemSliderSelectorComponent: Component {
}
}
let sliderSize = self.slider.update(
transition: transition,
component: AnyComponent(SliderComponent(
valueCount: component.values.count,
value: component.selectedIndex,
markPositions: component.markPositions,
trackBackgroundColor: component.theme.list.controlSecondaryColor,
trackForegroundColor: component.theme.list.itemAccentColor,
valueUpdated: { [weak self] value in
guard let self, let component = self.component else {
return
}
component.selectedIndexUpdated(value)
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
)
let sliderSize: CGSize
switch component.content {
case let .discrete(discrete):
sliderSize = self.slider.update(
transition: transition,
component: AnyComponent(SliderComponent(
content: .discrete(SliderComponent.Discrete(
valueCount: discrete.values.count,
value: discrete.selectedIndex,
markPositions: discrete.markPositions,
valueUpdated: { [weak self] value in
guard let self, let component = self.component, case let .discrete(discrete) = component.content else {
return
}
discrete.selectedIndexUpdated(value)
})
),
trackBackgroundColor: component.theme.list.controlSecondaryColor,
trackForegroundColor: component.theme.list.itemAccentColor
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
)
case let .continuous(continuous):
sliderSize = self.slider.update(
transition: transition,
component: AnyComponent(SliderComponent(
content: .continuous(SliderComponent.Continuous(
value: continuous.value,
valueUpdated: { [weak self] value in
guard let self, let component = self.component, case let .continuous(continuous) = component.content else {
return
}
continuous.valueUpdated(value)
})
),
trackBackgroundColor: component.theme.list.controlSecondaryColor,
trackForegroundColor: component.theme.list.itemAccentColor
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
)
}
let sliderFrame = CGRect(origin: CGPoint(x: sideInset, y: 36.0), size: sliderSize)
if let sliderView = self.slider.view {
if sliderView.superview == nil {

View File

@ -1281,20 +1281,22 @@ final class PeerAllowedReactionsScreenComponent: Component {
items: [
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent(
theme: environment.theme,
values: reactionCountValueList.map { item in
return item
},
markPositions: false,
selectedIndex: max(0, min(reactionCountValueList.count - 1, self.allowedReactionCount - 1)),
title: sliderTitle,
selectedIndexUpdated: { [weak self] index in
guard let self else {
return
content: .discrete(ListItemSliderSelectorComponent.Discrete(
values: reactionCountValueList.map { item in
return item
},
markPositions: false,
selectedIndex: max(0, min(reactionCountValueList.count - 1, self.allowedReactionCount - 1)),
title: sliderTitle,
selectedIndexUpdated: { [weak self] index in
guard let self else {
return
}
let index = max(1, min(reactionCountValueList.count, index + 1))
self.allowedReactionCount = index
self.state?.updated(transition: .immediate)
}
let index = max(1, min(reactionCountValueList.count, index + 1))
self.allowedReactionCount = index
self.state?.updated(transition: .immediate)
}
))
)))
],
displaySeparators: false

View File

@ -0,0 +1,36 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AffiliateProgramSetupScreen",
module_name = "AffiliateProgramSetupScreen",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/AsyncDisplayKit",
"//submodules/TelegramCore",
"//submodules/Postbox",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/TelegramStringFormatting",
"//submodules/ComponentFlow",
"//submodules/AppBundle",
"//submodules/UndoUI",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListItemSliderSelectorComponent",
"//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/Components/BlurredBackgroundComponent",
"//submodules/Markdown",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,715 @@
import Foundation
import UIKit
import Display
import TelegramPresentationData
import ComponentFlow
import ComponentDisplayAdapters
import AppBundle
import ViewControllerComponent
import AccountContext
import TelegramCore
import Postbox
import SwiftSignalKit
import MultilineTextComponent
import ButtonComponent
import UndoUI
import BundleIconComponent
import ListSectionComponent
import ListItemSliderSelectorComponent
import ListActionItemComponent
import Markdown
import BlurredBackgroundComponent
final class AffiliateProgramSetupScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let initialContent: AffiliateProgramSetupScreen.Content
init(
context: AccountContext,
initialContent: AffiliateProgramSetupScreen.Content
) {
self.context = context
self.initialContent = initialContent
}
static func ==(lhs: AffiliateProgramSetupScreenComponent, rhs: AffiliateProgramSetupScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
final class View: UIView, UIScrollViewDelegate {
private let scrollView: UIScrollView
private let title = ComponentView<Empty>()
private let titleTransformContainer: UIView
private let subtitle = ComponentView<Empty>()
private let introBackground = ComponentView<Empty>()
private var introIconItems: [Int: ComponentView<Empty>] = [:]
private var introTitleItems: [Int: ComponentView<Empty>] = [:]
private var introTextItems: [Int: ComponentView<Empty>] = [:]
private let commissionSection = ComponentView<Empty>()
private let durationSection = ComponentView<Empty>()
private let existingProgramsSection = ComponentView<Empty>()
private let endProgramSection = ComponentView<Empty>()
private let bottomPanelSeparator = SimpleLayer()
private let bottomPanelBackground = ComponentView<Empty>()
private let bottomPanelButton = ComponentView<Empty>()
private let bottomPanelText = ComponentView<Empty>()
private var isUpdating: Bool = false
private var component: AffiliateProgramSetupScreenComponent?
private(set) weak var state: EmptyComponentState?
private var environment: EnvironmentType?
override init(frame: CGRect) {
self.scrollView = UIScrollView()
self.scrollView.showsVerticalScrollIndicator = true
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.scrollsToTop = false
self.scrollView.delaysContentTouches = false
self.scrollView.canCancelContentTouches = true
self.scrollView.contentInsetAdjustmentBehavior = .never
self.scrollView.alwaysBounceVertical = true
self.titleTransformContainer = UIView()
self.scrollView.addSubview(self.titleTransformContainer)
super.init(frame: frame)
self.scrollView.delegate = self
self.addSubview(self.scrollView)
self.layer.addSublayer(self.bottomPanelSeparator)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
func scrollToTop() {
self.scrollView.setContentOffset(CGPoint(), animated: true)
}
func attemptNavigation(complete: @escaping () -> Void) -> Bool {
return true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateScrolling(transition: .immediate)
}
private func updateScrolling(transition: ComponentTransition) {
let navigationAlphaDistance: CGFloat = 16.0
let navigationAlpha: CGFloat = max(0.0, min(1.0, self.scrollView.contentOffset.y / navigationAlphaDistance))
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha)
transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha)
}
}
func update(component: AffiliateProgramSetupScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
let environment = environment[EnvironmentType.self].value
let themeUpdated = self.environment?.theme !== environment.theme
self.environment = environment
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
if themeUpdated {
self.backgroundColor = environment.theme.list.blocksBackgroundColor
self.bottomPanelSeparator.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
}
self.component = component
self.state = state
let topInset: CGFloat = environment.navigationHeight + 87.0
let bottomInset: CGFloat = 8.0
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let textSideInset: CGFloat = 16.0
let sectionSpacing: CGFloat = 24.0
var contentHeight: CGFloat = 0.0
contentHeight += topInset
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Affiliate Program", font: Font.bold(30.0), textColor: environment.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - textSideInset * 2.0, height: 1000.0)
)
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: contentHeight), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.titleTransformContainer.addSubview(titleView)
}
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
transition.setPosition(view: self.titleTransformContainer, position: titleFrame.center)
}
contentHeight += titleSize.height
contentHeight += 10.0
let subtitleSize = self.subtitle.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Reward those who help grow your userbase.", font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)),
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: availableSize.width - textSideInset * 2.0, height: 1000.0)
)
let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) * 0.5), y: contentHeight), size: subtitleSize)
if let subtitleView = self.subtitle.view {
if subtitleView.superview == nil {
self.scrollView.addSubview(subtitleView)
subtitleView.bounds = CGRect(origin: CGPoint(), size: subtitleFrame.size)
transition.setPosition(view: subtitleView, position: subtitleFrame.center)
}
}
contentHeight += subtitleSize.height
contentHeight += 24.0
let introItems: [(icon: String, title: String, text: String)] = [
(
"Chat/Context Menu/Smile",
"Share revenue with affiliates",
"Set the commission for revenue generated by users referred to you."
),
(
"Chat/Context Menu/Smile",
"Launch your affiliate program",
"Telegram will feature your program for millions of potential affiliates."
),
(
"Chat/Context Menu/Smile",
"Let affiliates promote you",
"Affiliates will share your referral link with their audience."
)
]
var introItemsHeight: CGFloat = 17.0
let introItemIconX: CGFloat = sideInset + 19.0
let introItemTextX: CGFloat = sideInset + 56.0
let introItemTextRightInset: CGFloat = sideInset + 10.0
let introItemSpacing: CGFloat = 22.0
for i in 0 ..< introItems.count {
if i != 0 {
introItemsHeight += introItemSpacing
}
let item = introItems[i]
let itemIcon: ComponentView<Empty>
let itemTitle: ComponentView<Empty>
let itemText: ComponentView<Empty>
if let current = self.introIconItems[i] {
itemIcon = current
} else {
itemIcon = ComponentView()
self.introIconItems[i] = itemIcon
}
if let current = self.introTitleItems[i] {
itemTitle = current
} else {
itemTitle = ComponentView()
self.introTitleItems[i] = itemTitle
}
if let current = self.introTextItems[i] {
itemText = current
} else {
itemText = ComponentView()
self.introTextItems[i] = itemText
}
let iconSize = itemIcon.update(
transition: .immediate,
component: AnyComponent(BundleIconComponent(
name: item.icon,
tintColor: environment.theme.list.itemAccentColor
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let titleSize = itemTitle.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: item.title, font: Font.semibold(15.0), textColor: environment.theme.list.itemPrimaryTextColor)),
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: availableSize.width - introItemTextRightInset - introItemTextX, height: 1000.0)
)
let textSize = itemText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: item.text, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor)),
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: availableSize.width - introItemTextRightInset - introItemTextX, height: 1000.0)
)
let itemIconFrame = CGRect(origin: CGPoint(x: introItemIconX, y: contentHeight + introItemsHeight + 8.0), size: iconSize)
let itemTitleFrame = CGRect(origin: CGPoint(x: introItemTextX, y: contentHeight + introItemsHeight), size: titleSize)
let itemTextFrame = CGRect(origin: CGPoint(x: introItemTextX, y: itemTitleFrame.maxY + 5.0), size: textSize)
if let itemIconView = itemIcon.view {
if itemIconView.superview == nil {
self.scrollView.addSubview(itemIconView)
}
transition.setFrame(view: itemIconView, frame: itemIconFrame)
}
if let itemTitleView = itemTitle.view {
if itemTitleView.superview == nil {
itemTitleView.layer.anchorPoint = CGPoint()
self.scrollView.addSubview(itemTitleView)
}
transition.setPosition(view: itemTitleView, position: itemTitleFrame.origin)
itemTitleView.bounds = CGRect(origin: CGPoint(), size: itemTitleFrame.size)
}
if let itemTextView = itemText.view {
if itemTextView.superview == nil {
itemTextView.layer.anchorPoint = CGPoint()
self.scrollView.addSubview(itemTextView)
}
transition.setPosition(view: itemTextView, position: itemTextFrame.origin)
itemTextView.bounds = CGRect(origin: CGPoint(), size: itemTextFrame.size)
}
introItemsHeight = itemTextFrame.maxY - contentHeight
}
introItemsHeight += 19.0
let introBackgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: introItemsHeight))
let _ = self.introBackground.update(
transition: transition,
component: AnyComponent(FilledRoundedRectangleComponent(
color: environment.theme.list.itemBlocksBackgroundColor,
cornerRadius: .value(5.0),
smoothCorners: true
)),
environment: {},
containerSize: introBackgroundFrame.size
)
if let introBackgroundView = self.introBackground.view {
if introBackgroundView.superview == nil, let firstIconItemView = self.introIconItems[0]?.view {
self.scrollView.insertSubview(introBackgroundView, belowSubview: firstIconItemView)
}
transition.setFrame(view: introBackgroundView, frame: introBackgroundFrame)
}
contentHeight += introItemsHeight
contentHeight += sectionSpacing + 6.0
let commissionSectionSize = self.commissionSection.update(
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "COMMISSION",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Define the percentage of star revenue your affiliates earn for referring users to your bot.",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
items: [
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent(
theme: environment.theme,
content: .continuous(ListItemSliderSelectorComponent.Continuous(
value: 0.0,
lowerBoundTitle: "1%",
upperBoundTitle: "90%",
title: "1%",
valueUpdated: { [weak self] value in
guard let self else {
return
}
let _ = self
}
))
)))
],
displaySeparators: false
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
)
let commissionSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: commissionSectionSize)
if let commissionSectionView = self.commissionSection.view {
if commissionSectionView.superview == nil {
self.scrollView.addSubview(commissionSectionView)
}
transition.setFrame(view: commissionSectionView, frame: commissionSectionFrame)
}
contentHeight += commissionSectionSize.height
contentHeight += sectionSpacing + 12.0
let durationItems: [(months: Int32, title: String, selectedTitle: String)] = [
(1, "1m", "1 MONTH"),
(3, "3m", "3 MONTHS"),
(6, "6m", "6 MONTHS"),
(12, "1y", "1 YEAR"),
(2 * 12, "2y", "2 YEARS"),
(3 * 12, "3y", "3 YEARS"),
(Int32.max, "", "INDEFINITELY")
]
let durationSectionSize = self.durationSection.update(
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
header: AnyComponent(HStack([
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "DURATION",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
))),
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: durationItems[0].selectedTitle,
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)))
], spacing: 4.0, alignment: .alternatingLeftRight)),
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Set the duration for which affiliates will earn commissions from referred users.",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
items: [
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent(
theme: environment.theme,
content: .discrete(ListItemSliderSelectorComponent.Discrete(
values: durationItems.map(\.title),
markPositions: true,
selectedIndex: 0,
title: nil,
selectedIndexUpdated: { [weak self] value in
guard let self else {
return
}
let _ = self
}
))
)))
],
displaySeparators: false
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
)
let durationSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: durationSectionSize)
if let durationSectionView = self.durationSection.view {
if durationSectionView.superview == nil {
self.scrollView.addSubview(durationSectionView)
}
transition.setFrame(view: durationSectionView, frame: durationSectionFrame)
}
contentHeight += durationSectionSize.height
contentHeight += sectionSpacing + 12.0
let existingProgramsSectionSize = self.existingProgramsSection.update(
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
header: nil,
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Explore what other mini apps offer.",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
items: [
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "View Existing Programs",
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
maximumNumberOfLines: 1
))),
], alignment: .left, spacing: 2.0)),
accessory: .arrow,
action: { [weak self] _ in
guard let self else {
return
}
let _ = self
}
)))
],
displaySeparators: false
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
)
let existingProgramsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: existingProgramsSectionSize)
if let existingProgramsSectionView = self.existingProgramsSection.view {
if existingProgramsSectionView.superview == nil {
self.scrollView.addSubview(existingProgramsSectionView)
}
transition.setFrame(view: existingProgramsSectionView, frame: existingProgramsSectionFrame)
}
contentHeight += existingProgramsSectionSize.height
contentHeight += sectionSpacing + 12.0
let endProgramSectionSize = self.endProgramSection.update(
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
header: nil,
footer: nil,
items: [
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "End Affiliate Program",
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemDestructiveColor
)),
maximumNumberOfLines: 1
))),
], alignment: .center, spacing: 2.0)),
accessory: nil,
action: { [weak self] _ in
guard let self else {
return
}
let _ = self
}
)))
],
displaySeparators: false
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
)
let endProgramSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: endProgramSectionSize)
if let endProgramSectionView = self.endProgramSection.view {
if endProgramSectionView.superview == nil {
self.scrollView.addSubview(endProgramSectionView)
}
transition.setFrame(view: endProgramSectionView, frame: endProgramSectionFrame)
}
contentHeight += endProgramSectionSize.height
contentHeight += sectionSpacing
contentHeight += bottomInset
let bottomPanelTextSize = self.bottomPanelText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .markdown(
text: "By creating an affiliate program, you afree to the [terms and conditions](https://telegram.org/terms) of Affiliate Programs.",
attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemSecondaryTextColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor),
linkAttribute: { url in
return ("URL", url)
}
)
),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
)
let bottomPanelButtonInsets = UIEdgeInsets(top: 10.0, left: sideInset, bottom: 10.0, right: sideInset)
let bottomPanelButtonSize = self.bottomPanelButton.update(
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
color: environment.theme.list.itemCheckColors.fillColor,
foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
),
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(Text(text: "Start Affiliate Program", font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor))),
isEnabled: true,
allowActionWhenDisabled: true,
displaysProgress: false,
action: { [weak self] in
guard let self else {
return
}
let _ = self
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - bottomPanelButtonInsets.left - bottomPanelButtonInsets.right, height: 50.0)
)
let bottomPanelHeight: CGFloat = bottomPanelButtonInsets.top + bottomPanelButtonSize.height + bottomPanelButtonInsets.bottom + bottomPanelTextSize.height + 8.0 + environment.safeInsets.bottom
let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight), size: CGSize(width: availableSize.width, height: bottomPanelHeight))
let _ = self.bottomPanelBackground.update(
transition: transition,
component: AnyComponent(BlurredBackgroundComponent(
color: environment.theme.rootController.navigationBar.blurredBackgroundColor
)),
environment: {},
containerSize: bottomPanelFrame.size
)
if let bottomPanelBackgroundView = self.bottomPanelBackground.view {
if bottomPanelBackgroundView.superview == nil {
self.addSubview(bottomPanelBackgroundView)
}
transition.setFrame(view: bottomPanelBackgroundView, frame: bottomPanelFrame)
}
transition.setFrame(layer: self.bottomPanelSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: bottomPanelFrame.minY - UIScreenPixel), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
let bottomPanelButtonFrame = CGRect(origin: CGPoint(x: bottomPanelFrame.minX + bottomPanelButtonInsets.left, y: bottomPanelFrame.minY + bottomPanelButtonInsets.top), size: bottomPanelButtonSize)
if let bottomPanelButtonView = self.bottomPanelButton.view {
if bottomPanelButtonView.superview == nil {
self.addSubview(bottomPanelButtonView)
}
transition.setFrame(view: bottomPanelButtonView, frame: bottomPanelButtonFrame)
}
let bottomPanelTextFrame = CGRect(origin: CGPoint(x: bottomPanelFrame.minX + floor((bottomPanelFrame.width - bottomPanelTextSize.width) * 0.5), y: bottomPanelButtonFrame.maxY + bottomPanelButtonInsets.bottom), size: bottomPanelTextSize)
if let bottomPanelTextView = self.bottomPanelText.view {
if bottomPanelTextView.superview == nil {
self.addSubview(bottomPanelTextView)
}
transition.setPosition(view: bottomPanelTextView, position: bottomPanelTextFrame.center)
bottomPanelTextView.bounds = CGRect(origin: CGPoint(), size: bottomPanelTextFrame.size)
}
contentHeight += bottomPanelFrame.height
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) {
self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize)
}
if self.scrollView.contentSize != contentSize {
self.scrollView.contentSize = contentSize
}
let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: environment.safeInsets.bottom, right: 0.0)
if self.scrollView.scrollIndicatorInsets != scrollInsets {
self.scrollView.scrollIndicatorInsets = scrollInsets
}
self.updateScrolling(transition: transition)
return availableSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public class AffiliateProgramSetupScreen: ViewControllerComponentContainer {
public final class Content: AffiliateProgramSetupScreenInitialData {
let peerId: EnginePeer.Id
init(
peerId: EnginePeer.Id
) {
self.peerId = peerId
}
}
private let context: AccountContext
private var isDismissed: Bool = false
public init(
context: AccountContext,
initialContent: Content
) {
self.context = context
super.init(context: context, component: AffiliateProgramSetupScreenComponent(
context: context,
initialContent: initialContent
), navigationBarAppearance: .default, theme: .default)
self.scrollToTop = { [weak self] in
guard let self, let componentView = self.node.hostView.componentView as? AffiliateProgramSetupScreenComponent.View else {
return
}
componentView.scrollToTop()
}
self.attemptNavigation = { [weak self] complete in
guard let self, let componentView = self.node.hostView.componentView as? AffiliateProgramSetupScreenComponent.View else {
return true
}
return componentView.attemptNavigation(complete: complete)
}
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
@objc private func cancelPressed() {
self.dismiss()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
}
public static func content(context: AccountContext, peerId: EnginePeer.Id) -> Signal<AffiliateProgramSetupScreenInitialData, NoError> {
return .single(Content(
peerId: peerId
))
}
}

View File

@ -607,6 +607,7 @@ private final class PeerInfoInteraction {
let openWorkingHoursContextMenu: (ASDisplayNode, ContextGesture?) -> Void
let openBusinessLocationContextMenu: (ASDisplayNode, ContextGesture?) -> Void
let openBirthdayContextMenu: (ASDisplayNode, ContextGesture?) -> Void
let editingOpenAffiliateProgram: () -> Void
let getController: () -> ViewController?
init(
@ -675,6 +676,7 @@ private final class PeerInfoInteraction {
openWorkingHoursContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
openBusinessLocationContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
openBirthdayContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
editingOpenAffiliateProgram: @escaping () -> Void,
getController: @escaping () -> ViewController?
) {
self.openUsername = openUsername
@ -742,6 +744,7 @@ private final class PeerInfoInteraction {
self.openWorkingHoursContextMenu = openWorkingHoursContextMenu
self.openBusinessLocationContextMenu = openBusinessLocationContextMenu
self.openBirthdayContextMenu = openBirthdayContextMenu
self.editingOpenAffiliateProgram = editingOpenAffiliateProgram
self.getController = getController
}
}
@ -1906,6 +1909,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
let ItemInfo = 3
let ItemDelete = 4
let ItemUsername = 5
let ItemAffiliateProgram = 6
let ItemIntro = 7
let ItemCommands = 8
@ -1916,6 +1920,10 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text("@\(user.addressName ?? "")"), text: presentationData.strings.PeerInfo_Bot_Username, icon: PresentationResourcesSettings.bot, action: {
interaction.editingOpenPublicLinkSetup()
}))
//TODO:localize
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliateProgram, label: .text("Off"), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Affiliate Program", icon: PresentationResourcesSettings.bot, action: {
interaction.editingOpenAffiliateProgram()
}))
items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemIntro, text: presentationData.strings.PeerInfo_Bot_EditIntro, icon: UIImage(bundleImageName: "Peer Info/BotIntro"), action: {
interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-intro", behavior: .interactive)))
@ -3012,6 +3020,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
return
}
self.openBirthdayContextMenu(node: node, gesture: gesture)
}, editingOpenAffiliateProgram: { [weak self] in
guard let self else {
return
}
self.editingOpenAffiliateProgram()
},
getController: { [weak self] in
return self?.controller
@ -8548,6 +8561,19 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
}
private func editingOpenAffiliateProgram() {
if let peer = self.data?.peer as? TelegramUser, peer.botInfo != nil {
let _ = (self.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: self.context, peerId: peer.id)
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
guard let self else {
return
}
let controller = self.context.sharedContext.makeAffiliateProgramSetupScreen(context: self.context, initialData: initialData)
self.controller?.push(controller)
})
}
}
private func editingOpenNameColorSetup() {
if self.peerId == self.context.account.peerId {
let controller = PeerNameColorScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, subject: .account)

View File

@ -1461,20 +1461,22 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
items: [
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent(
theme: environment.theme,
values: valueList.map { item in
return environment.strings.MessageTimer_Days(Int32(item))
},
markPositions: true,
selectedIndex: selectedInactivityIndex,
title: nil,
selectedIndexUpdated: { [weak self] index in
guard let self else {
return
content: .discrete(ListItemSliderSelectorComponent.Discrete(
values: valueList.map { item in
return environment.strings.MessageTimer_Days(Int32(item))
},
markPositions: true,
selectedIndex: selectedInactivityIndex,
title: nil,
selectedIndexUpdated: { [weak self] index in
guard let self else {
return
}
let index = max(0, min(valueList.count - 1, index))
self.inactivityDays = valueList[index]
self.state?.updated(transition: .immediate)
}
let index = max(0, min(valueList.count - 1, index))
self.inactivityDays = valueList[index]
self.state?.updated(transition: .immediate)
}
))
)))
]
)),

View File

@ -7,46 +7,80 @@ import LegacyComponents
import ComponentFlow
public final class SliderComponent: Component {
public let valueCount: Int
public let value: Int
public let markPositions: Bool
public final class Discrete: Equatable {
public let valueCount: Int
public let value: Int
public let markPositions: Bool
public let valueUpdated: (Int) -> Void
public init(valueCount: Int, value: Int, markPositions: Bool, valueUpdated: @escaping (Int) -> Void) {
self.valueCount = valueCount
self.value = value
self.markPositions = markPositions
self.valueUpdated = valueUpdated
}
public static func ==(lhs: Discrete, rhs: Discrete) -> Bool {
if lhs.valueCount != rhs.valueCount {
return false
}
if lhs.value != rhs.value {
return false
}
if lhs.markPositions != rhs.markPositions {
return false
}
return true
}
}
public final class Continuous: Equatable {
public let value: CGFloat
public let valueUpdated: (CGFloat) -> Void
public init(value: CGFloat, valueUpdated: @escaping (CGFloat) -> Void) {
self.value = value
self.valueUpdated = valueUpdated
}
public static func ==(lhs: Continuous, rhs: Continuous) -> Bool {
if lhs.value != rhs.value {
return false
}
return true
}
}
public enum Content: Equatable {
case discrete(Discrete)
case continuous(Continuous)
}
public let content: Content
public let trackBackgroundColor: UIColor
public let trackForegroundColor: UIColor
public let knobSize: CGFloat?
public let knobColor: UIColor?
public let valueUpdated: (Int) -> Void
public let isTrackingUpdated: ((Bool) -> Void)?
public init(
valueCount: Int,
value: Int,
markPositions: Bool,
content: Content,
trackBackgroundColor: UIColor,
trackForegroundColor: UIColor,
knobSize: CGFloat? = nil,
knobColor: UIColor? = nil,
valueUpdated: @escaping (Int) -> Void,
isTrackingUpdated: ((Bool) -> Void)? = nil
) {
self.valueCount = valueCount
self.value = value
self.markPositions = markPositions
self.content = content
self.trackBackgroundColor = trackBackgroundColor
self.trackForegroundColor = trackForegroundColor
self.knobSize = knobSize
self.knobColor = knobColor
self.valueUpdated = valueUpdated
self.isTrackingUpdated = isTrackingUpdated
}
public static func ==(lhs: SliderComponent, rhs: SliderComponent) -> Bool {
if lhs.valueCount != rhs.valueCount {
return false
}
if lhs.value != rhs.value {
return false
}
if lhs.markPositions != rhs.markPositions {
if lhs.content != rhs.content {
return false
}
if lhs.trackBackgroundColor != rhs.trackBackgroundColor {
@ -122,10 +156,16 @@ public final class SliderComponent: Component {
sliderView.minimumValue = 0.0
sliderView.startValue = 0.0
sliderView.disablesInteractiveTransitionGestureRecognizer = true
sliderView.maximumValue = CGFloat(component.valueCount - 1)
sliderView.positionsCount = component.valueCount
sliderView.useLinesForPositions = true
sliderView.markPositions = component.markPositions
switch component.content {
case let .discrete(discrete):
sliderView.maximumValue = CGFloat(discrete.valueCount - 1)
sliderView.positionsCount = discrete.valueCount
sliderView.useLinesForPositions = true
sliderView.markPositions = discrete.markPositions
case .continuous:
sliderView.maximumValue = 1.0
}
sliderView.backgroundColor = nil
sliderView.isOpaque = false
@ -162,7 +202,12 @@ public final class SliderComponent: Component {
self.sliderView = sliderView
self.addSubview(sliderView)
}
sliderView.value = CGFloat(component.value)
switch component.content {
case let .discrete(discrete):
sliderView.value = CGFloat(discrete.value)
case let .continuous(continuous):
sliderView.value = continuous.value
}
sliderView.interactionBegan = {
internalIsTrackingUpdated?(true)
}
@ -180,7 +225,12 @@ public final class SliderComponent: Component {
guard let component = self.component, let sliderView = self.sliderView else {
return
}
component.valueUpdated(Int(sliderView.value))
switch component.content {
case let .discrete(discrete):
discrete.valueUpdated(Int(sliderView.value))
case let .continuous(continuous):
continuous.valueUpdated(sliderView.value)
}
}
}

View File

@ -74,6 +74,7 @@ import GiftOptionsScreen
import GiftViewScreen
import StarsIntroScreen
import ContentReportScreen
import AffiliateProgramSetupScreen
private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>()
@ -2828,6 +2829,14 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?) {
openWebAppImpl(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: chatPeer, threadId: threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: skipTermsOfService, payload: payload)
}
public func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id) -> Signal<AffiliateProgramSetupScreenInitialData, NoError> {
return AffiliateProgramSetupScreen.content(context: context, peerId: peerId)
}
public func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController {
return AffiliateProgramSetupScreen(context: context, initialContent: initialData as! AffiliateProgramSetupScreen.Content)
}
}
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {