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 BusinessLinksSetupScreenInitialData: AnyObject {
} }
public protocol AffiliateProgramSetupScreenInitialData: AnyObject {
}
public enum CollectibleItemInfoScreenSubject { public enum CollectibleItemInfoScreenSubject {
case phoneNumber(String) case phoneNumber(String)
case username(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 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 makeDebugSettingsController(context: AccountContext?) -> ViewController?
func navigateToCurrentCall() func navigateToCurrentCall()

View File

@ -1,15 +1,22 @@
import Foundation import Foundation
import UIKit import UIKit
public enum HStackAlignment {
case left
case alternatingLeftRight
}
public final class HStack<ChildEnvironment: Equatable>: CombinedComponent { public final class HStack<ChildEnvironment: Equatable>: CombinedComponent {
public typealias EnvironmentType = ChildEnvironment public typealias EnvironmentType = ChildEnvironment
private let items: [AnyComponentWithIdentity<ChildEnvironment>] private let items: [AnyComponentWithIdentity<ChildEnvironment>]
private let spacing: CGFloat 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.items = items
self.spacing = spacing self.spacing = spacing
self.alignment = alignment
} }
public static func ==(lhs: HStack<ChildEnvironment>, rhs: HStack<ChildEnvironment>) -> Bool { 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 { if lhs.spacing != rhs.spacing {
return false return false
} }
if lhs.alignment != rhs.alignment {
return false
}
return true return true
} }
@ -42,6 +52,8 @@ public final class HStack<ChildEnvironment: Equatable>: CombinedComponent {
} }
var size = CGSize(width: 0.0, height: 0.0) var size = CGSize(width: 0.0, height: 0.0)
switch context.component.alignment {
case .left:
for child in updatedChildren { for child in updatedChildren {
size.width += child.size.width size.width += child.size.width
size.height = max(size.height, child.size.height) size.height = max(size.height, child.size.height)
@ -58,6 +70,34 @@ public final class HStack<ChildEnvironment: Equatable>: CombinedComponent {
nextX += child.size.width nextX += child.size.width
nextX += context.component.spacing 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 return size
} }

View File

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

View File

@ -1363,13 +1363,10 @@ private final class ChatSendStarsScreenComponent: Component {
let sliderSize = self.slider.update( let sliderSize = self.slider.update(
transition: transition, transition: transition,
component: AnyComponent(SliderComponent( component: AnyComponent(SliderComponent(
content: .discrete(SliderComponent.Discrete(
valueCount: self.amount.maxSliderValue + 1, valueCount: self.amount.maxSliderValue + 1,
value: self.amount.sliderValue, value: self.amount.sliderValue,
markPositions: false, markPositions: false,
trackBackgroundColor: .clear,
trackForegroundColor: .clear,
knobSize: 26.0,
knobColor: .white,
valueUpdated: { [weak self] value in valueUpdated: { [weak self] value in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
@ -1398,7 +1395,12 @@ private final class ChatSendStarsScreenComponent: Component {
self.previousSliderValue = sliderValue self.previousSliderValue = sliderValue
self.previousTimestamp = currentTimestamp self.previousTimestamp = currentTimestamp
}, }
)),
trackBackgroundColor: .clear,
trackForegroundColor: .clear,
knobSize: 26.0,
knobColor: .white,
isTrackingUpdated: { [weak self] isTracking in isTrackingUpdated: { [weak self] isTracking in
guard let self else { guard let self else {
return return

View File

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

View File

@ -1281,6 +1281,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
items: [ items: [
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent( AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent(
theme: environment.theme, theme: environment.theme,
content: .discrete(ListItemSliderSelectorComponent.Discrete(
values: reactionCountValueList.map { item in values: reactionCountValueList.map { item in
return item return item
}, },
@ -1295,6 +1296,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
self.allowedReactionCount = index self.allowedReactionCount = index
self.state?.updated(transition: .immediate) self.state?.updated(transition: .immediate)
} }
))
))) )))
], ],
displaySeparators: false 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 openWorkingHoursContextMenu: (ASDisplayNode, ContextGesture?) -> Void
let openBusinessLocationContextMenu: (ASDisplayNode, ContextGesture?) -> Void let openBusinessLocationContextMenu: (ASDisplayNode, ContextGesture?) -> Void
let openBirthdayContextMenu: (ASDisplayNode, ContextGesture?) -> Void let openBirthdayContextMenu: (ASDisplayNode, ContextGesture?) -> Void
let editingOpenAffiliateProgram: () -> Void
let getController: () -> ViewController? let getController: () -> ViewController?
init( init(
@ -675,6 +676,7 @@ private final class PeerInfoInteraction {
openWorkingHoursContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, openWorkingHoursContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
openBusinessLocationContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, openBusinessLocationContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
openBirthdayContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, openBirthdayContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
editingOpenAffiliateProgram: @escaping () -> Void,
getController: @escaping () -> ViewController? getController: @escaping () -> ViewController?
) { ) {
self.openUsername = openUsername self.openUsername = openUsername
@ -742,6 +744,7 @@ private final class PeerInfoInteraction {
self.openWorkingHoursContextMenu = openWorkingHoursContextMenu self.openWorkingHoursContextMenu = openWorkingHoursContextMenu
self.openBusinessLocationContextMenu = openBusinessLocationContextMenu self.openBusinessLocationContextMenu = openBusinessLocationContextMenu
self.openBirthdayContextMenu = openBirthdayContextMenu self.openBirthdayContextMenu = openBirthdayContextMenu
self.editingOpenAffiliateProgram = editingOpenAffiliateProgram
self.getController = getController self.getController = getController
} }
} }
@ -1906,6 +1909,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
let ItemInfo = 3 let ItemInfo = 3
let ItemDelete = 4 let ItemDelete = 4
let ItemUsername = 5 let ItemUsername = 5
let ItemAffiliateProgram = 6
let ItemIntro = 7 let ItemIntro = 7
let ItemCommands = 8 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: { items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text("@\(user.addressName ?? "")"), text: presentationData.strings.PeerInfo_Bot_Username, icon: PresentationResourcesSettings.bot, action: {
interaction.editingOpenPublicLinkSetup() 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: { 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))) interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-intro", behavior: .interactive)))
@ -3012,6 +3020,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
return return
} }
self.openBirthdayContextMenu(node: node, gesture: gesture) self.openBirthdayContextMenu(node: node, gesture: gesture)
}, editingOpenAffiliateProgram: { [weak self] in
guard let self else {
return
}
self.editingOpenAffiliateProgram()
}, },
getController: { [weak self] in getController: { [weak self] in
return self?.controller 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() { private func editingOpenNameColorSetup() {
if self.peerId == self.context.account.peerId { if self.peerId == self.context.account.peerId {
let controller = PeerNameColorScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, subject: .account) let controller = PeerNameColorScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, subject: .account)

View File

@ -1461,6 +1461,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
items: [ items: [
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent( AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent(
theme: environment.theme, theme: environment.theme,
content: .discrete(ListItemSliderSelectorComponent.Discrete(
values: valueList.map { item in values: valueList.map { item in
return environment.strings.MessageTimer_Days(Int32(item)) return environment.strings.MessageTimer_Days(Int32(item))
}, },
@ -1475,6 +1476,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
self.inactivityDays = valueList[index] self.inactivityDays = valueList[index]
self.state?.updated(transition: .immediate) self.state?.updated(transition: .immediate)
} }
))
))) )))
] ]
)), )),

