Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,253 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import ViewControllerComponent
import AccountContext
import SheetComponent
import AnimatedStickerComponent
import SolidRoundedButtonComponent
import MultilineTextComponent
import PresentationDataUtils
private final class AddPaymentMethodSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
private let context: AccountContext
private let action: () -> Void
private let dismiss: () -> Void
init(context: AccountContext, action: @escaping () -> Void, dismiss: @escaping () -> Void) {
self.context = context
self.action = action
self.dismiss = dismiss
}
static func ==(lhs: AddPaymentMethodSheetContent, rhs: AddPaymentMethodSheetContent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
static var body: Body {
let animation = Child(AnimatedStickerComponent.self)
let title = Child(MultilineTextComponent.self)
let text = Child(MultilineTextComponent.self)
let actionButton = Child(SolidRoundedButtonComponent.self)
let cancelButton = Child(Button.self)
return { context in
let sideInset: CGFloat = 40.0
let buttonSideInset: CGFloat = 16.0
let environment = context.environment[EnvironmentType.self].value
let action = context.component.action
let dismiss = context.component.dismiss
let animation = animation.update(
component: AnimatedStickerComponent(
account: context.component.context.account,
animation: AnimatedStickerComponent.Animation(
source: .bundle(name: "CreateStream"),
loop: true
),
size: CGSize(width: 138.0, height: 138.0)
),
availableSize: CGSize(width: 138.0, height: 138.0),
transition: context.transition
)
let title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(string: "Payment Method", font: UIFont.boldSystemFont(ofSize: 17.0), textColor: .black)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
environment: {},
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
transition: context.transition
)
let text = text.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(string: "Add your debit or credit card to buy goods and services on Telegram.", font: UIFont.systemFont(ofSize: 15.0), textColor: .gray)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
),
environment: {},
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
transition: context.transition
)
let actionButton = actionButton.update(
component: SolidRoundedButtonComponent(
title: "Add Payment Method",
theme: SolidRoundedButtonComponent.Theme(theme: environment.theme),
font: .bold,
fontSize: 17.0,
height: 50.0,
cornerRadius: 10.0,
gloss: true,
action: {
dismiss()
action()
}
),
availableSize: CGSize(width: context.availableSize.width - buttonSideInset * 2.0, height: 50.0),
transition: context.transition
)
let cancelButton = cancelButton.update(
component: Button(
content: AnyComponent(
Text(
text: "Cancel",
font: UIFont.systemFont(ofSize: 17.0),
color: environment.theme.list.itemAccentColor
)
),
action: {
dismiss()
}
).minSize(CGSize(width: context.availableSize.width - buttonSideInset * 2.0, height: 50.0)),
environment: {},
availableSize: CGSize(width: context.availableSize.width - buttonSideInset * 2.0, height: 50.0),
transition: context.transition
)
var size = CGSize(width: context.availableSize.width, height: 24.0)
context.add(animation
.position(CGPoint(x: size.width / 2.0, y: size.height + animation.size.height / 2.0))
)
size.height += animation.size.height
size.height += 16.0
context.add(title
.position(CGPoint(x: size.width / 2.0, y: size.height + title.size.height / 2.0))
)
size.height += title.size.height
size.height += 16.0
context.add(text
.position(CGPoint(x: size.width / 2.0, y: size.height + text.size.height / 2.0))
)
size.height += text.size.height
size.height += 40.0
context.add(actionButton
.position(CGPoint(x: size.width / 2.0, y: size.height + actionButton.size.height / 2.0))
)
size.height += actionButton.size.height
size.height += 8.0
context.add(cancelButton
.position(CGPoint(x: size.width / 2.0, y: size.height + cancelButton.size.height / 2.0))
)
size.height += cancelButton.size.height
size.height += 8.0 + max(environment.safeInsets.bottom, 15.0)
return size
}
}
}
private final class AddPaymentMethodSheetComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let action: () -> Void
init(context: AccountContext, action: @escaping () -> Void) {
self.context = context
self.action = action
}
static func ==(lhs: AddPaymentMethodSheetComponent, rhs: AddPaymentMethodSheetComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
static var body: Body {
let sheet = Child(SheetComponent<EnvironmentType>.self)
let animateOut = StoredActionSlot(Action<Void>.self)
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
let sheet = sheet.update(
component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(AddPaymentMethodSheetContent(
context: context.component.context,
action: context.component.action,
dismiss: {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
}
)),
backgroundColor: .color(.white),
animateOut: animateOut
),
environment: {
environment
SheetComponentEnvironment(
isDisplaying: environment.value.isVisible,
isCentered: false,
hasInputHeight: !environment.inputHeight.isZero,
regularMetricsSize: nil,
dismiss: { animated in
if animated {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
} else {
if let controller = controller() {
controller.dismiss(completion: nil)
}
}
}
)
},
availableSize: context.availableSize,
transition: context.transition
)
context.add(sheet
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
return context.availableSize
}
}
}
public final class AddPaymentMethodSheetScreen: ViewControllerComponentContainer {
public init(context: AccountContext, action: @escaping () -> Void) {
super.init(context: context, component: AddPaymentMethodSheetComponent(context: context, action: action), navigationBarAppearance: .none)
self.navigationPresentation = .flatModal
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func viewDidLoad() {
super.viewDidLoad()
self.view.disablesInteractiveModalDismiss = true
}
}
@@ -0,0 +1,447 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import ViewControllerComponent
import AccountContext
import AnimatedStickerComponent
import SolidRoundedButtonComponent
import MultilineTextComponent
import PresentationDataUtils
import PrefixSectionGroupComponent
import TextInputComponent
import CreditCardInputComponent
import Markdown
public final class ScrollChildEnvironment: Equatable {
public let insets: UIEdgeInsets
public init(insets: UIEdgeInsets) {
self.insets = insets
}
public static func ==(lhs: ScrollChildEnvironment, rhs: ScrollChildEnvironment) -> Bool {
if lhs.insets != rhs.insets {
return false
}
return true
}
}
public final class ScrollComponent<ChildEnvironment: Equatable>: Component {
public typealias EnvironmentType = ChildEnvironment
public let content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>
public let contentInsets: UIEdgeInsets
public init(
content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>,
contentInsets: UIEdgeInsets
) {
self.content = content
self.contentInsets = contentInsets
}
public static func ==(lhs: ScrollComponent, rhs: ScrollComponent) -> Bool {
if lhs.content != rhs.content {
return false
}
if lhs.contentInsets != rhs.contentInsets {
return false
}
return true
}
public final class View: UIScrollView {
private let contentView: ComponentHostView<(ChildEnvironment, ScrollChildEnvironment)>
override init(frame: CGRect) {
self.contentView = ComponentHostView()
super.init(frame: frame)
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.contentInsetAdjustmentBehavior = .never
}
self.addSubview(self.contentView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: ScrollComponent<ChildEnvironment>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ChildEnvironment>, transition: ComponentTransition) -> CGSize {
let contentSize = self.contentView.update(
transition: transition,
component: component.content,
environment: {
environment[ChildEnvironment.self]
ScrollChildEnvironment(insets: component.contentInsets)
},
containerSize: CGSize(width: availableSize.width, height: .greatestFiniteMagnitude)
)
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil)
self.contentSize = contentSize
self.scrollIndicatorInsets = component.contentInsets
return availableSize
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ChildEnvironment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private struct CardEntryModel: Equatable {
var number: String
var name: String
var expiration: String
var code: String
}
private extension CardEntryModel {
var isValid: Bool {
if self.number.count != 4 * 4 {
return false
}
if self.name.isEmpty {
return false
}
if self.expiration.isEmpty {
return false
}
if self.code.count != 3 {
return false
}
return true
}
}
private final class PaymentCardEntryScreenContentComponent: CombinedComponent {
typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment)
let context: AccountContext
let model: CardEntryModel
let updateModelKey: (WritableKeyPath<CardEntryModel, String>, String) -> Void
init(context: AccountContext, model: CardEntryModel, updateModelKey: @escaping (WritableKeyPath<CardEntryModel, String>, String) -> Void) {
self.context = context
self.model = model
self.updateModelKey = updateModelKey
}
static func ==(lhs: PaymentCardEntryScreenContentComponent, rhs: PaymentCardEntryScreenContentComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.model != rhs.model {
return false
}
return true
}
static var body: Body {
let animation = Child(AnimatedStickerComponent.self)
let text = Child(MultilineTextComponent.self)
let inputSection = Child(PrefixSectionGroupComponent.self)
let infoText = Child(MultilineTextComponent.self)
return { context in
let sideInset: CGFloat = 16.0
let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
let updateModelKey = context.component.updateModelKey
var size = CGSize(width: context.availableSize.width, height: scrollEnvironment.insets.top)
size.height += 18.0
let animation = animation.update(
component: AnimatedStickerComponent(
account: context.component.context.account,
animation: AnimatedStickerComponent.Animation(
source: .bundle(name: "CreateStream"),
loop: true
),
size: CGSize(width: 84.0, height: 84.0)
),
availableSize: CGSize(width: 84.0, height: 84.0),
transition: context.transition
)
context.add(animation
.position(CGPoint(x: size.width / 2.0, y: size.height + animation.size.height / 2.0))
)
size.height += animation.size.height
size.height += 35.0
let text = text.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(string: "Enter your card information or take a photo.", font: UIFont.systemFont(ofSize: 13.0), textColor: environment.theme.list.freeTextColor, paragraphAlignment: .center))
),
environment: {},
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 100.0),
transition: context.transition
)
context.add(text
.position(CGPoint(x: size.width / 2.0, y: size.height + text.size.height / 2.0))
)
size.height += text.size.height
size.height += 32.0
let inputSection = inputSection.update(
component: PrefixSectionGroupComponent(
items: [
PrefixSectionGroupComponent.Item(
prefix: AnyComponentWithIdentity(
id: "numberLabel",
component: AnyComponent(Text(text: "Number", font: Font.regular(17.0), color: environment.theme.list.itemPrimaryTextColor))
),
content: AnyComponentWithIdentity(
id: "numberInput",
component: AnyComponent(CreditCardInputComponent(
dataType: .cardNumber,
text: context.component.model.number,
textColor: environment.theme.list.itemPrimaryTextColor,
errorTextColor: environment.theme.list.itemDestructiveColor,
placeholder: "Card Number",
placeholderColor: environment.theme.list.itemPlaceholderTextColor,
updated: { value in
updateModelKey(\.number, value)
}
))
)
),
PrefixSectionGroupComponent.Item(
prefix: AnyComponentWithIdentity(
id: "nameLabel",
component: AnyComponent(Text(text: "Name", font: Font.regular(17.0), color: environment.theme.list.itemPrimaryTextColor))
),
content: AnyComponentWithIdentity(
id: "nameInput",
component: AnyComponent(TextInputComponent(
text: context.component.model.name,
textColor: environment.theme.list.itemPrimaryTextColor,
placeholder: "Cardholder",
placeholderColor: environment.theme.list.itemPlaceholderTextColor,
updated: { value in
updateModelKey(\.name, value)
}
))
)
),
PrefixSectionGroupComponent.Item(
prefix: AnyComponentWithIdentity(
id: "expiresLabel",
component: AnyComponent(Text(text: "Expires", font: Font.regular(17.0), color: environment.theme.list.itemPrimaryTextColor))
),
content: AnyComponentWithIdentity(
id: "expiresInput",
component: AnyComponent(CreditCardInputComponent(
dataType: .expirationDate,
text: context.component.model.expiration,
textColor: environment.theme.list.itemPrimaryTextColor,
errorTextColor: environment.theme.list.itemDestructiveColor,
placeholder: "MM/YY",
placeholderColor: environment.theme.list.itemPlaceholderTextColor,
updated: { value in
updateModelKey(\.expiration, value)
}
))
)
),
PrefixSectionGroupComponent.Item(
prefix: AnyComponentWithIdentity(
id: "cvvLabel",
component: AnyComponent(Text(text: "CVV", font: Font.regular(17.0), color: environment.theme.list.itemPrimaryTextColor))
),
content: AnyComponentWithIdentity(
id: "cvvInput",
component: AnyComponent(TextInputComponent(
text: context.component.model.code,
textColor: environment.theme.list.itemPrimaryTextColor,
placeholder: "123",
placeholderColor: environment.theme.list.itemPlaceholderTextColor,
updated: { value in
updateModelKey(\.code, value)
}
))
)
)
],
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
separatorColor: environment.theme.list.itemBlocksSeparatorColor
),
environment: {},
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
transition: context.transition
)
context.add(inputSection
.position(CGPoint(x: size.width / 2.0, y: size.height + inputSection.size.height / 2.0))
)
size.height += inputSection.size.height
size.height += 8.0
let body = MarkdownAttributeSet(font: UIFont.systemFont(ofSize: 13.0), textColor: environment.theme.list.freeTextColor)
let link = MarkdownAttributeSet(font: UIFont.systemFont(ofSize: 13.0), textColor: environment.theme.list.itemAccentColor, additionalAttributes: ["URL": true as NSNumber])
let attributes = MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in
return nil
})
let infoText = infoText.update(
component: MultilineTextComponent(
text: .markdown(text: "By adding a card, you agree to the [Terms of Service](terms).", attributes: attributes)
),
environment: {},
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 100.0),
transition: context.transition
)
context.add(infoText
.position(CGPoint(x: sideInset + sideInset + infoText.size.width / 2.0, y: size.height + infoText.size.height / 2.0))
)
size.height += text.size.height
size.height += scrollEnvironment.insets.bottom
return size
}
}
}
private final class PaymentCardEntryScreenComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let model: CardEntryModel
let updateModelKey: (WritableKeyPath<CardEntryModel, String>, String) -> Void
init(context: AccountContext, model: CardEntryModel, updateModelKey: @escaping(WritableKeyPath<CardEntryModel, String>, String) -> Void) {
self.context = context
self.model = model
self.updateModelKey = updateModelKey
}
static func ==(lhs: PaymentCardEntryScreenComponent, rhs: PaymentCardEntryScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.model != rhs.model {
return false
}
return true
}
static var body: Body {
let background = Child(Rectangle.self)
let scrollContent = Child(ScrollComponent<EnvironmentType>.self)
return { context in
let environment = context.environment[EnvironmentType.self].value
let background = background.update(component: Rectangle(color: environment.theme.list.blocksBackgroundColor), environment: {}, availableSize: context.availableSize, transition: context.transition)
let scrollContent = scrollContent.update(
component: ScrollComponent<EnvironmentType>(
content: AnyComponent(PaymentCardEntryScreenContentComponent(
context: context.component.context,
model: context.component.model,
updateModelKey: context.component.updateModelKey
)),
contentInsets: UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: environment.safeInsets.bottom, right: 0.0)
),
environment: { environment },
availableSize: context.availableSize,
transition: context.transition
)
context.add(background
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
context.add(scrollContent
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
return context.availableSize
}
}
}
public final class PaymentCardEntryScreen: ViewControllerComponentContainer {
public struct EnteredCardInfo: Equatable {
public var id: UInt64
public var number: String
public var name: String
public var expiration: String
public var code: String
}
private let context: AccountContext
private let completion: (EnteredCardInfo) -> Void
private var doneItem: UIBarButtonItem?
private var model: CardEntryModel
public init(context: AccountContext, completion: @escaping (EnteredCardInfo) -> Void) {
self.context = context
self.completion = completion
self.model = CardEntryModel(number: "", name: "", expiration: "", code: "")
var updateModelKeyImpl: ((WritableKeyPath<CardEntryModel, String>, String) -> Void)?
super.init(context: context, component: PaymentCardEntryScreenComponent(context: context, model: self.model, updateModelKey: { key, value in
updateModelKeyImpl?(key, value)
}), navigationBarAppearance: .transparent)
self.title = "Add Payment Method"
self.doneItem = UIBarButtonItem(title: "Add", style: .done, target: self, action: #selector(self.donePressed))
self.navigationItem.setRightBarButton(self.doneItem, animated: false)
self.doneItem?.isEnabled = false
self.navigationPresentation = .modal
updateModelKeyImpl = { [weak self] key, value in
self?.updateModelKey(key: key, value: value)
}
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func cancelPressed() {
self.dismiss()
}
@objc private func donePressed() {
self.dismiss(completion: nil)
self.completion(EnteredCardInfo(id: UInt64.random(in: 0 ... UInt64.max), number: self.model.number, name: self.model.name, expiration: self.model.expiration, code: self.model.code))
}
private func updateModelKey(key: WritableKeyPath<CardEntryModel, String>, value: String) {
self.model[keyPath: key] = value
self.updateComponent(component: AnyComponent(PaymentCardEntryScreenComponent(context: self.context, model: self.model, updateModelKey: { [weak self] key, value in
self?.updateModelKey(key: key, value: value)
})), transition: .immediate)
self.doneItem?.isEnabled = self.model.isValid
}
override public func viewDidLoad() {
super.viewDidLoad()
}
}
@@ -0,0 +1,286 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import AccountContext
import PresentationDataUtils
import TelegramStringFormatting
import UndoUI
import InviteLinksUI
import Stripe
private final class PaymentMethodListScreenArguments {
let context: AccountContext
let addMethod: () -> Void
let deleteMethod: (UInt64) -> Void
let selectMethod: (UInt64) -> Void
init(context: AccountContext, addMethod: @escaping () -> Void, deleteMethod: @escaping (UInt64) -> Void, selectMethod: @escaping (UInt64) -> Void) {
self.context = context
self.addMethod = addMethod
self.deleteMethod = deleteMethod
self.selectMethod = selectMethod
}
}
private enum PaymentMethodListSection: Int32 {
case header
case methods
}
private enum InviteLinksListEntry: ItemListNodeEntry {
case header(String)
case methodsHeader(String)
case addMethod(String)
case item(index: Int, info: PaymentCardEntryScreen.EnteredCardInfo, isSelected: Bool)
var section: ItemListSectionId {
switch self {
case .header:
return PaymentMethodListSection.header.rawValue
case .methodsHeader, .addMethod, .item:
return PaymentMethodListSection.methods.rawValue
}
}
var sortId: Int {
switch self {
case .header:
return 0
case .methodsHeader:
return 1
case .addMethod:
return 2
case let .item(index, _, _):
return 10 + index
}
}
var stableId: UInt64 {
switch self {
case .header:
return 0
case .methodsHeader:
return 1
case .addMethod:
return 2
case let .item(_, item, _):
return item.id
}
}
static func ==(lhs: InviteLinksListEntry, rhs: InviteLinksListEntry) -> Bool {
switch lhs {
case let .header(lhsText):
if case let .header(rhsText) = rhs, lhsText == rhsText {
return true
} else {
return false
}
case let .methodsHeader(lhsText):
if case let .methodsHeader(rhsText) = rhs, lhsText == rhsText {
return true
} else {
return false
}
case let .addMethod(lhsText):
if case let .addMethod(rhsText) = rhs, lhsText == rhsText {
return true
} else {
return false
}
case let .item(lhsIndex, lhsItem, lhsIsSelected):
if case let .item(rhsIndex, rhsItem, rhsIsSelected) = rhs, lhsIndex == rhsIndex, lhsItem == rhsItem, lhsIsSelected == rhsIsSelected {
return true
} else {
return false
}
}
}
static func <(lhs: InviteLinksListEntry, rhs: InviteLinksListEntry) -> Bool {
return lhs.sortId < rhs.sortId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! PaymentMethodListScreenArguments
switch self {
case let .header(text):
return InviteLinkHeaderItem(context: arguments.context, theme: presentationData.theme, text: NSAttributedString(string: text), animationName: "Invite", sectionId: self.section)
case let .methodsHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .addMethod(text):
let icon = PresentationResourcesItemList.plusIconImage(presentationData.theme)
return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.addMethod()
})
case let .item(_, info, isSelected):
return ItemListCheckboxItem(
presentationData: presentationData,
systemStyle: .glass,
icon: STPPaymentCardTextField.brandImage(for: .masterCard), iconSize: nil,
iconPlacement: .default,
title: "•••• " + info.number.suffix(4),
subtitle: "Expires \(info.expiration)",
style: .right,
color: .accent,
textColor: .primary,
checked: isSelected,
zeroSeparatorInsets: false,
sectionId: self.section,
action: {
arguments.selectMethod(info.id)
},
deleteAction: {
arguments.deleteMethod(info.id)
}
)
}
}
}
private func paymentMethodListScreenEntries(presentationData: PresentationData, state: PaymentMethodListScreenState) -> [InviteLinksListEntry] {
var entries: [InviteLinksListEntry] = []
entries.append(.header("Add your debit or credit card to buy goods and\nservices on Telegram."))
entries.append(.methodsHeader("PAYMENT METHOD"))
entries.append(.addMethod("Add Payment Method"))
for item in state.items {
entries.append(.item(index: entries.count, info: item, isSelected: state.selectedId == item.id))
}
return entries
}
private struct PaymentMethodListScreenState: Equatable {
var items: [PaymentCardEntryScreen.EnteredCardInfo]
var selectedId: UInt64?
}
public func paymentMethodListScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, items: [PaymentCardEntryScreen.EnteredCardInfo]) -> ViewController {
var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
let _ = presentControllerImpl
let _ = presentInGlobalOverlayImpl
var dismissTooltipsImpl: (() -> Void)?
let actionsDisposable = DisposableSet()
let initialState = PaymentMethodListScreenState(items: items, selectedId: items.first?.id)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
let updateState: ((PaymentMethodListScreenState) -> PaymentMethodListScreenState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
let _ = updateState
var getControllerImpl: (() -> ViewController?)?
let _ = getControllerImpl
let arguments = PaymentMethodListScreenArguments(
context: context,
addMethod: {
pushControllerImpl?(PaymentCardEntryScreen(context: context, completion: { result in
updateState { state in
var state = state
state.items.insert(result, at: 0)
state.selectedId = result.id
return state
}
}))
},
deleteMethod: { id in
updateState { state in
var state = state
state.items.removeAll(where: { $0.id == id })
if state.selectedId == id {
state.selectedId = state.items.first?.id
}
return state
}
},
selectMethod: { id in
updateState { state in
var state = state
state.selectedId = id
return state
}
}
)
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
let signal = combineLatest(queue: .mainQueue(),
presentationData,
statePromise.get()
)
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Payment Method"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: paymentMethodListScreenEntries(presentationData: presentationData, state: state), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: false)
return (controllerState, (listState, arguments))
}
|> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(context: context, state: signal)
controller.willDisappear = { _ in
dismissTooltipsImpl?()
}
controller.didDisappear = { [weak controller] _ in
controller?.clearItemNodesHighlight(animated: true)
}
controller.visibleBottomContentOffsetChanged = { offset in
if case let .known(value) = offset, value < 40.0 {
}
}
pushControllerImpl = { [weak controller] c in
if let controller = controller {
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
}
}
presentControllerImpl = { [weak controller] c, p in
if let controller = controller {
controller.present(c, in: .window(.root), with: p)
}
}
presentInGlobalOverlayImpl = { [weak controller] c in
if let controller = controller {
controller.presentInGlobalOverlay(c)
}
}
getControllerImpl = { [weak controller] in
return controller
}
dismissTooltipsImpl = { [weak controller] in
controller?.window?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitAction()
}
})
controller?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitAction()
}
return true
})
}
return controller
}