View File

@ -7,39 +7,20 @@ import LegacyComponents
import ComponentFlow import ComponentFlow
public final class SliderComponent: Component { public final class SliderComponent: Component {
public final class Discrete: Equatable {
public let valueCount: Int public let valueCount: Int
public let value: Int public let value: Int
public let markPositions: Bool public let markPositions: Bool
public let trackBackgroundColor: UIColor
public let trackForegroundColor: UIColor
public let knobSize: CGFloat?
public let knobColor: UIColor?
public let valueUpdated: (Int) -> Void public let valueUpdated: (Int) -> Void
public let isTrackingUpdated: ((Bool) -> Void)?
public init( public init(valueCount: Int, value: Int, markPositions: Bool, valueUpdated: @escaping (Int) -> Void) {
valueCount: Int,
value: Int,
markPositions: Bool,
trackBackgroundColor: UIColor,
trackForegroundColor: UIColor,
knobSize: CGFloat? = nil,
knobColor: UIColor? = nil,
valueUpdated: @escaping (Int) -> Void,
isTrackingUpdated: ((Bool) -> Void)? = nil
) {
self.valueCount = valueCount self.valueCount = valueCount
self.value = value self.value = value
self.markPositions = markPositions self.markPositions = markPositions
self.trackBackgroundColor = trackBackgroundColor
self.trackForegroundColor = trackForegroundColor
self.knobSize = knobSize
self.knobColor = knobColor
self.valueUpdated = valueUpdated self.valueUpdated = valueUpdated
self.isTrackingUpdated = isTrackingUpdated
} }
public static func ==(lhs: SliderComponent, rhs: SliderComponent) -> Bool { public static func ==(lhs: Discrete, rhs: Discrete) -> Bool {
if lhs.valueCount != rhs.valueCount { if lhs.valueCount != rhs.valueCount {
return false return false
} }
@ -49,6 +30,59 @@ public final class SliderComponent: Component {
if lhs.markPositions != rhs.markPositions { if lhs.markPositions != rhs.markPositions {
return false 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 isTrackingUpdated: ((Bool) -> Void)?
public init(
content: Content,
trackBackgroundColor: UIColor,
trackForegroundColor: UIColor,
knobSize: CGFloat? = nil,
knobColor: UIColor? = nil,
isTrackingUpdated: ((Bool) -> Void)? = nil
) {
self.content = content
self.trackBackgroundColor = trackBackgroundColor
self.trackForegroundColor = trackForegroundColor
self.knobSize = knobSize
self.knobColor = knobColor
self.isTrackingUpdated = isTrackingUpdated
}
public static func ==(lhs: SliderComponent, rhs: SliderComponent) -> Bool {
if lhs.content != rhs.content {
return false
}
if lhs.trackBackgroundColor != rhs.trackBackgroundColor { if lhs.trackBackgroundColor != rhs.trackBackgroundColor {
return false return false
} }
@ -122,10 +156,16 @@ public final class SliderComponent: Component {
sliderView.minimumValue = 0.0 sliderView.minimumValue = 0.0
sliderView.startValue = 0.0 sliderView.startValue = 0.0
sliderView.disablesInteractiveTransitionGestureRecognizer = true sliderView.disablesInteractiveTransitionGestureRecognizer = true
sliderView.maximumValue = CGFloat(component.valueCount - 1)
sliderView.positionsCount = component.valueCount switch component.content {
case let .discrete(discrete):
sliderView.maximumValue = CGFloat(discrete.valueCount - 1)
sliderView.positionsCount = discrete.valueCount
sliderView.useLinesForPositions = true sliderView.useLinesForPositions = true
sliderView.markPositions = component.markPositions sliderView.markPositions = discrete.markPositions
case .continuous:
sliderView.maximumValue = 1.0
}
sliderView.backgroundColor = nil sliderView.backgroundColor = nil
sliderView.isOpaque = false sliderView.isOpaque = false
@ -162,7 +202,12 @@ public final class SliderComponent: Component {
self.sliderView = sliderView self.sliderView = sliderView
self.addSubview(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 = { sliderView.interactionBegan = {
internalIsTrackingUpdated?(true) internalIsTrackingUpdated?(true)
} }
@ -180,7 +225,12 @@ public final class SliderComponent: Component {
guard let component = self.component, let sliderView = self.sliderView else { guard let component = self.component, let sliderView = self.sliderView else {
return 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 GiftViewScreen
import StarsIntroScreen import StarsIntroScreen
import ContentReportScreen import ContentReportScreen
import AffiliateProgramSetupScreen
private final class AccountUserInterfaceInUseContext { private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>() 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?) { 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) 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? { private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {