mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 17:30:12 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
6b7ed2e6ac
@ -252,13 +252,11 @@ public enum ResolvedUrl {
|
|||||||
case share(url: String?, text: String?, to: String?)
|
case share(url: String?, text: String?, to: String?)
|
||||||
case wallpaper(WallpaperUrlParameter)
|
case wallpaper(WallpaperUrlParameter)
|
||||||
case theme(String)
|
case theme(String)
|
||||||
#if ENABLE_WALLET
|
|
||||||
case wallet(address: String, amount: Int64?, comment: String?)
|
|
||||||
#endif
|
|
||||||
case settings(ResolvedUrlSettingsSection)
|
case settings(ResolvedUrlSettingsSection)
|
||||||
case joinVoiceChat(PeerId, String?)
|
case joinVoiceChat(PeerId, String?)
|
||||||
case importStickers
|
case importStickers
|
||||||
case startAttach(peerId: PeerId, payload: String?)
|
case startAttach(peerId: PeerId, payload: String?)
|
||||||
|
case invoice(slug: String, invoice: TelegramMediaInvoice)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum NavigateToChatKeepStack {
|
public enum NavigateToChatKeepStack {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ public final class BotCheckoutController: ViewController {
|
|||||||
self.validatedFormInfo = validatedFormInfo
|
self.validatedFormInfo = validatedFormInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func fetch(context: AccountContext, messageId: EngineMessage.Id) -> Signal<InputData, FetchError> {
|
public static func fetch(context: AccountContext, source: BotPaymentInvoiceSource) -> Signal<InputData, FetchError> {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let themeParams: [String: Any] = [
|
let themeParams: [String: Any] = [
|
||||||
"bg_color": Int32(bitPattern: presentationData.theme.list.plainBackgroundColor.argb),
|
"bg_color": Int32(bitPattern: presentationData.theme.list.plainBackgroundColor.argb),
|
||||||
@ -34,13 +34,13 @@ public final class BotCheckoutController: ViewController {
|
|||||||
"button_text_color": Int32(bitPattern: presentationData.theme.list.itemCheckColors.foregroundColor.argb)
|
"button_text_color": Int32(bitPattern: presentationData.theme.list.itemCheckColors.foregroundColor.argb)
|
||||||
]
|
]
|
||||||
|
|
||||||
return context.engine.payments.fetchBotPaymentForm(messageId: messageId, themeParams: themeParams)
|
return context.engine.payments.fetchBotPaymentForm(source: source, themeParams: themeParams)
|
||||||
|> mapError { _ -> FetchError in
|
|> mapError { _ -> FetchError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
|> mapToSignal { paymentForm -> Signal<InputData, FetchError> in
|
|> mapToSignal { paymentForm -> Signal<InputData, FetchError> in
|
||||||
if let current = paymentForm.savedInfo {
|
if let current = paymentForm.savedInfo {
|
||||||
return context.engine.payments.validateBotPaymentForm(saveInfo: true, messageId: messageId, formInfo: current)
|
return context.engine.payments.validateBotPaymentForm(saveInfo: true, source: source, formInfo: current)
|
||||||
|> mapError { _ -> FetchError in
|
|> mapError { _ -> FetchError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
@ -77,7 +77,7 @@ public final class BotCheckoutController: ViewController {
|
|||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let invoice: TelegramMediaInvoice
|
private let invoice: TelegramMediaInvoice
|
||||||
private let messageId: EngineMessage.Id
|
private let source: BotPaymentInvoiceSource
|
||||||
private let completed: (String, EngineMessage.Id?) -> Void
|
private let completed: (String, EngineMessage.Id?) -> Void
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
@ -86,10 +86,10 @@ public final class BotCheckoutController: ViewController {
|
|||||||
|
|
||||||
private let inputData: Promise<BotCheckoutController.InputData?>
|
private let inputData: Promise<BotCheckoutController.InputData?>
|
||||||
|
|
||||||
public init(context: AccountContext, invoice: TelegramMediaInvoice, messageId: EngineMessage.Id, inputData: Promise<BotCheckoutController.InputData?>, completed: @escaping (String, EngineMessage.Id?) -> Void) {
|
public init(context: AccountContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Promise<BotCheckoutController.InputData?>, completed: @escaping (String, EngineMessage.Id?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.invoice = invoice
|
self.invoice = invoice
|
||||||
self.messageId = messageId
|
self.source = source
|
||||||
self.inputData = inputData
|
self.inputData = inputData
|
||||||
self.completed = completed
|
self.completed = completed
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ public final class BotCheckoutController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
let displayNode = BotCheckoutControllerNode(controller: self, navigationBar: self.navigationBar!, context: self.context, invoice: self.invoice, messageId: self.messageId, inputData: self.inputData, present: { [weak self] c, a in
|
let displayNode = BotCheckoutControllerNode(controller: self, navigationBar: self.navigationBar!, context: self.context, invoice: self.invoice, source: self.source, inputData: self.inputData, present: { [weak self] c, a in
|
||||||
self?.present(c, in: .window(.root), with: a)
|
self?.present(c, in: .window(.root), with: a)
|
||||||
}, dismissAnimated: { [weak self] in
|
}, dismissAnimated: { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
|
|||||||
@ -493,7 +493,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
private weak var controller: BotCheckoutController?
|
private weak var controller: BotCheckoutController?
|
||||||
private let navigationBar: NavigationBar
|
private let navigationBar: NavigationBar
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let messageId: EngineMessage.Id
|
private let source: BotPaymentInvoiceSource
|
||||||
private let present: (ViewController, Any?) -> Void
|
private let present: (ViewController, Any?) -> Void
|
||||||
private let dismissAnimated: () -> Void
|
private let dismissAnimated: () -> Void
|
||||||
private let completed: (String, EngineMessage.Id?) -> Void
|
private let completed: (String, EngineMessage.Id?) -> Void
|
||||||
@ -527,11 +527,11 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
private var passwordTip: String?
|
private var passwordTip: String?
|
||||||
private var passwordTipDisposable: Disposable?
|
private var passwordTipDisposable: Disposable?
|
||||||
|
|
||||||
init(controller: BotCheckoutController?, navigationBar: NavigationBar, context: AccountContext, invoice: TelegramMediaInvoice, messageId: EngineMessage.Id, inputData: Promise<BotCheckoutController.InputData?>, present: @escaping (ViewController, Any?) -> Void, dismissAnimated: @escaping () -> Void, completed: @escaping (String, EngineMessage.Id?) -> Void) {
|
init(controller: BotCheckoutController?, navigationBar: NavigationBar, context: AccountContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Promise<BotCheckoutController.InputData?>, present: @escaping (ViewController, Any?) -> Void, dismissAnimated: @escaping () -> Void, completed: @escaping (String, EngineMessage.Id?) -> Void) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.navigationBar = navigationBar
|
self.navigationBar = navigationBar
|
||||||
self.context = context
|
self.context = context
|
||||||
self.messageId = messageId
|
self.source = source
|
||||||
self.present = present
|
self.present = present
|
||||||
self.dismissAnimated = dismissAnimated
|
self.dismissAnimated = dismissAnimated
|
||||||
self.completed = completed
|
self.completed = completed
|
||||||
@ -603,7 +603,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
openInfoImpl = { [weak self] focus in
|
openInfoImpl = { [weak self] focus in
|
||||||
if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let currentFormInfo = strongSelf.currentFormInfo {
|
if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let currentFormInfo = strongSelf.currentFormInfo {
|
||||||
strongSelf.controller?.view.endEditing(true)
|
strongSelf.controller?.view.endEditing(true)
|
||||||
strongSelf.present(BotCheckoutInfoController(context: context, invoice: paymentFormValue.invoice, messageId: messageId, initialFormInfo: currentFormInfo, focus: focus, formInfoUpdated: { formInfo, validatedInfo in
|
strongSelf.present(BotCheckoutInfoController(context: context, invoice: paymentFormValue.invoice, source: source, initialFormInfo: currentFormInfo, focus: focus, formInfoUpdated: { formInfo, validatedInfo in
|
||||||
if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue {
|
if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue {
|
||||||
strongSelf.currentFormInfo = formInfo
|
strongSelf.currentFormInfo = formInfo
|
||||||
strongSelf.currentValidatedFormInfo = validatedInfo
|
strongSelf.currentValidatedFormInfo = validatedInfo
|
||||||
@ -1125,7 +1125,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
countryCode = paramsCountryCode
|
countryCode = paramsCountryCode
|
||||||
}
|
}
|
||||||
|
|
||||||
let botPeerId = self.messageId.peerId
|
let botPeerId = paymentForm.paymentBotId
|
||||||
let _ = (context.engine.data.get(
|
let _ = (context.engine.data.get(
|
||||||
TelegramEngine.EngineData.Item.Peer.Peer(id: botPeerId)
|
TelegramEngine.EngineData.Item.Peer.Peer(id: botPeerId)
|
||||||
)
|
)
|
||||||
@ -1239,7 +1239,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
|||||||
let totalAmount = currentTotalPrice(paymentForm: paymentForm, validatedFormInfo: self.currentValidatedFormInfo, currentShippingOptionId: self.currentShippingOptionId, currentTip: self.currentTipAmount)
|
let totalAmount = currentTotalPrice(paymentForm: paymentForm, validatedFormInfo: self.currentValidatedFormInfo, currentShippingOptionId: self.currentShippingOptionId, currentTip: self.currentTipAmount)
|
||||||
let currencyValue = formatCurrencyAmount(totalAmount, currency: paymentForm.invoice.currency)
|
let currencyValue = formatCurrencyAmount(totalAmount, currency: paymentForm.invoice.currency)
|
||||||
|
|
||||||
self.payDisposable.set((self.context.engine.payments.sendBotPaymentForm(messageId: self.messageId, formId: paymentForm.id, validatedInfoId: self.currentValidatedFormInfo?.id, shippingOptionId: self.currentShippingOptionId, tipAmount: tipAmount, credentials: credentials) |> deliverOnMainQueue).start(next: { [weak self] result in
|
self.payDisposable.set((self.context.engine.payments.sendBotPaymentForm(source: self.source, formId: paymentForm.id, validatedInfoId: self.currentValidatedFormInfo?.id, shippingOptionId: self.currentShippingOptionId, tipAmount: tipAmount, credentials: credentials) |> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.inProgressDimNode.isUserInteractionEnabled = false
|
strongSelf.inProgressDimNode.isUserInteractionEnabled = false
|
||||||
strongSelf.inProgressDimNode.alpha = 0.0
|
strongSelf.inProgressDimNode.alpha = 0.0
|
||||||
|
|||||||
@ -30,7 +30,7 @@ final class BotCheckoutInfoController: ViewController {
|
|||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let invoice: BotPaymentInvoice
|
private let invoice: BotPaymentInvoice
|
||||||
private let messageId: EngineMessage.Id
|
private let source: BotPaymentInvoiceSource
|
||||||
private let initialFormInfo: BotPaymentRequestedInfo
|
private let initialFormInfo: BotPaymentRequestedInfo
|
||||||
private let focus: BotCheckoutInfoControllerFocus
|
private let focus: BotCheckoutInfoControllerFocus
|
||||||
|
|
||||||
@ -46,14 +46,14 @@ final class BotCheckoutInfoController: ViewController {
|
|||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
invoice: BotPaymentInvoice,
|
invoice: BotPaymentInvoice,
|
||||||
messageId: EngineMessage.Id,
|
source: BotPaymentInvoiceSource,
|
||||||
initialFormInfo: BotPaymentRequestedInfo,
|
initialFormInfo: BotPaymentRequestedInfo,
|
||||||
focus: BotCheckoutInfoControllerFocus,
|
focus: BotCheckoutInfoControllerFocus,
|
||||||
formInfoUpdated: @escaping (BotPaymentRequestedInfo, BotPaymentValidatedFormInfo) -> Void
|
formInfoUpdated: @escaping (BotPaymentRequestedInfo, BotPaymentValidatedFormInfo) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.invoice = invoice
|
self.invoice = invoice
|
||||||
self.messageId = messageId
|
self.source = source
|
||||||
self.initialFormInfo = initialFormInfo
|
self.initialFormInfo = initialFormInfo
|
||||||
self.focus = focus
|
self.focus = focus
|
||||||
self.formInfoUpdated = formInfoUpdated
|
self.formInfoUpdated = formInfoUpdated
|
||||||
@ -80,7 +80,7 @@ final class BotCheckoutInfoController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = BotCheckoutInfoControllerNode(context: self.context, navigationBar: self.navigationBar, invoice: self.invoice, messageId: self.messageId, formInfo: self.initialFormInfo, focus: self.focus, theme: self.presentationData.theme, strings: self.presentationData.strings, dismiss: { [weak self] in
|
self.displayNode = BotCheckoutInfoControllerNode(context: self.context, navigationBar: self.navigationBar, invoice: self.invoice, source: self.source, formInfo: self.initialFormInfo, focus: self.focus, theme: self.presentationData.theme, strings: self.presentationData.strings, dismiss: { [weak self] in
|
||||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
}, openCountrySelection: { [weak self] in
|
}, openCountrySelection: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
|||||||
@ -96,7 +96,7 @@ final class BotCheckoutInfoControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private weak var navigationBar: NavigationBar?
|
private weak var navigationBar: NavigationBar?
|
||||||
private let invoice: BotPaymentInvoice
|
private let invoice: BotPaymentInvoice
|
||||||
private let messageId: EngineMessage.Id
|
private let source: BotPaymentInvoiceSource
|
||||||
private var focus: BotCheckoutInfoControllerFocus?
|
private var focus: BotCheckoutInfoControllerFocus?
|
||||||
|
|
||||||
private let dismiss: () -> Void
|
private let dismiss: () -> Void
|
||||||
@ -130,7 +130,7 @@ final class BotCheckoutInfoControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
navigationBar: NavigationBar?,
|
navigationBar: NavigationBar?,
|
||||||
invoice: BotPaymentInvoice,
|
invoice: BotPaymentInvoice,
|
||||||
messageId: EngineMessage.Id,
|
source: BotPaymentInvoiceSource,
|
||||||
formInfo: BotPaymentRequestedInfo,
|
formInfo: BotPaymentRequestedInfo,
|
||||||
focus: BotCheckoutInfoControllerFocus,
|
focus: BotCheckoutInfoControllerFocus,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
@ -144,7 +144,7 @@ final class BotCheckoutInfoControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.navigationBar = navigationBar
|
self.navigationBar = navigationBar
|
||||||
self.invoice = invoice
|
self.invoice = invoice
|
||||||
self.messageId = messageId
|
self.source = source
|
||||||
self.formInfo = formInfo
|
self.formInfo = formInfo
|
||||||
self.focus = focus
|
self.focus = focus
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
@ -367,7 +367,7 @@ final class BotCheckoutInfoControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
func verify() {
|
func verify() {
|
||||||
self.isVerifying = true
|
self.isVerifying = true
|
||||||
let formInfo = self.collectFormInfo()
|
let formInfo = self.collectFormInfo()
|
||||||
self.verifyDisposable.set((self.context.engine.payments.validateBotPaymentForm(saveInfo: self.saveInfoItem.isOn, messageId: self.messageId, formInfo: formInfo) |> deliverOnMainQueue).start(next: { [weak self] result in
|
self.verifyDisposable.set((self.context.engine.payments.validateBotPaymentForm(saveInfo: self.saveInfoItem.isOn, source: self.source, formInfo: formInfo) |> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.formInfoUpdated(formInfo, result)
|
strongSelf.formInfoUpdated(formInfo, result)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,8 +104,9 @@ public final class _ConcreteChildComponent<ComponentType: Component>: _AnyChildC
|
|||||||
|
|
||||||
let context = view.context(component: component)
|
let context = view.context(component: component)
|
||||||
EnvironmentBuilder._environment = context.erasedEnvironment
|
EnvironmentBuilder._environment = context.erasedEnvironment
|
||||||
let _ = environment()
|
let environmentResult = environment()
|
||||||
EnvironmentBuilder._environment = nil
|
EnvironmentBuilder._environment = nil
|
||||||
|
context.erasedEnvironment = environmentResult
|
||||||
|
|
||||||
return updateChildAnyComponent(
|
return updateChildAnyComponent(
|
||||||
id: self.id,
|
id: self.id,
|
||||||
@ -288,9 +289,11 @@ public final class _EnvironmentChildComponent<EnvironmentType>: _AnyChildCompone
|
|||||||
transition = .immediate
|
transition = .immediate
|
||||||
}
|
}
|
||||||
|
|
||||||
EnvironmentBuilder._environment = view.context(component: component).erasedEnvironment
|
let viewContext = view.context(component: component)
|
||||||
let _ = environment()
|
EnvironmentBuilder._environment = viewContext.erasedEnvironment
|
||||||
|
let environmentResult = environment()
|
||||||
EnvironmentBuilder._environment = nil
|
EnvironmentBuilder._environment = nil
|
||||||
|
viewContext.erasedEnvironment = environmentResult
|
||||||
|
|
||||||
return updateChildAnyComponent(
|
return updateChildAnyComponent(
|
||||||
id: self.id,
|
id: self.id,
|
||||||
@ -342,9 +345,11 @@ public final class _EnvironmentChildComponentFromMap<EnvironmentType>: _AnyChild
|
|||||||
transition = .immediate
|
transition = .immediate
|
||||||
}
|
}
|
||||||
|
|
||||||
EnvironmentBuilder._environment = view.context(component: component).erasedEnvironment
|
let viewContext = view.context(component: component)
|
||||||
let _ = environment()
|
EnvironmentBuilder._environment = viewContext.erasedEnvironment
|
||||||
|
let environmentResult = environment()
|
||||||
EnvironmentBuilder._environment = nil
|
EnvironmentBuilder._environment = nil
|
||||||
|
viewContext.erasedEnvironment = environmentResult
|
||||||
|
|
||||||
return updateChildAnyComponent(
|
return updateChildAnyComponent(
|
||||||
id: self.id,
|
id: self.id,
|
||||||
|
|||||||
@ -26,7 +26,11 @@ class AnyComponentContext<EnvironmentType>: _TypeErasedComponentContext {
|
|||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
}
|
}
|
||||||
var erasedEnvironment: _Environment {
|
var erasedEnvironment: _Environment {
|
||||||
|
get {
|
||||||
return self.environment
|
return self.environment
|
||||||
|
} set(value) {
|
||||||
|
self.environment = value as! Environment<EnvironmentType>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let layoutResult: ComponentLayoutResult
|
let layoutResult: ComponentLayoutResult
|
||||||
|
|||||||
@ -23,13 +23,13 @@ public final class Button: Component {
|
|||||||
|
|
||||||
private init(
|
private init(
|
||||||
content: AnyComponent<Empty>,
|
content: AnyComponent<Empty>,
|
||||||
minSize: CGSize?,
|
minSize: CGSize? = nil,
|
||||||
tag: AnyObject? = nil,
|
tag: AnyObject? = nil,
|
||||||
automaticHighlight: Bool = true,
|
automaticHighlight: Bool = true,
|
||||||
action: @escaping () -> Void
|
action: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.content = content
|
self.content = content
|
||||||
self.minSize = nil
|
self.minSize = minSize
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.automaticHighlight = automaticHighlight
|
self.automaticHighlight = automaticHighlight
|
||||||
self.action = action
|
self.action = action
|
||||||
|
|||||||
@ -17,7 +17,7 @@ private func findTaggedViewImpl(view: UIView, tag: Any) -> UIView? {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ComponentHostView<EnvironmentType: Equatable>: UIView {
|
public final class ComponentHostView<EnvironmentType>: UIView {
|
||||||
private var currentComponent: AnyComponent<EnvironmentType>?
|
private var currentComponent: AnyComponent<EnvironmentType>?
|
||||||
private var currentContainerSize: CGSize?
|
private var currentContainerSize: CGSize?
|
||||||
private var currentSize: CGSize?
|
private var currentSize: CGSize?
|
||||||
@ -43,9 +43,7 @@ public final class ComponentHostView<EnvironmentType: Equatable>: UIView {
|
|||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
|
|
||||||
precondition(containerSize.width.isFinite)
|
precondition(containerSize.width.isFinite)
|
||||||
precondition(containerSize.width < .greatestFiniteMagnitude)
|
|
||||||
precondition(containerSize.height.isFinite)
|
precondition(containerSize.height.isFinite)
|
||||||
precondition(containerSize.height < .greatestFiniteMagnitude)
|
|
||||||
|
|
||||||
let componentView: UIView
|
let componentView: UIView
|
||||||
if let current = self.componentView {
|
if let current = self.componentView {
|
||||||
@ -62,8 +60,9 @@ public final class ComponentHostView<EnvironmentType: Equatable>: UIView {
|
|||||||
|
|
||||||
if updateEnvironment {
|
if updateEnvironment {
|
||||||
EnvironmentBuilder._environment = context.erasedEnvironment
|
EnvironmentBuilder._environment = context.erasedEnvironment
|
||||||
let _ = maybeEnvironment()
|
let environmentResult = maybeEnvironment()
|
||||||
EnvironmentBuilder._environment = nil
|
EnvironmentBuilder._environment = nil
|
||||||
|
context.erasedEnvironment = environmentResult
|
||||||
}
|
}
|
||||||
|
|
||||||
let isEnvironmentUpdated = context.erasedEnvironment.calculateIsUpdated()
|
let isEnvironmentUpdated = context.erasedEnvironment.calculateIsUpdated()
|
||||||
|
|||||||
20
submodules/Components/Forms/CreditCardInputComponent/BUILD
Normal file
20
submodules/Components/Forms/CreditCardInputComponent/BUILD
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "CreditCardInputComponent",
|
||||||
|
module_name = "CreditCardInputComponent",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display:Display",
|
||||||
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
|
"//submodules/Stripe:Stripe",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -0,0 +1,172 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import ComponentFlow
|
||||||
|
import Display
|
||||||
|
import Stripe
|
||||||
|
|
||||||
|
public final class CreditCardInputComponent: Component {
|
||||||
|
public enum DataType {
|
||||||
|
case cardNumber
|
||||||
|
case expirationDate
|
||||||
|
}
|
||||||
|
|
||||||
|
public let dataType: DataType
|
||||||
|
public let text: String
|
||||||
|
public let textColor: UIColor
|
||||||
|
public let errorTextColor: UIColor
|
||||||
|
public let placeholder: String
|
||||||
|
public let placeholderColor: UIColor
|
||||||
|
public let updated: (String) -> Void
|
||||||
|
|
||||||
|
public init(
|
||||||
|
dataType: DataType,
|
||||||
|
text: String,
|
||||||
|
textColor: UIColor,
|
||||||
|
errorTextColor: UIColor,
|
||||||
|
placeholder: String,
|
||||||
|
placeholderColor: UIColor,
|
||||||
|
updated: @escaping (String) -> Void
|
||||||
|
) {
|
||||||
|
self.dataType = dataType
|
||||||
|
self.text = text
|
||||||
|
self.textColor = textColor
|
||||||
|
self.errorTextColor = errorTextColor
|
||||||
|
self.placeholder = placeholder
|
||||||
|
self.placeholderColor = placeholderColor
|
||||||
|
self.updated = updated
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: CreditCardInputComponent, rhs: CreditCardInputComponent) -> Bool {
|
||||||
|
if lhs.dataType != rhs.dataType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.text != rhs.text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.textColor != rhs.textColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.errorTextColor != rhs.errorTextColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.placeholder != rhs.placeholder {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.placeholderColor != rhs.placeholderColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView, STPFormTextFieldDelegate, UITextFieldDelegate {
|
||||||
|
private let textField: STPFormTextField
|
||||||
|
|
||||||
|
private var component: CreditCardInputComponent?
|
||||||
|
private let viewModel: STPPaymentCardTextFieldViewModel
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.textField = STPFormTextField(frame: CGRect())
|
||||||
|
|
||||||
|
self.viewModel = STPPaymentCardTextFieldViewModel()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.textField.backgroundColor = .clear
|
||||||
|
self.textField.keyboardType = .phonePad
|
||||||
|
|
||||||
|
self.textField.formDelegate = self
|
||||||
|
self.textField.validText = true
|
||||||
|
|
||||||
|
self.addSubview(self.textField)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func textFieldChanged(_ textField: UITextField) {
|
||||||
|
self.component?.updated(self.textField.text ?? "")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func formTextFieldDidBackspace(onEmpty formTextField: STPFormTextField) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func formTextField(_ formTextField: STPFormTextField, modifyIncomingTextChange input: NSAttributedString) -> NSAttributedString {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
switch component.dataType {
|
||||||
|
case .cardNumber:
|
||||||
|
self.viewModel.cardNumber = input.string
|
||||||
|
return NSAttributedString(string: self.viewModel.cardNumber ?? "", attributes: self.textField.defaultTextAttributes)
|
||||||
|
case .expirationDate:
|
||||||
|
self.viewModel.rawExpiration = input.string
|
||||||
|
return NSAttributedString(string: self.viewModel.rawExpiration ?? "", attributes: self.textField.defaultTextAttributes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func formTextFieldTextDidChange(_ textField: STPFormTextField) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
component.updated(self.textField.text ?? "")
|
||||||
|
|
||||||
|
let state: STPCardValidationState
|
||||||
|
switch component.dataType {
|
||||||
|
case .cardNumber:
|
||||||
|
state = self.viewModel.validationState(for: .number)
|
||||||
|
case .expirationDate:
|
||||||
|
state = self.viewModel.validationState(for: .expiration)
|
||||||
|
}
|
||||||
|
self.textField.validText = true
|
||||||
|
switch state {
|
||||||
|
case .invalid:
|
||||||
|
self.textField.validText = false
|
||||||
|
case .incomplete:
|
||||||
|
break
|
||||||
|
case .valid:
|
||||||
|
break
|
||||||
|
@unknown default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: CreditCardInputComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
switch component.dataType {
|
||||||
|
case .cardNumber:
|
||||||
|
self.textField.autoFormattingBehavior = .cardNumbers
|
||||||
|
case .expirationDate:
|
||||||
|
self.textField.autoFormattingBehavior = .expiration
|
||||||
|
}
|
||||||
|
|
||||||
|
self.textField.font = UIFont.systemFont(ofSize: 17.0)
|
||||||
|
self.textField.defaultColor = component.textColor
|
||||||
|
self.textField.errorColor = .red
|
||||||
|
self.textField.placeholderColor = component.placeholderColor
|
||||||
|
|
||||||
|
if self.textField.text != component.text {
|
||||||
|
self.textField.text = component.text
|
||||||
|
}
|
||||||
|
|
||||||
|
self.textField.attributedPlaceholder = NSAttributedString(string: component.placeholder, font: self.textField.font, textColor: component.placeholderColor)
|
||||||
|
|
||||||
|
let size = CGSize(width: availableSize.width, height: 44.0)
|
||||||
|
|
||||||
|
transition.setFrame(view: self.textField, frame: CGRect(origin: CGPoint(), size: size), completion: nil)
|
||||||
|
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "PrefixSectionGroupComponent",
|
||||||
|
module_name = "PrefixSectionGroupComponent",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display:Display",
|
||||||
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -0,0 +1,194 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import ComponentFlow
|
||||||
|
import Display
|
||||||
|
|
||||||
|
public final class PrefixSectionGroupComponent: Component {
|
||||||
|
public final class Item: Equatable {
|
||||||
|
public let prefix: AnyComponentWithIdentity<Empty>
|
||||||
|
public let content: AnyComponentWithIdentity<Empty>
|
||||||
|
|
||||||
|
public init(prefix: AnyComponentWithIdentity<Empty>, content: AnyComponentWithIdentity<Empty>) {
|
||||||
|
self.prefix = prefix
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||||
|
if lhs.prefix != rhs.prefix {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.content != rhs.content {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let items: [Item]
|
||||||
|
public let backgroundColor: UIColor
|
||||||
|
public let separatorColor: UIColor
|
||||||
|
|
||||||
|
public init(
|
||||||
|
items: [Item],
|
||||||
|
backgroundColor: UIColor,
|
||||||
|
separatorColor: UIColor
|
||||||
|
) {
|
||||||
|
self.items = items
|
||||||
|
self.backgroundColor = backgroundColor
|
||||||
|
self.separatorColor = separatorColor
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: PrefixSectionGroupComponent, rhs: PrefixSectionGroupComponent) -> Bool {
|
||||||
|
if lhs.items != rhs.items {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.backgroundColor != rhs.backgroundColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.separatorColor != rhs.separatorColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView {
|
||||||
|
private let backgroundView: UIView
|
||||||
|
private var itemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||||
|
private var separatorViews: [UIView] = []
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.backgroundView = UIView()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.backgroundView)
|
||||||
|
self.backgroundView.layer.cornerRadius = 10.0
|
||||||
|
self.backgroundView.layer.masksToBounds = true
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: PrefixSectionGroupComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
let spacing: CGFloat = 16.0
|
||||||
|
let sideInset: CGFloat = 16.0
|
||||||
|
|
||||||
|
self.backgroundView.backgroundColor = component.backgroundColor
|
||||||
|
|
||||||
|
var size = CGSize(width: availableSize.width, height: 0.0)
|
||||||
|
|
||||||
|
var validIds: [AnyHashable] = []
|
||||||
|
|
||||||
|
var maxPrefixSize = CGSize()
|
||||||
|
var prefixItemSizes: [CGSize] = []
|
||||||
|
for item in component.items {
|
||||||
|
validIds.append(item.prefix.id)
|
||||||
|
|
||||||
|
let itemView: ComponentHostView<Empty>
|
||||||
|
var itemTransition = transition
|
||||||
|
if let current = self.itemViews[item.prefix.id] {
|
||||||
|
itemView = current
|
||||||
|
} else {
|
||||||
|
itemTransition = transition.withAnimation(.none)
|
||||||
|
itemView = ComponentHostView<Empty>()
|
||||||
|
self.itemViews[item.prefix.id] = itemView
|
||||||
|
self.addSubview(itemView)
|
||||||
|
}
|
||||||
|
let itemSize = itemView.update(
|
||||||
|
transition: itemTransition,
|
||||||
|
component: item.prefix.component,
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: size.width, height: .greatestFiniteMagnitude)
|
||||||
|
)
|
||||||
|
prefixItemSizes.append(itemSize)
|
||||||
|
maxPrefixSize.width = max(maxPrefixSize.width, itemSize.width)
|
||||||
|
maxPrefixSize.height = max(maxPrefixSize.height, itemSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxContentSize = CGSize()
|
||||||
|
var contentItemSizes: [CGSize] = []
|
||||||
|
for item in component.items {
|
||||||
|
validIds.append(item.content.id)
|
||||||
|
|
||||||
|
let itemView: ComponentHostView<Empty>
|
||||||
|
var itemTransition = transition
|
||||||
|
if let current = self.itemViews[item.content.id] {
|
||||||
|
itemView = current
|
||||||
|
} else {
|
||||||
|
itemTransition = transition.withAnimation(.none)
|
||||||
|
itemView = ComponentHostView<Empty>()
|
||||||
|
self.itemViews[item.content.id] = itemView
|
||||||
|
self.addSubview(itemView)
|
||||||
|
}
|
||||||
|
let itemSize = itemView.update(
|
||||||
|
transition: itemTransition,
|
||||||
|
component: item.content.component,
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: size.width - maxPrefixSize.width - sideInset - spacing, height: .greatestFiniteMagnitude)
|
||||||
|
)
|
||||||
|
contentItemSizes.append(itemSize)
|
||||||
|
maxContentSize.width = max(maxContentSize.width, itemSize.width)
|
||||||
|
maxContentSize.height = max(maxContentSize.height, itemSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0 ..< component.items.count {
|
||||||
|
let itemSize = CGSize(width: size.width, height: max(prefixItemSizes[i].height, contentItemSizes[i].height))
|
||||||
|
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: itemSize)
|
||||||
|
|
||||||
|
let prefixView = itemViews[component.items[i].prefix.id]!
|
||||||
|
let contentView = itemViews[component.items[i].content.id]!
|
||||||
|
|
||||||
|
prefixView.frame = CGRect(origin: CGPoint(x: sideInset, y: itemFrame.minY + floor((itemFrame.height - prefixItemSizes[i].height) / 2.0)), size: prefixItemSizes[i])
|
||||||
|
|
||||||
|
contentView.frame = CGRect(origin: CGPoint(x: itemFrame.minX + sideInset + maxPrefixSize.width + spacing, y: itemFrame.minY + floor((itemFrame.height - contentItemSizes[i].height) / 2.0)), size: contentItemSizes[i])
|
||||||
|
|
||||||
|
size.height += itemSize.height
|
||||||
|
|
||||||
|
if i != component.items.count - 1 {
|
||||||
|
let separatorView: UIView
|
||||||
|
if self.separatorViews.count > i {
|
||||||
|
separatorView = self.separatorViews[i]
|
||||||
|
} else {
|
||||||
|
separatorView = UIView()
|
||||||
|
self.separatorViews.append(separatorView)
|
||||||
|
self.addSubview(separatorView)
|
||||||
|
}
|
||||||
|
separatorView.backgroundColor = component.separatorColor
|
||||||
|
separatorView.frame = CGRect(origin: CGPoint(x: itemFrame.minX + sideInset, y: itemFrame.maxY), size: CGSize(width: itemFrame.width - sideInset, height: UIScreenPixel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeIds: [AnyHashable] = []
|
||||||
|
for (id, itemView) in self.itemViews {
|
||||||
|
if !validIds.contains(id) {
|
||||||
|
removeIds.append(id)
|
||||||
|
itemView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in removeIds {
|
||||||
|
self.itemViews.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.separatorViews.count > component.items.count - 1 {
|
||||||
|
for i in (component.items.count - 1) ..< self.separatorViews.count {
|
||||||
|
self.separatorViews[i].removeFromSuperview()
|
||||||
|
}
|
||||||
|
self.separatorViews.removeSubrange((component.items.count - 1) ..< self.separatorViews.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size), completion: nil)
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
19
submodules/Components/Forms/TextInputComponent/BUILD
Normal file
19
submodules/Components/Forms/TextInputComponent/BUILD
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "TextInputComponent",
|
||||||
|
module_name = "TextInputComponent",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display:Display",
|
||||||
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import ComponentFlow
|
||||||
|
import Display
|
||||||
|
|
||||||
|
public final class TextInputComponent: Component {
|
||||||
|
public let text: String
|
||||||
|
public let textColor: UIColor
|
||||||
|
public let placeholder: String
|
||||||
|
public let placeholderColor: UIColor
|
||||||
|
public let updated: (String) -> Void
|
||||||
|
|
||||||
|
public init(
|
||||||
|
text: String,
|
||||||
|
textColor: UIColor,
|
||||||
|
placeholder: String,
|
||||||
|
placeholderColor: UIColor,
|
||||||
|
updated: @escaping (String) -> Void
|
||||||
|
) {
|
||||||
|
self.text = text
|
||||||
|
self.textColor = textColor
|
||||||
|
self.placeholder = placeholder
|
||||||
|
self.placeholderColor = placeholderColor
|
||||||
|
self.updated = updated
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: TextInputComponent, rhs: TextInputComponent) -> Bool {
|
||||||
|
if lhs.text != rhs.text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.textColor != rhs.textColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.placeholder != rhs.placeholder {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.placeholderColor != rhs.placeholderColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UITextField, UITextFieldDelegate {
|
||||||
|
private var component: TextInputComponent?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.delegate = self
|
||||||
|
self.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func textFieldChanged(_ textField: UITextField) {
|
||||||
|
self.component?.updated(self.text ?? "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: TextInputComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
self.font = UIFont.systemFont(ofSize: 17.0)
|
||||||
|
self.textColor = component.textColor
|
||||||
|
|
||||||
|
if self.text != component.text {
|
||||||
|
self.text = component.text
|
||||||
|
}
|
||||||
|
|
||||||
|
self.attributedPlaceholder = NSAttributedString(string: component.placeholder, font: self.font, textColor: component.placeholderColor)
|
||||||
|
|
||||||
|
let size = CGSize(width: availableSize.width, height: 44.0)
|
||||||
|
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,9 +12,7 @@ swift_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//submodules/Display:Display",
|
"//submodules/Display:Display",
|
||||||
"//submodules/ComponentFlow:ComponentFlow",
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
"//submodules/Markdown:Markdown",
|
||||||
"//submodules/AccountContext:AccountContext",
|
|
||||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -2,21 +2,27 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import Display
|
import Display
|
||||||
|
import Markdown
|
||||||
|
|
||||||
public final class MultilineTextComponent: Component {
|
public final class MultilineTextComponent: Component {
|
||||||
public let text: NSAttributedString
|
public enum TextContent: Equatable {
|
||||||
|
case plain(NSAttributedString)
|
||||||
|
case markdown(text: String, attributes: MarkdownAttributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
public let text: TextContent
|
||||||
public let horizontalAlignment: NSTextAlignment
|
public let horizontalAlignment: NSTextAlignment
|
||||||
public let verticalAlignment: TextVerticalAlignment
|
public let verticalAlignment: TextVerticalAlignment
|
||||||
public var truncationType: CTLineTruncationType
|
public let truncationType: CTLineTruncationType
|
||||||
public var maximumNumberOfLines: Int
|
public let maximumNumberOfLines: Int
|
||||||
public var lineSpacing: CGFloat
|
public let lineSpacing: CGFloat
|
||||||
public var cutout: TextNodeCutout?
|
public let cutout: TextNodeCutout?
|
||||||
public var insets: UIEdgeInsets
|
public let insets: UIEdgeInsets
|
||||||
public var textShadowColor: UIColor?
|
public let textShadowColor: UIColor?
|
||||||
public var textStroke: (UIColor, CGFloat)?
|
public let textStroke: (UIColor, CGFloat)?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
text: NSAttributedString,
|
text: TextContent,
|
||||||
horizontalAlignment: NSTextAlignment = .natural,
|
horizontalAlignment: NSTextAlignment = .natural,
|
||||||
verticalAlignment: TextVerticalAlignment = .top,
|
verticalAlignment: TextVerticalAlignment = .top,
|
||||||
truncationType: CTLineTruncationType = .end,
|
truncationType: CTLineTruncationType = .end,
|
||||||
@ -40,7 +46,7 @@ public final class MultilineTextComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: MultilineTextComponent, rhs: MultilineTextComponent) -> Bool {
|
public static func ==(lhs: MultilineTextComponent, rhs: MultilineTextComponent) -> Bool {
|
||||||
if !lhs.text.isEqual(to: rhs.text) {
|
if lhs.text != rhs.text {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.horizontalAlignment != rhs.horizontalAlignment {
|
if lhs.horizontalAlignment != rhs.horizontalAlignment {
|
||||||
@ -89,9 +95,17 @@ public final class MultilineTextComponent: Component {
|
|||||||
|
|
||||||
public final class View: TextView {
|
public final class View: TextView {
|
||||||
public func update(component: MultilineTextComponent, availableSize: CGSize) -> CGSize {
|
public func update(component: MultilineTextComponent, availableSize: CGSize) -> CGSize {
|
||||||
|
let attributedString: NSAttributedString
|
||||||
|
switch component.text {
|
||||||
|
case let .plain(string):
|
||||||
|
attributedString = string
|
||||||
|
case let .markdown(text, attributes):
|
||||||
|
attributedString = parseMarkdownIntoAttributedString(text, attributes: attributes)
|
||||||
|
}
|
||||||
|
|
||||||
let makeLayout = TextView.asyncLayout(self)
|
let makeLayout = TextView.asyncLayout(self)
|
||||||
let (layout, apply) = makeLayout(TextNodeLayoutArguments(
|
let (layout, apply) = makeLayout(TextNodeLayoutArguments(
|
||||||
attributedString: component.text,
|
attributedString: attributedString,
|
||||||
backgroundColor: nil,
|
backgroundColor: nil,
|
||||||
maximumNumberOfLines: component.maximumNumberOfLines,
|
maximumNumberOfLines: component.maximumNumberOfLines,
|
||||||
truncationType: component.truncationType,
|
truncationType: component.truncationType,
|
||||||
|
|||||||
@ -120,11 +120,14 @@ open class ViewControllerComponentContainer: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class AnimateInTransition {
|
||||||
|
}
|
||||||
|
|
||||||
public final class Node: ViewControllerTracingNode {
|
public final class Node: ViewControllerTracingNode {
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private weak var controller: ViewControllerComponentContainer?
|
private weak var controller: ViewControllerComponentContainer?
|
||||||
|
|
||||||
private let component: AnyComponent<ViewControllerComponentContainer.Environment>
|
private var component: AnyComponent<ViewControllerComponentContainer.Environment>
|
||||||
private let theme: PresentationTheme?
|
private let theme: PresentationTheme?
|
||||||
public let hostView: ComponentHostView<ViewControllerComponentContainer.Environment>
|
public let hostView: ComponentHostView<ViewControllerComponentContainer.Environment>
|
||||||
|
|
||||||
@ -171,7 +174,7 @@ open class ViewControllerComponentContainer: ViewController {
|
|||||||
transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: layout.size), completion: nil)
|
transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: layout.size), completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateIsVisible(isVisible: Bool) {
|
func updateIsVisible(isVisible: Bool, animated: Bool) {
|
||||||
if self.currentIsVisible == isVisible {
|
if self.currentIsVisible == isVisible {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -180,7 +183,16 @@ open class ViewControllerComponentContainer: ViewController {
|
|||||||
guard let currentLayout = self.currentLayout else {
|
guard let currentLayout = self.currentLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: .immediate)
|
self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: animated ? Transition(animation: .none).withUserData(AnimateInTransition()) : .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateComponent(component: AnyComponent<ViewControllerComponentContainer.Environment>, transition: Transition) {
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
guard let currentLayout = self.currentLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,13 +234,13 @@ open class ViewControllerComponentContainer: ViewController {
|
|||||||
override open func viewDidAppear(_ animated: Bool) {
|
override open func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
self.node.updateIsVisible(isVisible: true)
|
self.node.updateIsVisible(isVisible: true, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override open func viewDidDisappear(_ animated: Bool) {
|
override open func viewDidDisappear(_ animated: Bool) {
|
||||||
super.viewDidDisappear(animated)
|
super.viewDidDisappear(animated)
|
||||||
|
|
||||||
self.node.updateIsVisible(isVisible: false)
|
self.node.updateIsVisible(isVisible: false, animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
@ -238,4 +250,8 @@ open class ViewControllerComponentContainer: ViewController {
|
|||||||
|
|
||||||
self.node.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition))
|
self.node.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func updateComponent(component: AnyComponent<ViewControllerComponentContainer.Environment>, transition: Transition) {
|
||||||
|
self.node.updateComponent(component: component, transition: transition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,15 +12,15 @@ import AccountContext
|
|||||||
import Markdown
|
import Markdown
|
||||||
import TextFormat
|
import TextFormat
|
||||||
|
|
||||||
class InviteLinkHeaderItem: ListViewItem, ItemListItem {
|
public class InviteLinkHeaderItem: ListViewItem, ItemListItem {
|
||||||
let context: AccountContext
|
public let context: AccountContext
|
||||||
let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
let text: String
|
public let text: String
|
||||||
let animationName: String
|
public let animationName: String
|
||||||
let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let linkAction: ((ItemListTextItemLinkAction) -> Void)?
|
public let linkAction: ((ItemListTextItemLinkAction) -> Void)?
|
||||||
|
|
||||||
init(context: AccountContext, theme: PresentationTheme, text: String, animationName: String, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil) {
|
public init(context: AccountContext, theme: PresentationTheme, text: String, animationName: String, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.text = text
|
self.text = text
|
||||||
@ -29,7 +29,7 @@ class InviteLinkHeaderItem: ListViewItem, ItemListItem {
|
|||||||
self.linkAction = linkAction
|
self.linkAction = linkAction
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
async {
|
async {
|
||||||
let node = InviteLinkHeaderItemNode()
|
let node = InviteLinkHeaderItemNode()
|
||||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
@ -45,7 +45,7 @@ class InviteLinkHeaderItem: ListViewItem, ItemListItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
guard let nodeValue = node() as? InviteLinkHeaderItemNode else {
|
guard let nodeValue = node() as? InviteLinkHeaderItemNode else {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
|
|||||||
@ -31,6 +31,7 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
|
|||||||
let iconSize: CGSize?
|
let iconSize: CGSize?
|
||||||
let iconPlacement: IconPlacement
|
let iconPlacement: IconPlacement
|
||||||
let title: String
|
let title: String
|
||||||
|
let subtitle: String?
|
||||||
let style: ItemListCheckboxItemStyle
|
let style: ItemListCheckboxItemStyle
|
||||||
let color: ItemListCheckboxItemColor
|
let color: ItemListCheckboxItemColor
|
||||||
let textColor: TextColor
|
let textColor: TextColor
|
||||||
@ -40,12 +41,13 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem {
|
|||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
let deleteAction: (() -> Void)?
|
let deleteAction: (() -> Void)?
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, iconSize: CGSize? = nil, iconPlacement: IconPlacement = .default, title: String, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, textColor: TextColor = .primary, checked: Bool, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void, deleteAction: (() -> Void)? = nil) {
|
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, iconSize: CGSize? = nil, iconPlacement: IconPlacement = .default, title: String, subtitle: String? = nil, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, textColor: TextColor = .primary, checked: Bool, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void, deleteAction: (() -> Void)? = nil) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.iconSize = iconSize
|
self.iconSize = iconSize
|
||||||
self.iconPlacement = iconPlacement
|
self.iconPlacement = iconPlacement
|
||||||
self.title = title
|
self.title = title
|
||||||
|
self.subtitle = subtitle
|
||||||
self.style = style
|
self.style = style
|
||||||
self.color = color
|
self.color = color
|
||||||
self.textColor = textColor
|
self.textColor = textColor
|
||||||
@ -111,6 +113,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
|
|||||||
private let imageNode: ASImageNode
|
private let imageNode: ASImageNode
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
|
private let subtitleNode: TextNode
|
||||||
|
|
||||||
private var item: ItemListCheckboxItem?
|
private var item: ItemListCheckboxItem?
|
||||||
|
|
||||||
@ -149,6 +152,11 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.titleNode.contentMode = .left
|
self.titleNode.contentMode = .left
|
||||||
self.titleNode.contentsScale = UIScreen.main.scale
|
self.titleNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
|
self.subtitleNode = TextNode()
|
||||||
|
self.subtitleNode.isUserInteractionEnabled = false
|
||||||
|
self.subtitleNode.contentMode = .left
|
||||||
|
self.subtitleNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
self.highlightedBackgroundNode = ASDisplayNode()
|
self.highlightedBackgroundNode = ASDisplayNode()
|
||||||
self.highlightedBackgroundNode.isLayerBacked = true
|
self.highlightedBackgroundNode.isLayerBacked = true
|
||||||
|
|
||||||
@ -161,6 +169,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.contentContainerNode.addSubnode(self.imageNode)
|
self.contentContainerNode.addSubnode(self.imageNode)
|
||||||
self.contentContainerNode.addSubnode(self.iconNode)
|
self.contentContainerNode.addSubnode(self.iconNode)
|
||||||
self.contentContainerNode.addSubnode(self.titleNode)
|
self.contentContainerNode.addSubnode(self.titleNode)
|
||||||
|
self.contentContainerNode.addSubnode(self.subtitleNode)
|
||||||
self.addSubnode(self.activateArea)
|
self.addSubnode(self.activateArea)
|
||||||
|
|
||||||
self.activateArea.activate = { [weak self] in
|
self.activateArea.activate = { [weak self] in
|
||||||
@ -171,6 +180,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
public func asyncLayout() -> (_ item: ItemListCheckboxItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
public func asyncLayout() -> (_ item: ItemListCheckboxItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
|
||||||
|
|
||||||
let currentItem = self.item
|
let currentItem = self.item
|
||||||
|
|
||||||
@ -181,7 +191,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
|
|||||||
case .left:
|
case .left:
|
||||||
leftInset += 62.0
|
leftInset += 62.0
|
||||||
case .right:
|
case .right:
|
||||||
leftInset += 16.0
|
leftInset += 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let iconInset: CGFloat = 62.0
|
let iconInset: CGFloat = 62.0
|
||||||
@ -195,8 +205,10 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||||
|
let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
|
||||||
|
|
||||||
let titleColor: UIColor
|
let titleColor: UIColor
|
||||||
|
let subtitleColor: UIColor = item.presentationData.theme.list.itemSecondaryTextColor
|
||||||
switch item.textColor {
|
switch item.textColor {
|
||||||
case .primary:
|
case .primary:
|
||||||
titleColor = item.presentationData.theme.list.itemPrimaryTextColor
|
titleColor = item.presentationData.theme.list.itemPrimaryTextColor
|
||||||
@ -206,10 +218,15 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 28.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 28.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.subtitle ?? "", font: subtitleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 28.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let separatorHeight = UIScreenPixel
|
let separatorHeight = UIScreenPixel
|
||||||
|
|
||||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||||
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + 22.0)
|
var contentSize = CGSize(width: params.width, height: titleLayout.size.height + 22.0)
|
||||||
|
if item.subtitle != nil {
|
||||||
|
contentSize.height += subtitleLayout.size.height
|
||||||
|
}
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
|
|
||||||
@ -257,6 +274,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
|
let _ = subtitleApply()
|
||||||
|
|
||||||
if let image = strongSelf.iconNode.image {
|
if let image = strongSelf.iconNode.image {
|
||||||
switch item.style {
|
switch item.style {
|
||||||
@ -313,6 +331,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
||||||
|
|
||||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size)
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size)
|
||||||
|
strongSelf.subtitleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY), size: subtitleLayout.size)
|
||||||
|
|
||||||
if let icon = item.icon {
|
if let icon = item.icon {
|
||||||
let iconSize = item.iconSize ?? icon.size
|
let iconSize = item.iconSize ?? icon.size
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import UIKit
|
|||||||
private let controlStartCharactersSet = CharacterSet(charactersIn: "[*")
|
private let controlStartCharactersSet = CharacterSet(charactersIn: "[*")
|
||||||
private let controlCharactersSet = CharacterSet(charactersIn: "[]()*_-\\")
|
private let controlCharactersSet = CharacterSet(charactersIn: "[]()*_-\\")
|
||||||
|
|
||||||
public final class MarkdownAttributeSet {
|
public final class MarkdownAttributeSet: Equatable {
|
||||||
public let font: UIFont
|
public let font: UIFont
|
||||||
public let textColor: UIColor
|
public let textColor: UIColor
|
||||||
public let additionalAttributes: [String: Any]
|
public let additionalAttributes: [String: Any]
|
||||||
@ -14,9 +14,19 @@ public final class MarkdownAttributeSet {
|
|||||||
self.textColor = textColor
|
self.textColor = textColor
|
||||||
self.additionalAttributes = additionalAttributes
|
self.additionalAttributes = additionalAttributes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: MarkdownAttributeSet, rhs: MarkdownAttributeSet) -> Bool {
|
||||||
|
if !lhs.font.isEqual(rhs.font) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.textColor != rhs.textColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class MarkdownAttributes {
|
public final class MarkdownAttributes: Equatable {
|
||||||
public let body: MarkdownAttributeSet
|
public let body: MarkdownAttributeSet
|
||||||
public let bold: MarkdownAttributeSet
|
public let bold: MarkdownAttributeSet
|
||||||
public let link: MarkdownAttributeSet
|
public let link: MarkdownAttributeSet
|
||||||
@ -28,6 +38,19 @@ public final class MarkdownAttributes {
|
|||||||
self.bold = bold
|
self.bold = bold
|
||||||
self.linkAttribute = linkAttribute
|
self.linkAttribute = linkAttribute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: MarkdownAttributes, rhs: MarkdownAttributes) -> Bool {
|
||||||
|
if lhs.body != rhs.body {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.bold != rhs.bold {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.link != rhs.link {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func escapedPlaintextForMarkdown(_ string: String) -> String {
|
public func escapedPlaintextForMarkdown(_ string: String) -> String {
|
||||||
|
|||||||
43
submodules/PaymentMethodUI/BUILD
Normal file
43
submodules/PaymentMethodUI/BUILD
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "PaymentMethodUI",
|
||||||
|
module_name = "PaymentMethodUI",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display:Display",
|
||||||
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
|
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
|
||||||
|
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||||
|
"//submodules/Components/AnimatedStickerComponent:AnimatedStickerComponent",
|
||||||
|
"//submodules/Components/BundleIconComponent:BundleIconComponent",
|
||||||
|
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
||||||
|
"//submodules/Components/UndoPanelComponent:UndoPanelComponent",
|
||||||
|
"//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent",
|
||||||
|
"//submodules/AccountContext:AccountContext",
|
||||||
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
|
"//submodules/Components/Forms/PrefixSectionGroupComponent:PrefixSectionGroupComponent",
|
||||||
|
"//submodules/Components/Forms/TextInputComponent:TextInputComponent",
|
||||||
|
"//submodules/Markdown:Markdown",
|
||||||
|
"//submodules/InviteLinksUI:InviteLinksUI",
|
||||||
|
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||||
|
"//submodules/Postbox:Postbox",
|
||||||
|
"//submodules/TelegramCore:TelegramCore",
|
||||||
|
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||||
|
"//submodules/ItemListUI:ItemListUI",
|
||||||
|
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||||
|
"//submodules/UndoUI:UndoUI",
|
||||||
|
"//submodules/Stripe:Stripe",
|
||||||
|
"//submodules/Components/Forms/CreditCardInputComponent:CreditCardInputComponent",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -0,0 +1,416 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import ViewControllerComponent
|
||||||
|
import AccountContext
|
||||||
|
import AnimatedStickerComponent
|
||||||
|
import SolidRoundedButtonComponent
|
||||||
|
import MultilineTextComponent
|
||||||
|
import PresentationDataUtils
|
||||||
|
|
||||||
|
public final class SheetComponentEnvironment: Equatable {
|
||||||
|
public let isDisplaying: Bool
|
||||||
|
public let dismiss: () -> Void
|
||||||
|
|
||||||
|
public init(isDisplaying: Bool, dismiss: @escaping () -> Void) {
|
||||||
|
self.isDisplaying = isDisplaying
|
||||||
|
self.dismiss = dismiss
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: SheetComponentEnvironment, rhs: SheetComponentEnvironment) -> Bool {
|
||||||
|
if lhs.isDisplaying != rhs.isDisplaying {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
|
||||||
|
public typealias EnvironmentType = (ChildEnvironmentType, SheetComponentEnvironment)
|
||||||
|
|
||||||
|
public let content: AnyComponent<ChildEnvironmentType>
|
||||||
|
public let backgroundColor: UIColor
|
||||||
|
public let animateOut: ActionSlot<Action<()>>
|
||||||
|
|
||||||
|
public init(content: AnyComponent<ChildEnvironmentType>, backgroundColor: UIColor, animateOut: ActionSlot<Action<()>>) {
|
||||||
|
self.content = content
|
||||||
|
self.backgroundColor = backgroundColor
|
||||||
|
self.animateOut = animateOut
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: SheetComponent, rhs: SheetComponent) -> Bool {
|
||||||
|
if lhs.content != rhs.content {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.backgroundColor != rhs.backgroundColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.animateOut != rhs.animateOut {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView, UIScrollViewDelegate {
|
||||||
|
private let dimView: UIView
|
||||||
|
private let scrollView: UIScrollView
|
||||||
|
private let backgroundView: UIView
|
||||||
|
private let contentView: ComponentHostView<ChildEnvironmentType>
|
||||||
|
|
||||||
|
private var previousIsDisplaying: Bool = false
|
||||||
|
private var dismiss: (() -> Void)?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.dimView = UIView()
|
||||||
|
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
|
||||||
|
|
||||||
|
self.scrollView = UIScrollView()
|
||||||
|
self.scrollView.delaysContentTouches = false
|
||||||
|
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||||
|
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||||
|
}
|
||||||
|
self.scrollView.showsVerticalScrollIndicator = false
|
||||||
|
self.scrollView.showsHorizontalScrollIndicator = false
|
||||||
|
self.scrollView.alwaysBounceVertical = true
|
||||||
|
|
||||||
|
self.backgroundView = UIView()
|
||||||
|
self.backgroundView.layer.cornerRadius = 10.0
|
||||||
|
self.backgroundView.layer.masksToBounds = true
|
||||||
|
|
||||||
|
self.contentView = ComponentHostView<ChildEnvironmentType>()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.dimView)
|
||||||
|
|
||||||
|
self.scrollView.addSubview(self.backgroundView)
|
||||||
|
self.scrollView.addSubview(self.contentView)
|
||||||
|
self.addSubview(self.scrollView)
|
||||||
|
|
||||||
|
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimViewTapGesture(_:))))
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func dimViewTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
if case .ended = recognizer.state {
|
||||||
|
self.dismiss?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
if !self.backgroundView.bounds.contains(self.convert(point, to: self.backgroundView)) {
|
||||||
|
return self.dimView
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.hitTest(point, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func animateOut(completion: @escaping () -> Void) {
|
||||||
|
self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||||
|
self.scrollView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: self.bounds.height - self.scrollView.contentInset.top), duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue, removeOnCompletion: false, additive: true, completion: { _ in
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: SheetComponent<ChildEnvironmentType>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
|
component.animateOut.connect { [weak self] completion in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.animateOut {
|
||||||
|
completion(Void())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.backgroundView.backgroundColor != component.backgroundColor {
|
||||||
|
self.backgroundView.backgroundColor = component.backgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil)
|
||||||
|
|
||||||
|
let contentSize = self.contentView.update(
|
||||||
|
transition: transition,
|
||||||
|
component: component.content,
|
||||||
|
environment: {
|
||||||
|
environment[ChildEnvironmentType.self]
|
||||||
|
},
|
||||||
|
containerSize: CGSize(width: availableSize.width, height: .greatestFiniteMagnitude)
|
||||||
|
)
|
||||||
|
|
||||||
|
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil)
|
||||||
|
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil)
|
||||||
|
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil)
|
||||||
|
self.scrollView.contentSize = contentSize
|
||||||
|
self.scrollView.contentInset = UIEdgeInsets(top: max(0.0, availableSize.height - contentSize.height), left: 0.0, bottom: 0.0, right: 0.0)
|
||||||
|
|
||||||
|
if environment[SheetComponentEnvironment.self].value.isDisplaying, !self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateInTransition.self) {
|
||||||
|
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.scrollView.layer.animatePosition(from: CGPoint(x: 0.0, y: availableSize.height - self.scrollView.contentInset.top), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true, completion: nil)
|
||||||
|
}
|
||||||
|
self.previousIsDisplaying = environment[SheetComponentEnvironment.self].value.isDisplaying
|
||||||
|
|
||||||
|
self.dismiss = environment[SheetComponentEnvironment.self].value.dismiss
|
||||||
|
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
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: .white,
|
||||||
|
animateOut: animateOut
|
||||||
|
),
|
||||||
|
environment: {
|
||||||
|
environment
|
||||||
|
SheetComponentEnvironment(
|
||||||
|
isDisplaying: environment.value.isVisible,
|
||||||
|
dismiss: {
|
||||||
|
animateOut.invoke(Action { _ in
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
449
submodules/PaymentMethodUI/Sources/PaymentCardEntryScreen.swift
Normal file
449
submodules/PaymentMethodUI/Sources/PaymentCardEntryScreen.swift
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
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: Transition) -> 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: Transition) -> 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)
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
self.title = "Add Payment Method"
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
286
submodules/PaymentMethodUI/Sources/PaymentMethodListScreen.swift
Normal file
286
submodules/PaymentMethodUI/Sources/PaymentMethodListScreen.swift
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
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: 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, 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,
|
||||||
|
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
|
||||||
|
}
|
||||||
@ -216,7 +216,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent
|
|||||||
|
|
||||||
let text = text.update(
|
let text = text.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: NSAttributedString(string: environment.strings.CreateExternalStream_Text, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor, paragraphAlignment: .center),
|
text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_Text, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor, paragraphAlignment: .center)),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
lineSpacing: 0.1
|
lineSpacing: 0.1
|
||||||
@ -228,7 +228,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent
|
|||||||
let bottomText = Condition(mode == .create) {
|
let bottomText = Condition(mode == .create) {
|
||||||
bottomText.update(
|
bottomText.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: NSAttributedString(string: environment.strings.CreateExternalStream_StartStreamingInfo, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor, paragraphAlignment: .center),
|
text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_StartStreamingInfo, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor, paragraphAlignment: .center)),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
lineSpacing: 0.1
|
lineSpacing: 0.1
|
||||||
@ -290,7 +290,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent
|
|||||||
if let credentials = context.state.credentials {
|
if let credentials = context.state.credentials {
|
||||||
let credentialsURLTitle = credentialsURLTitle.update(
|
let credentialsURLTitle = credentialsURLTitle.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: NSAttributedString(string: environment.strings.CreateExternalStream_ServerUrl, font: Font.regular(14.0), textColor: environment.theme.list.itemPrimaryTextColor, paragraphAlignment: .left),
|
text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_ServerUrl, font: Font.regular(14.0), textColor: environment.theme.list.itemPrimaryTextColor, paragraphAlignment: .left)),
|
||||||
horizontalAlignment: .left,
|
horizontalAlignment: .left,
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
),
|
),
|
||||||
@ -300,7 +300,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent
|
|||||||
|
|
||||||
let credentialsKeyTitle = credentialsKeyTitle.update(
|
let credentialsKeyTitle = credentialsKeyTitle.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: NSAttributedString(string: environment.strings.CreateExternalStream_StreamKey, font: Font.regular(14.0), textColor: environment.theme.list.itemPrimaryTextColor, paragraphAlignment: .left),
|
text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_StreamKey, font: Font.regular(14.0), textColor: environment.theme.list.itemPrimaryTextColor, paragraphAlignment: .left)),
|
||||||
horizontalAlignment: .left,
|
horizontalAlignment: .left,
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
),
|
),
|
||||||
@ -310,7 +310,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent
|
|||||||
|
|
||||||
let credentialsURLText = credentialsURLText.update(
|
let credentialsURLText = credentialsURLText.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: NSAttributedString(string: credentials.url, font: Font.regular(16.0), textColor: environment.theme.list.itemAccentColor, paragraphAlignment: .left),
|
text: .plain(NSAttributedString(string: credentials.url, font: Font.regular(16.0), textColor: environment.theme.list.itemAccentColor, paragraphAlignment: .left)),
|
||||||
horizontalAlignment: .left,
|
horizontalAlignment: .left,
|
||||||
truncationType: .middle,
|
truncationType: .middle,
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
@ -321,7 +321,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent
|
|||||||
|
|
||||||
let credentialsKeyText = credentialsKeyText.update(
|
let credentialsKeyText = credentialsKeyText.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: NSAttributedString(string: credentials.streamKey, font: Font.regular(16.0), textColor: environment.theme.list.itemAccentColor, paragraphAlignment: .left),
|
text: .plain(NSAttributedString(string: credentials.streamKey, font: Font.regular(16.0), textColor: environment.theme.list.itemAccentColor, paragraphAlignment: .left)),
|
||||||
horizontalAlignment: .left,
|
horizontalAlignment: .left,
|
||||||
truncationType: .middle,
|
truncationType: .middle,
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
|
|||||||
@ -163,12 +163,12 @@ private final class LimitScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let title = title.update(
|
let title = title.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: strings.Premium_LimitReached,
|
string: strings.Premium_LimitReached,
|
||||||
font: Font.semibold(17.0),
|
font: Font.semibold(17.0),
|
||||||
textColor: theme.actionSheet.primaryTextColor,
|
textColor: theme.actionSheet.primaryTextColor,
|
||||||
paragraphAlignment: .center
|
paragraphAlignment: .center
|
||||||
),
|
)),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
),
|
),
|
||||||
@ -186,7 +186,7 @@ private final class LimitScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let text = text.update(
|
let text = text.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: attributedText,
|
text: .plain(attributedText),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
lineSpacing: 0.2
|
lineSpacing: 0.2
|
||||||
|
|||||||
@ -99,6 +99,7 @@ swift_library(
|
|||||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||||
"//submodules/FetchManagerImpl:FetchManagerImpl",
|
"//submodules/FetchManagerImpl:FetchManagerImpl",
|
||||||
"//submodules/ListMessageItem:ListMessageItem",
|
"//submodules/ListMessageItem:ListMessageItem",
|
||||||
|
"//submodules/PaymentMethodUI:PaymentMethodUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#import <Stripe/STPAddress.h>
|
#import <Stripe/STPAddress.h>
|
||||||
#import <Stripe/STPPaymentCardTextField.h>
|
#import <Stripe/STPPaymentCardTextField.h>
|
||||||
|
#import <Stripe/STPFormTextField.h>
|
||||||
#import <Stripe/STPAPIClient.h>
|
#import <Stripe/STPAPIClient.h>
|
||||||
#import <Stripe/STPAPIClient+ApplePay.h>
|
#import <Stripe/STPAPIClient+ApplePay.h>
|
||||||
#import <Stripe/STPAPIResponseDecodable.h>
|
#import <Stripe/STPAPIResponseDecodable.h>
|
||||||
|
|||||||
@ -277,6 +277,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
|
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
|
||||||
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
|
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
|
||||||
dict[-659913713] = { return Api.InputGroupCall.parse_inputGroupCall($0) }
|
dict[-659913713] = { return Api.InputGroupCall.parse_inputGroupCall($0) }
|
||||||
|
dict[-977967015] = { return Api.InputInvoice.parse_inputInvoiceMessage($0) }
|
||||||
|
dict[-1020867857] = { return Api.InputInvoice.parse_inputInvoiceSlug($0) }
|
||||||
dict[-122978821] = { return Api.InputMedia.parse_inputMediaContact($0) }
|
dict[-122978821] = { return Api.InputMedia.parse_inputMediaContact($0) }
|
||||||
dict[-428884101] = { return Api.InputMedia.parse_inputMediaDice($0) }
|
dict[-428884101] = { return Api.InputMedia.parse_inputMediaDice($0) }
|
||||||
dict[860303448] = { return Api.InputMedia.parse_inputMediaDocument($0) }
|
dict[860303448] = { return Api.InputMedia.parse_inputMediaDocument($0) }
|
||||||
@ -987,7 +989,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-1575684144] = { return Api.messages.TranslatedText.parse_translateResultText($0) }
|
dict[-1575684144] = { return Api.messages.TranslatedText.parse_translateResultText($0) }
|
||||||
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
||||||
dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) }
|
dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) }
|
||||||
dict[378828315] = { return Api.payments.PaymentForm.parse_paymentForm($0) }
|
dict[-1362048039] = { return Api.payments.ExportedInvoice.parse_exportedInvoice($0) }
|
||||||
|
dict[-1340916937] = { return Api.payments.PaymentForm.parse_paymentForm($0) }
|
||||||
dict[1891958275] = { return Api.payments.PaymentReceipt.parse_paymentReceipt($0) }
|
dict[1891958275] = { return Api.payments.PaymentReceipt.parse_paymentReceipt($0) }
|
||||||
dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) }
|
dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) }
|
||||||
dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) }
|
dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) }
|
||||||
@ -1280,6 +1283,8 @@ public extension Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.InputGroupCall:
|
case let _1 as Api.InputGroupCall:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.InputInvoice:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.InputMedia:
|
case let _1 as Api.InputMedia:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.InputMessage:
|
case let _1 as Api.InputMessage:
|
||||||
@ -1740,6 +1745,8 @@ public extension Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.payments.BankCardData:
|
case let _1 as Api.payments.BankCardData:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.payments.ExportedInvoice:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.payments.PaymentForm:
|
case let _1 as Api.payments.PaymentForm:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.payments.PaymentReceipt:
|
case let _1 as Api.payments.PaymentReceipt:
|
||||||
|
|||||||
@ -647,18 +647,57 @@ public extension Api.payments {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.payments {
|
public extension Api.payments {
|
||||||
enum PaymentForm: TypeConstructorDescription {
|
enum ExportedInvoice: TypeConstructorDescription {
|
||||||
case paymentForm(flags: Int32, formId: Int64, botId: Int64, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: Api.PaymentSavedCredentials?, users: [Api.User])
|
case exportedInvoice(url: String)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .paymentForm(let flags, let formId, let botId, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let savedInfo, let savedCredentials, let users):
|
case .exportedInvoice(let url):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(378828315)
|
buffer.appendInt32(-1362048039)
|
||||||
|
}
|
||||||
|
serializeString(url, buffer: buffer, boxed: false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .exportedInvoice(let url):
|
||||||
|
return ("exportedInvoice", [("url", String(describing: url))])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_exportedInvoice(_ reader: BufferReader) -> ExportedInvoice? {
|
||||||
|
var _1: String?
|
||||||
|
_1 = parseString(reader)
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
if _c1 {
|
||||||
|
return Api.payments.ExportedInvoice.exportedInvoice(url: _1!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public extension Api.payments {
|
||||||
|
enum PaymentForm: TypeConstructorDescription {
|
||||||
|
case paymentForm(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: Api.PaymentSavedCredentials?, users: [Api.User])
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let savedInfo, let savedCredentials, let users):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1340916937)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt64(formId, buffer: buffer, boxed: false)
|
serializeInt64(formId, buffer: buffer, boxed: false)
|
||||||
serializeInt64(botId, buffer: buffer, boxed: false)
|
serializeInt64(botId, buffer: buffer, boxed: false)
|
||||||
|
serializeString(title, buffer: buffer, boxed: false)
|
||||||
|
serializeString(description, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 5) != 0 {photo!.serialize(buffer, true)}
|
||||||
invoice.serialize(buffer, true)
|
invoice.serialize(buffer, true)
|
||||||
serializeInt64(providerId, buffer: buffer, boxed: false)
|
serializeInt64(providerId, buffer: buffer, boxed: false)
|
||||||
serializeString(url, buffer: buffer, boxed: false)
|
serializeString(url, buffer: buffer, boxed: false)
|
||||||
@ -677,8 +716,8 @@ public extension Api.payments {
|
|||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .paymentForm(let flags, let formId, let botId, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let savedInfo, let savedCredentials, let users):
|
case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let savedInfo, let savedCredentials, let users):
|
||||||
return ("paymentForm", [("flags", String(describing: flags)), ("formId", String(describing: formId)), ("botId", String(describing: botId)), ("invoice", String(describing: invoice)), ("providerId", String(describing: providerId)), ("url", String(describing: url)), ("nativeProvider", String(describing: nativeProvider)), ("nativeParams", String(describing: nativeParams)), ("savedInfo", String(describing: savedInfo)), ("savedCredentials", String(describing: savedCredentials)), ("users", String(describing: users))])
|
return ("paymentForm", [("flags", String(describing: flags)), ("formId", String(describing: formId)), ("botId", String(describing: botId)), ("title", String(describing: title)), ("description", String(describing: description)), ("photo", String(describing: photo)), ("invoice", String(describing: invoice)), ("providerId", String(describing: providerId)), ("url", String(describing: url)), ("nativeProvider", String(describing: nativeProvider)), ("nativeParams", String(describing: nativeParams)), ("savedInfo", String(describing: savedInfo)), ("savedCredentials", String(describing: savedCredentials)), ("users", String(describing: users))])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -689,45 +728,56 @@ public extension Api.payments {
|
|||||||
_2 = reader.readInt64()
|
_2 = reader.readInt64()
|
||||||
var _3: Int64?
|
var _3: Int64?
|
||||||
_3 = reader.readInt64()
|
_3 = reader.readInt64()
|
||||||
var _4: Api.Invoice?
|
var _4: String?
|
||||||
|
_4 = parseString(reader)
|
||||||
|
var _5: String?
|
||||||
|
_5 = parseString(reader)
|
||||||
|
var _6: Api.WebDocument?
|
||||||
|
if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_6 = Api.parse(reader, signature: signature) as? Api.WebDocument
|
||||||
|
} }
|
||||||
|
var _7: Api.Invoice?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
_4 = Api.parse(reader, signature: signature) as? Api.Invoice
|
_7 = Api.parse(reader, signature: signature) as? Api.Invoice
|
||||||
}
|
}
|
||||||
var _5: Int64?
|
var _8: Int64?
|
||||||
_5 = reader.readInt64()
|
_8 = reader.readInt64()
|
||||||
var _6: String?
|
var _9: String?
|
||||||
_6 = parseString(reader)
|
_9 = parseString(reader)
|
||||||
var _7: String?
|
var _10: String?
|
||||||
if Int(_1!) & Int(1 << 4) != 0 {_7 = parseString(reader) }
|
if Int(_1!) & Int(1 << 4) != 0 {_10 = parseString(reader) }
|
||||||
var _8: Api.DataJSON?
|
var _11: Api.DataJSON?
|
||||||
if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() {
|
if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() {
|
||||||
_8 = Api.parse(reader, signature: signature) as? Api.DataJSON
|
_11 = Api.parse(reader, signature: signature) as? Api.DataJSON
|
||||||
} }
|
} }
|
||||||
var _9: Api.PaymentRequestedInfo?
|
var _12: Api.PaymentRequestedInfo?
|
||||||
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||||
_9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo
|
_12 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo
|
||||||
} }
|
} }
|
||||||
var _10: Api.PaymentSavedCredentials?
|
var _13: Api.PaymentSavedCredentials?
|
||||||
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
|
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
|
||||||
_10 = Api.parse(reader, signature: signature) as? Api.PaymentSavedCredentials
|
_13 = Api.parse(reader, signature: signature) as? Api.PaymentSavedCredentials
|
||||||
} }
|
} }
|
||||||
var _11: [Api.User]?
|
var _14: [Api.User]?
|
||||||
if let _ = reader.readInt32() {
|
if let _ = reader.readInt32() {
|
||||||
_11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
_14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||||
}
|
}
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
let _c4 = _4 != nil
|
let _c4 = _4 != nil
|
||||||
let _c5 = _5 != nil
|
let _c5 = _5 != nil
|
||||||
let _c6 = _6 != nil
|
let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil
|
||||||
let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil
|
let _c7 = _7 != nil
|
||||||
let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil
|
let _c8 = _8 != nil
|
||||||
let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil
|
let _c9 = _9 != nil
|
||||||
let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil
|
let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil
|
||||||
let _c11 = _11 != nil
|
let _c11 = (Int(_1!) & Int(1 << 4) == 0) || _11 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
|
let _c12 = (Int(_1!) & Int(1 << 0) == 0) || _12 != nil
|
||||||
return Api.payments.PaymentForm.paymentForm(flags: _1!, formId: _2!, botId: _3!, invoice: _4!, providerId: _5!, url: _6!, nativeProvider: _7, nativeParams: _8, savedInfo: _9, savedCredentials: _10, users: _11!)
|
let _c13 = (Int(_1!) & Int(1 << 1) == 0) || _13 != nil
|
||||||
|
let _c14 = _14 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 {
|
||||||
|
return Api.payments.PaymentForm.paymentForm(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, providerId: _8!, url: _9!, nativeProvider: _10, nativeParams: _11, savedInfo: _12, savedCredentials: _13, users: _14!)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -6192,6 +6192,21 @@ public extension Api.functions.payments {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.payments {
|
||||||
|
static func exportInvoice(invoiceMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.ExportedInvoice>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(261206117)
|
||||||
|
invoiceMedia.serialize(buffer, true)
|
||||||
|
return (FunctionDescription(name: "payments.exportInvoice", parameters: [("invoiceMedia", String(describing: invoiceMedia))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ExportedInvoice? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.payments.ExportedInvoice?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.payments.ExportedInvoice
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.payments {
|
public extension Api.functions.payments {
|
||||||
static func getBankCardData(number: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.BankCardData>) {
|
static func getBankCardData(number: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.BankCardData>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
@ -6208,14 +6223,13 @@ public extension Api.functions.payments {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.payments {
|
public extension Api.functions.payments {
|
||||||
static func getPaymentForm(flags: Int32, peer: Api.InputPeer, msgId: Int32, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentForm>) {
|
static func getPaymentForm(flags: Int32, invoice: Api.InputInvoice, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentForm>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-1976353651)
|
buffer.appendInt32(924093883)
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
peer.serialize(buffer, true)
|
invoice.serialize(buffer, true)
|
||||||
serializeInt32(msgId, buffer: buffer, boxed: false)
|
|
||||||
if Int(flags) & Int(1 << 0) != 0 {themeParams!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 0) != 0 {themeParams!.serialize(buffer, true)}
|
||||||
return (FunctionDescription(name: "payments.getPaymentForm", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("themeParams", String(describing: themeParams))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentForm? in
|
return (FunctionDescription(name: "payments.getPaymentForm", parameters: [("flags", String(describing: flags)), ("invoice", String(describing: invoice)), ("themeParams", String(describing: themeParams))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentForm? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.payments.PaymentForm?
|
var result: Api.payments.PaymentForm?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
@ -6257,18 +6271,17 @@ public extension Api.functions.payments {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.payments {
|
public extension Api.functions.payments {
|
||||||
static func sendPaymentForm(flags: Int32, formId: Int64, peer: Api.InputPeer, msgId: Int32, requestedInfoId: String?, shippingOptionId: String?, credentials: Api.InputPaymentCredentials, tipAmount: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentResult>) {
|
static func sendPaymentForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice, requestedInfoId: String?, shippingOptionId: String?, credentials: Api.InputPaymentCredentials, tipAmount: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentResult>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(818134173)
|
buffer.appendInt32(755192367)
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt64(formId, buffer: buffer, boxed: false)
|
serializeInt64(formId, buffer: buffer, boxed: false)
|
||||||
peer.serialize(buffer, true)
|
invoice.serialize(buffer, true)
|
||||||
serializeInt32(msgId, buffer: buffer, boxed: false)
|
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(requestedInfoId!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 0) != 0 {serializeString(requestedInfoId!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(shippingOptionId!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 1) != 0 {serializeString(shippingOptionId!, buffer: buffer, boxed: false)}
|
||||||
credentials.serialize(buffer, true)
|
credentials.serialize(buffer, true)
|
||||||
if Int(flags) & Int(1 << 2) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 2) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)}
|
||||||
return (FunctionDescription(name: "payments.sendPaymentForm", parameters: [("flags", String(describing: flags)), ("formId", String(describing: formId)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("requestedInfoId", String(describing: requestedInfoId)), ("shippingOptionId", String(describing: shippingOptionId)), ("credentials", String(describing: credentials)), ("tipAmount", String(describing: tipAmount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentResult? in
|
return (FunctionDescription(name: "payments.sendPaymentForm", parameters: [("flags", String(describing: flags)), ("formId", String(describing: formId)), ("invoice", String(describing: invoice)), ("requestedInfoId", String(describing: requestedInfoId)), ("shippingOptionId", String(describing: shippingOptionId)), ("credentials", String(describing: credentials)), ("tipAmount", String(describing: tipAmount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentResult? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.payments.PaymentResult?
|
var result: Api.payments.PaymentResult?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
@ -6279,14 +6292,13 @@ public extension Api.functions.payments {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.payments {
|
public extension Api.functions.payments {
|
||||||
static func validateRequestedInfo(flags: Int32, peer: Api.InputPeer, msgId: Int32, info: Api.PaymentRequestedInfo) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.ValidatedRequestedInfo>) {
|
static func validateRequestedInfo(flags: Int32, invoice: Api.InputInvoice, info: Api.PaymentRequestedInfo) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.ValidatedRequestedInfo>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-619695760)
|
buffer.appendInt32(-1228345045)
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
peer.serialize(buffer, true)
|
invoice.serialize(buffer, true)
|
||||||
serializeInt32(msgId, buffer: buffer, boxed: false)
|
|
||||||
info.serialize(buffer, true)
|
info.serialize(buffer, true)
|
||||||
return (FunctionDescription(name: "payments.validateRequestedInfo", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("info", String(describing: info))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ValidatedRequestedInfo? in
|
return (FunctionDescription(name: "payments.validateRequestedInfo", parameters: [("flags", String(describing: flags)), ("invoice", String(describing: invoice)), ("info", String(describing: info))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ValidatedRequestedInfo? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.payments.ValidatedRequestedInfo?
|
var result: Api.payments.ValidatedRequestedInfo?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
|
|||||||
@ -800,6 +800,68 @@ public extension Api {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api {
|
||||||
|
enum InputInvoice: TypeConstructorDescription {
|
||||||
|
case inputInvoiceMessage(peer: Api.InputPeer, msgId: Int32)
|
||||||
|
case inputInvoiceSlug(slug: String)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .inputInvoiceMessage(let peer, let msgId):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-977967015)
|
||||||
|
}
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
serializeInt32(msgId, buffer: buffer, boxed: false)
|
||||||
|
break
|
||||||
|
case .inputInvoiceSlug(let slug):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1020867857)
|
||||||
|
}
|
||||||
|
serializeString(slug, buffer: buffer, boxed: false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .inputInvoiceMessage(let peer, let msgId):
|
||||||
|
return ("inputInvoiceMessage", [("peer", String(describing: peer)), ("msgId", String(describing: msgId))])
|
||||||
|
case .inputInvoiceSlug(let slug):
|
||||||
|
return ("inputInvoiceSlug", [("slug", String(describing: slug))])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_inputInvoiceMessage(_ reader: BufferReader) -> InputInvoice? {
|
||||||
|
var _1: Api.InputPeer?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
|
||||||
|
}
|
||||||
|
var _2: Int32?
|
||||||
|
_2 = reader.readInt32()
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
if _c1 && _c2 {
|
||||||
|
return Api.InputInvoice.inputInvoiceMessage(peer: _1!, msgId: _2!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static func parse_inputInvoiceSlug(_ reader: BufferReader) -> InputInvoice? {
|
||||||
|
var _1: String?
|
||||||
|
_1 = parseString(reader)
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
if _c1 {
|
||||||
|
return Api.InputInvoice.inputInvoiceSlug(slug: _1!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum InputMedia: TypeConstructorDescription {
|
enum InputMedia: TypeConstructorDescription {
|
||||||
case inputMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String)
|
case inputMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String)
|
||||||
|
|||||||
@ -286,7 +286,7 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
let noSignalSize = noSignalView.update(
|
let noSignalSize = noSignalView.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
text: NSAttributedString(string: component.isAdmin ? presentationData.strings.LiveStream_NoSignalAdminText : presentationData.strings.LiveStream_NoSignalUserText(component.peerTitle).string, font: Font.regular(16.0), textColor: .white, paragraphAlignment: .center),
|
text: .plain(NSAttributedString(string: component.isAdmin ? presentationData.strings.LiveStream_NoSignalAdminText : presentationData.strings.LiveStream_NoSignalUserText(component.peerTitle).string, font: Font.regular(16.0), textColor: .white, paragraphAlignment: .center)),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0
|
||||||
)),
|
)),
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import MtProtoKit
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramApi
|
import TelegramApi
|
||||||
|
|
||||||
|
public enum BotPaymentInvoiceSource {
|
||||||
|
case message(MessageId)
|
||||||
|
case slug(String)
|
||||||
|
}
|
||||||
|
|
||||||
public struct BotPaymentInvoiceFields: OptionSet {
|
public struct BotPaymentInvoiceFields: OptionSet {
|
||||||
public var rawValue: Int32
|
public var rawValue: Int32
|
||||||
@ -173,15 +177,70 @@ extension BotPaymentRequestedInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_fetchBotPaymentForm(postbox: Postbox, network: Network, messageId: MessageId, themeParams: [String: Any]?) -> Signal<BotPaymentForm, BotPaymentFormRequestError> {
|
func _internal_fetchBotPaymentInvoice(postbox: Postbox, network: Network, source: BotPaymentInvoiceSource) -> Signal<TelegramMediaInvoice, BotPaymentFormRequestError> {
|
||||||
return postbox.transaction { transaction -> Api.InputPeer? in
|
return postbox.transaction { transaction -> Api.InputInvoice? in
|
||||||
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
switch source {
|
||||||
|
case let .message(messageId):
|
||||||
|
guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .inputInvoiceMessage(peer: inputPeer, msgId: messageId.id)
|
||||||
|
case let .slug(slug):
|
||||||
|
return .inputInvoiceSlug(slug: slug)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|> castError(BotPaymentFormRequestError.self)
|
|> castError(BotPaymentFormRequestError.self)
|
||||||
|> mapToSignal { inputPeer -> Signal<BotPaymentForm, BotPaymentFormRequestError> in
|
|> mapToSignal { invoice -> Signal<TelegramMediaInvoice, BotPaymentFormRequestError> in
|
||||||
guard let inputPeer = inputPeer else {
|
guard let invoice = invoice else {
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let flags: Int32 = 0
|
||||||
|
|
||||||
|
return network.request(Api.functions.payments.getPaymentForm(flags: flags, invoice: invoice, themeParams: nil))
|
||||||
|
|> `catch` { _ -> Signal<Api.payments.PaymentForm, BotPaymentFormRequestError> in
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|> mapToSignal { result -> Signal<TelegramMediaInvoice, BotPaymentFormRequestError> in
|
||||||
|
return postbox.transaction { transaction -> TelegramMediaInvoice in
|
||||||
|
switch result {
|
||||||
|
case let .paymentForm(_, _, _, title, description, photo, invoice, _, _, _, _, _, _, _):
|
||||||
|
let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice)
|
||||||
|
|
||||||
|
var parsedFlags = TelegramMediaInvoiceFlags()
|
||||||
|
if parsedInvoice.isTest {
|
||||||
|
parsedFlags.insert(.isTest)
|
||||||
|
}
|
||||||
|
if parsedInvoice.requestedFields.contains(.shippingAddress) {
|
||||||
|
parsedFlags.insert(.shippingAddressRequested)
|
||||||
|
}
|
||||||
|
|
||||||
|
return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: 0, startParam: "", flags: parsedFlags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> mapError { _ -> BotPaymentFormRequestError in }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func _internal_fetchBotPaymentForm(postbox: Postbox, network: Network, source: BotPaymentInvoiceSource, themeParams: [String: Any]?) -> Signal<BotPaymentForm, BotPaymentFormRequestError> {
|
||||||
|
return postbox.transaction { transaction -> Api.InputInvoice? in
|
||||||
|
switch source {
|
||||||
|
case let .message(messageId):
|
||||||
|
guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .inputInvoiceMessage(peer: inputPeer, msgId: messageId.id)
|
||||||
|
case let .slug(slug):
|
||||||
|
return .inputInvoiceSlug(slug: slug)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> castError(BotPaymentFormRequestError.self)
|
||||||
|
|> mapToSignal { invoice -> Signal<BotPaymentForm, BotPaymentFormRequestError> in
|
||||||
|
guard let invoice = invoice else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
var serializedThemeParams: Api.DataJSON?
|
var serializedThemeParams: Api.DataJSON?
|
||||||
if let themeParams = themeParams, let data = try? JSONSerialization.data(withJSONObject: themeParams, options: []), let dataString = String(data: data, encoding: .utf8) {
|
if let themeParams = themeParams, let data = try? JSONSerialization.data(withJSONObject: themeParams, options: []), let dataString = String(data: data, encoding: .utf8) {
|
||||||
@ -191,14 +250,18 @@ func _internal_fetchBotPaymentForm(postbox: Postbox, network: Network, messageId
|
|||||||
flags |= 1 << 0
|
flags |= 1 << 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return network.request(Api.functions.payments.getPaymentForm(flags: flags, peer: inputPeer, msgId: messageId.id, themeParams: serializedThemeParams))
|
return network.request(Api.functions.payments.getPaymentForm(flags: flags, invoice: invoice, themeParams: serializedThemeParams))
|
||||||
|> `catch` { _ -> Signal<Api.payments.PaymentForm, BotPaymentFormRequestError> in
|
|> `catch` { _ -> Signal<Api.payments.PaymentForm, BotPaymentFormRequestError> in
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<BotPaymentForm, BotPaymentFormRequestError> in
|
|> mapToSignal { result -> Signal<BotPaymentForm, BotPaymentFormRequestError> in
|
||||||
return postbox.transaction { transaction -> BotPaymentForm in
|
return postbox.transaction { transaction -> BotPaymentForm in
|
||||||
switch result {
|
switch result {
|
||||||
case let .paymentForm(flags, id, botId, invoice, providerId, url, nativeProvider, nativeParams, savedInfo, savedCredentials, apiUsers):
|
case let .paymentForm(flags, id, botId, title, description, photo, invoice, providerId, url, nativeProvider, nativeParams, savedInfo, savedCredentials, apiUsers):
|
||||||
|
let _ = title
|
||||||
|
let _ = description
|
||||||
|
let _ = photo
|
||||||
|
|
||||||
var peers: [Peer] = []
|
var peers: [Peer] = []
|
||||||
for user in apiUsers {
|
for user in apiUsers {
|
||||||
let parsed = TelegramUser(user: user)
|
let parsed = TelegramUser(user: user)
|
||||||
@ -268,13 +331,21 @@ extension BotPaymentShippingOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_validateBotPaymentForm(account: Account, saveInfo: Bool, messageId: MessageId, formInfo: BotPaymentRequestedInfo) -> Signal<BotPaymentValidatedFormInfo, ValidateBotPaymentFormError> {
|
func _internal_validateBotPaymentForm(account: Account, saveInfo: Bool, source: BotPaymentInvoiceSource, formInfo: BotPaymentRequestedInfo) -> Signal<BotPaymentValidatedFormInfo, ValidateBotPaymentFormError> {
|
||||||
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
return account.postbox.transaction { transaction -> Api.InputInvoice? in
|
||||||
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
switch source {
|
||||||
|
case let .message(messageId):
|
||||||
|
guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .inputInvoiceMessage(peer: inputPeer, msgId: messageId.id)
|
||||||
|
case let .slug(slug):
|
||||||
|
return .inputInvoiceSlug(slug: slug)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|> castError(ValidateBotPaymentFormError.self)
|
|> castError(ValidateBotPaymentFormError.self)
|
||||||
|> mapToSignal { inputPeer -> Signal<BotPaymentValidatedFormInfo, ValidateBotPaymentFormError> in
|
|> mapToSignal { invoice -> Signal<BotPaymentValidatedFormInfo, ValidateBotPaymentFormError> in
|
||||||
guard let inputPeer = inputPeer else {
|
guard let invoice = invoice else {
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +368,7 @@ func _internal_validateBotPaymentForm(account: Account, saveInfo: Bool, messageI
|
|||||||
infoFlags |= (1 << 3)
|
infoFlags |= (1 << 3)
|
||||||
apiShippingAddress = .postAddress(streetLine1: address.streetLine1, streetLine2: address.streetLine2, city: address.city, state: address.state, countryIso2: address.countryIso2, postCode: address.postCode)
|
apiShippingAddress = .postAddress(streetLine1: address.streetLine1, streetLine2: address.streetLine2, city: address.city, state: address.state, countryIso2: address.countryIso2, postCode: address.postCode)
|
||||||
}
|
}
|
||||||
return account.network.request(Api.functions.payments.validateRequestedInfo(flags: flags, peer: inputPeer, msgId: messageId.id, info: .paymentRequestedInfo(flags: infoFlags, name: formInfo.name, phone: formInfo.phone, email: formInfo.email, shippingAddress: apiShippingAddress)))
|
return account.network.request(Api.functions.payments.validateRequestedInfo(flags: flags, invoice: invoice, info: .paymentRequestedInfo(flags: infoFlags, name: formInfo.name, phone: formInfo.phone, email: formInfo.email, shippingAddress: apiShippingAddress)))
|
||||||
|> mapError { error -> ValidateBotPaymentFormError in
|
|> mapError { error -> ValidateBotPaymentFormError in
|
||||||
if error.errorDescription == "SHIPPING_NOT_AVAILABLE" {
|
if error.errorDescription == "SHIPPING_NOT_AVAILABLE" {
|
||||||
return .shippingNotAvailable
|
return .shippingNotAvailable
|
||||||
@ -346,13 +417,21 @@ public enum SendBotPaymentResult {
|
|||||||
case externalVerificationRequired(url: String)
|
case externalVerificationRequired(url: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_sendBotPaymentForm(account: Account, messageId: MessageId, formId: Int64, validatedInfoId: String?, shippingOptionId: String?, tipAmount: Int64?, credentials: BotPaymentCredentials) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
|
func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPaymentInvoiceSource, validatedInfoId: String?, shippingOptionId: String?, tipAmount: Int64?, credentials: BotPaymentCredentials) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
|
||||||
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
return account.postbox.transaction { transaction -> Api.InputInvoice? in
|
||||||
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
switch source {
|
||||||
|
case let .message(messageId):
|
||||||
|
guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .inputInvoiceMessage(peer: inputPeer, msgId: messageId.id)
|
||||||
|
case let .slug(slug):
|
||||||
|
return .inputInvoiceSlug(slug: slug)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|> castError(SendBotPaymentFormError.self)
|
|> castError(SendBotPaymentFormError.self)
|
||||||
|> mapToSignal { inputPeer -> Signal<SendBotPaymentResult, SendBotPaymentFormError> in
|
|> mapToSignal { invoice -> Signal<SendBotPaymentResult, SendBotPaymentFormError> in
|
||||||
guard let inputPeer = inputPeer else {
|
guard let invoice = invoice else {
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,7 +458,8 @@ func _internal_sendBotPaymentForm(account: Account, messageId: MessageId, formId
|
|||||||
if tipAmount != nil {
|
if tipAmount != nil {
|
||||||
flags |= (1 << 2)
|
flags |= (1 << 2)
|
||||||
}
|
}
|
||||||
return account.network.request(Api.functions.payments.sendPaymentForm(flags: flags, formId: formId, peer: inputPeer, msgId: messageId.id, requestedInfoId: validatedInfoId, shippingOptionId: shippingOptionId, credentials: apiCredentials, tipAmount: tipAmount))
|
|
||||||
|
return account.network.request(Api.functions.payments.sendPaymentForm(flags: flags, formId: formId, invoice: invoice, requestedInfoId: validatedInfoId, shippingOptionId: shippingOptionId, credentials: apiCredentials, tipAmount: tipAmount))
|
||||||
|> map { result -> SendBotPaymentResult in
|
|> map { result -> SendBotPaymentResult in
|
||||||
switch result {
|
switch result {
|
||||||
case let .paymentResult(updates):
|
case let .paymentResult(updates):
|
||||||
@ -392,11 +472,16 @@ func _internal_sendBotPaymentForm(account: Account, messageId: MessageId, formId
|
|||||||
if case .paymentSent = action.action {
|
if case .paymentSent = action.action {
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let reply = attribute as? ReplyMessageAttribute {
|
if let reply = attribute as? ReplyMessageAttribute {
|
||||||
|
switch source {
|
||||||
|
case let .message(messageId):
|
||||||
if reply.messageId == messageId {
|
if reply.messageId == messageId {
|
||||||
if case let .Id(id) = message.id {
|
if case let .Id(id) = message.id {
|
||||||
receiptMessageId = id
|
receiptMessageId = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case .slug:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,16 +13,20 @@ public extension TelegramEngine {
|
|||||||
return _internal_getBankCardInfo(account: self.account, cardNumber: cardNumber)
|
return _internal_getBankCardInfo(account: self.account, cardNumber: cardNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchBotPaymentForm(messageId: MessageId, themeParams: [String: Any]?) -> Signal<BotPaymentForm, BotPaymentFormRequestError> {
|
public func fetchBotPaymentInvoice(source: BotPaymentInvoiceSource) -> Signal<TelegramMediaInvoice, BotPaymentFormRequestError> {
|
||||||
return _internal_fetchBotPaymentForm(postbox: self.account.postbox, network: self.account.network, messageId: messageId, themeParams: themeParams)
|
return _internal_fetchBotPaymentInvoice(postbox: self.account.postbox, network: self.account.network, source: source)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func validateBotPaymentForm(saveInfo: Bool, messageId: MessageId, formInfo: BotPaymentRequestedInfo) -> Signal<BotPaymentValidatedFormInfo, ValidateBotPaymentFormError> {
|
public func fetchBotPaymentForm(source: BotPaymentInvoiceSource, themeParams: [String: Any]?) -> Signal<BotPaymentForm, BotPaymentFormRequestError> {
|
||||||
return _internal_validateBotPaymentForm(account: self.account, saveInfo: saveInfo, messageId: messageId, formInfo: formInfo)
|
return _internal_fetchBotPaymentForm(postbox: self.account.postbox, network: self.account.network, source: source, themeParams: themeParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sendBotPaymentForm(messageId: MessageId, formId: Int64, validatedInfoId: String?, shippingOptionId: String?, tipAmount: Int64?, credentials: BotPaymentCredentials) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
|
public func validateBotPaymentForm(saveInfo: Bool, source: BotPaymentInvoiceSource, formInfo: BotPaymentRequestedInfo) -> Signal<BotPaymentValidatedFormInfo, ValidateBotPaymentFormError> {
|
||||||
return _internal_sendBotPaymentForm(account: self.account, messageId: messageId, formId: formId, validatedInfoId: validatedInfoId, shippingOptionId: shippingOptionId, tipAmount: tipAmount, credentials: credentials)
|
return _internal_validateBotPaymentForm(account: self.account, saveInfo: saveInfo, source: source, formInfo: formInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func sendBotPaymentForm(source: BotPaymentInvoiceSource, formId: Int64, validatedInfoId: String?, shippingOptionId: String?, tipAmount: Int64?, credentials: BotPaymentCredentials) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
|
||||||
|
return _internal_sendBotPaymentForm(account: self.account, formId: formId, source: source, validatedInfoId: validatedInfoId, shippingOptionId: shippingOptionId, tipAmount: tipAmount, credentials: credentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestBotPaymentReceipt(messageId: MessageId) -> Signal<BotPaymentReceipt, RequestBotPaymentReceiptError> {
|
public func requestBotPaymentReceipt(messageId: MessageId) -> Signal<BotPaymentReceipt, RequestBotPaymentReceiptError> {
|
||||||
|
|||||||
1549
submodules/TelegramUI/Resources/PATTERN_static.svg
Normal file
1549
submodules/TelegramUI/Resources/PATTERN_static.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 127 KiB |
BIN
submodules/TelegramUI/Resources/ptrnCAT_1162_1918.tgs
Normal file
BIN
submodules/TelegramUI/Resources/ptrnCAT_1162_1918.tgs
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/ptrnDOG_0440_2284.tgs
Normal file
BIN
submodules/TelegramUI/Resources/ptrnDOG_0440_2284.tgs
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/ptrnGLOB_0438_1553.tgs
Normal file
BIN
submodules/TelegramUI/Resources/ptrnGLOB_0438_1553.tgs
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/ptrnSLON_0906_1033.tgs
Normal file
BIN
submodules/TelegramUI/Resources/ptrnSLON_0906_1033.tgs
Normal file
Binary file not shown.
@ -2539,12 +2539,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
} else {
|
} else {
|
||||||
let inputData = Promise<BotCheckoutController.InputData?>()
|
let inputData = Promise<BotCheckoutController.InputData?>()
|
||||||
inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, messageId: message.id)
|
inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .message(message.id))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
|
|> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
})
|
})
|
||||||
strongSelf.present(BotCheckoutController(context: strongSelf.context, invoice: invoice, messageId: messageId, inputData: inputData, completed: { currencyValue, receiptMessageId in
|
strongSelf.present(BotCheckoutController(context: strongSelf.context, invoice: invoice, source: .message(messageId), inputData: inputData, completed: { currencyValue, receiptMessageId in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import InviteLinksUI
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import TelegramCallsUI
|
import TelegramCallsUI
|
||||||
import WallpaperBackgroundNode
|
import WallpaperBackgroundNode
|
||||||
|
import BotPaymentsUI
|
||||||
|
|
||||||
private final class ChatRecentActionsListOpaqueState {
|
private final class ChatRecentActionsListOpaqueState {
|
||||||
let entries: [ChatRecentActionsEntry]
|
let entries: [ChatRecentActionsEntry]
|
||||||
@ -899,6 +900,30 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
case let .stickerPack(name):
|
case let .stickerPack(name):
|
||||||
let packReference: StickerPackReference = .name(name)
|
let packReference: StickerPackReference = .name(name)
|
||||||
strongSelf.presentController(StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.getNavigationController()), .window(.root), nil)
|
strongSelf.presentController(StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.getNavigationController()), .window(.root), nil)
|
||||||
|
case let .invoice(slug, invoice):
|
||||||
|
let inputData = Promise<BotCheckoutController.InputData?>()
|
||||||
|
inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .slug(slug))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
})
|
||||||
|
strongSelf.controllerInteraction.presentController(BotCheckoutController(context: strongSelf.context, invoice: invoice, source: .slug(slug), inputData: inputData, completed: { currencyValue, receiptMessageId in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = strongSelf
|
||||||
|
/*strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .paymentSent(currencyValue: currencyValue, itemTitle: invoice.title), elevatedLayout: false, action: { action in
|
||||||
|
guard let strongSelf = self, let receiptMessageId = receiptMessageId else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if case .info = action {
|
||||||
|
strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}), in: .current)*/
|
||||||
|
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
case let .instantView(webpage, anchor):
|
case let .instantView(webpage, anchor):
|
||||||
strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, sourcePeerType: .channel, anchor: anchor))
|
strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, sourcePeerType: .channel, anchor: anchor))
|
||||||
case let .join(link):
|
case let .join(link):
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import ImportStickerPackUI
|
|||||||
import PeerInfoUI
|
import PeerInfoUI
|
||||||
import Markdown
|
import Markdown
|
||||||
import WebUI
|
import WebUI
|
||||||
|
import BotPaymentsUI
|
||||||
|
|
||||||
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
|
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
|
||||||
if case .default = navigation {
|
if case .default = navigation {
|
||||||
@ -574,5 +575,28 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
case let .invoice(slug, invoice):
|
||||||
|
dismissInput()
|
||||||
|
if let navigationController = navigationController {
|
||||||
|
let inputData = Promise<BotCheckoutController.InputData?>()
|
||||||
|
inputData.set(BotCheckoutController.InputData.fetch(context: context, source: .slug(slug))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
})
|
||||||
|
navigationController.pushViewController(BotCheckoutController(context: context, invoice: invoice, source: .slug(slug), inputData: inputData, completed: { currencyValue, receiptMessageId in
|
||||||
|
/*strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .paymentSent(currencyValue: currencyValue, itemTitle: invoice.title), elevatedLayout: false, action: { action in
|
||||||
|
guard let strongSelf = self, let receiptMessageId = receiptMessageId else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if case .info = action {
|
||||||
|
strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}), in: .current)*/
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -293,6 +293,22 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
convertedUrl = "https://t.me/addstickers/\(set)"
|
convertedUrl = "https://t.me/addstickers/\(set)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if parsedUrl.host == "invoice" {
|
||||||
|
if let components = URLComponents(string: "/?" + query) {
|
||||||
|
var slug: String?
|
||||||
|
if let queryItems = components.queryItems {
|
||||||
|
for queryItem in queryItems {
|
||||||
|
if let value = queryItem.value {
|
||||||
|
if queryItem.name == "slug" {
|
||||||
|
slug = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let slug = slug {
|
||||||
|
convertedUrl = "https://t.me/invoice/\(slug)"
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if parsedUrl.host == "setlanguage" {
|
} else if parsedUrl.host == "setlanguage" {
|
||||||
if let components = URLComponents(string: "/?" + query) {
|
if let components = URLComponents(string: "/?" + query) {
|
||||||
var lang: String?
|
var lang: String?
|
||||||
|
|||||||
@ -66,6 +66,7 @@ import QrCodeUI
|
|||||||
import TranslateUI
|
import TranslateUI
|
||||||
import ChatPresentationInterfaceState
|
import ChatPresentationInterfaceState
|
||||||
import CreateExternalMediaStreamScreen
|
import CreateExternalMediaStreamScreen
|
||||||
|
import PaymentMethodUI
|
||||||
|
|
||||||
protocol PeerInfoScreenItem: AnyObject {
|
protocol PeerInfoScreenItem: AnyObject {
|
||||||
var id: AnyHashable { get }
|
var id: AnyHashable { get }
|
||||||
@ -463,6 +464,7 @@ private final class PeerInfoInteraction {
|
|||||||
let requestLayout: (Bool) -> Void
|
let requestLayout: (Bool) -> Void
|
||||||
let openEncryptionKey: () -> Void
|
let openEncryptionKey: () -> Void
|
||||||
let openSettings: (PeerInfoSettingsSection) -> Void
|
let openSettings: (PeerInfoSettingsSection) -> Void
|
||||||
|
let openPaymentMethod: () -> Void
|
||||||
let switchToAccount: (AccountRecordId) -> Void
|
let switchToAccount: (AccountRecordId) -> Void
|
||||||
let logoutAccount: (AccountRecordId) -> Void
|
let logoutAccount: (AccountRecordId) -> Void
|
||||||
let accountContextMenu: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void
|
let accountContextMenu: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void
|
||||||
@ -505,6 +507,7 @@ private final class PeerInfoInteraction {
|
|||||||
requestLayout: @escaping (Bool) -> Void,
|
requestLayout: @escaping (Bool) -> Void,
|
||||||
openEncryptionKey: @escaping () -> Void,
|
openEncryptionKey: @escaping () -> Void,
|
||||||
openSettings: @escaping (PeerInfoSettingsSection) -> Void,
|
openSettings: @escaping (PeerInfoSettingsSection) -> Void,
|
||||||
|
openPaymentMethod: @escaping () -> Void,
|
||||||
switchToAccount: @escaping (AccountRecordId) -> Void,
|
switchToAccount: @escaping (AccountRecordId) -> Void,
|
||||||
logoutAccount: @escaping (AccountRecordId) -> Void,
|
logoutAccount: @escaping (AccountRecordId) -> Void,
|
||||||
accountContextMenu: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void,
|
accountContextMenu: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void,
|
||||||
@ -546,6 +549,7 @@ private final class PeerInfoInteraction {
|
|||||||
self.requestLayout = requestLayout
|
self.requestLayout = requestLayout
|
||||||
self.openEncryptionKey = openEncryptionKey
|
self.openEncryptionKey = openEncryptionKey
|
||||||
self.openSettings = openSettings
|
self.openSettings = openSettings
|
||||||
|
self.openPaymentMethod = openPaymentMethod
|
||||||
self.switchToAccount = switchToAccount
|
self.switchToAccount = switchToAccount
|
||||||
self.logoutAccount = logoutAccount
|
self.logoutAccount = logoutAccount
|
||||||
self.accountContextMenu = accountContextMenu
|
self.accountContextMenu = accountContextMenu
|
||||||
@ -568,6 +572,7 @@ private enum SettingsSection: Int, CaseIterable {
|
|||||||
case proxy
|
case proxy
|
||||||
case shortcuts
|
case shortcuts
|
||||||
case advanced
|
case advanced
|
||||||
|
case payment
|
||||||
case extra
|
case extra
|
||||||
case support
|
case support
|
||||||
}
|
}
|
||||||
@ -717,6 +722,10 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
|||||||
interaction.openSettings(.language)
|
interaction.openSettings(.language)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
/*items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: "Payment Method", icon: PresentationResourcesSettings.language, action: {
|
||||||
|
interaction.openPaymentMethod()
|
||||||
|
}))*/
|
||||||
|
|
||||||
let stickersLabel: String
|
let stickersLabel: String
|
||||||
if let settings = data.globalSettings {
|
if let settings = data.globalSettings {
|
||||||
stickersLabel = settings.unreadTrendingStickerPacks > 0 ? "\(settings.unreadTrendingStickerPacks)" : ""
|
stickersLabel = settings.unreadTrendingStickerPacks > 0 ? "\(settings.unreadTrendingStickerPacks)" : ""
|
||||||
@ -1785,6 +1794,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
openSettings: { [weak self] section in
|
openSettings: { [weak self] section in
|
||||||
self?.openSettings(section: section)
|
self?.openSettings(section: section)
|
||||||
},
|
},
|
||||||
|
openPaymentMethod: { [weak self] in
|
||||||
|
self?.openPaymentMethod()
|
||||||
|
},
|
||||||
switchToAccount: { [weak self] accountId in
|
switchToAccount: { [weak self] accountId in
|
||||||
self?.switchToAccount(id: accountId)
|
self?.switchToAccount(id: accountId)
|
||||||
},
|
},
|
||||||
@ -6225,6 +6237,20 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate func openPaymentMethod() {
|
||||||
|
self.controller?.push(AddPaymentMethodSheetScreen(context: self.context, action: { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.controller?.push(PaymentCardEntryScreen(context: strongSelf.context, completion: { result in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.controller?.push(paymentMethodListScreen(context: strongSelf.context, items: [result]))
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
private func openFaq(anchor: String? = nil) {
|
private func openFaq(anchor: String? = nil) {
|
||||||
let presentationData = self.presentationData
|
let presentationData = self.presentationData
|
||||||
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
|
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
|
||||||
|
|||||||
@ -254,7 +254,7 @@ private final class TranslateScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
let originalTitle = originalTitle.update(
|
let originalTitle = originalTitle.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: NSAttributedString(string: fromLanguage, font: Font.medium(13.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .natural),
|
text: .plain(NSAttributedString(string: fromLanguage, font: Font.medium(13.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .natural)),
|
||||||
horizontalAlignment: .natural,
|
horizontalAlignment: .natural,
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
),
|
),
|
||||||
@ -264,7 +264,7 @@ private final class TranslateScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let originalText = originalText.update(
|
let originalText = originalText.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: NSAttributedString(string: state.text, font: Font.medium(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .natural),
|
text: .plain(NSAttributedString(string: state.text, font: Font.medium(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .natural)),
|
||||||
horizontalAlignment: .natural,
|
horizontalAlignment: .natural,
|
||||||
maximumNumberOfLines: state.textExpanded ? 0 : 1,
|
maximumNumberOfLines: state.textExpanded ? 0 : 1,
|
||||||
lineSpacing: 0.1
|
lineSpacing: 0.1
|
||||||
@ -276,7 +276,7 @@ private final class TranslateScreenComponent: CombinedComponent {
|
|||||||
let toLanguage = locale.localizedString(forLanguageCode: state.toLanguage) ?? ""
|
let toLanguage = locale.localizedString(forLanguageCode: state.toLanguage) ?? ""
|
||||||
let translationTitle = translationTitle.update(
|
let translationTitle = translationTitle.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: NSAttributedString(string: toLanguage, font: Font.medium(13.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .natural),
|
text: .plain(NSAttributedString(string: toLanguage, font: Font.medium(13.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .natural)),
|
||||||
horizontalAlignment: .natural,
|
horizontalAlignment: .natural,
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
),
|
),
|
||||||
@ -291,7 +291,7 @@ private final class TranslateScreenComponent: CombinedComponent {
|
|||||||
if let translatedText = state.translatedText {
|
if let translatedText = state.translatedText {
|
||||||
maybeTranslationText = translationText.update(
|
maybeTranslationText = translationText.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: NSAttributedString(string: translatedText, font: Font.medium(17.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .natural),
|
text: .plain(NSAttributedString(string: translatedText, font: Font.medium(17.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .natural)),
|
||||||
horizontalAlignment: .natural,
|
horizontalAlignment: .natural,
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
lineSpacing: 0.1
|
lineSpacing: 0.1
|
||||||
|
|||||||
@ -77,6 +77,7 @@ public enum ParsedInternalUrl {
|
|||||||
case peerId(PeerId)
|
case peerId(PeerId)
|
||||||
case privateMessage(messageId: MessageId, threadId: Int32?, timecode: Double?)
|
case privateMessage(messageId: MessageId, threadId: Int32?, timecode: Double?)
|
||||||
case stickerPack(String)
|
case stickerPack(String)
|
||||||
|
case invoice(String)
|
||||||
case join(String)
|
case join(String)
|
||||||
case localization(String)
|
case localization(String)
|
||||||
case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?)
|
case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?)
|
||||||
@ -246,6 +247,8 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
} else if pathComponents.count == 2 || pathComponents.count == 3 {
|
} else if pathComponents.count == 2 || pathComponents.count == 3 {
|
||||||
if pathComponents[0] == "addstickers" {
|
if pathComponents[0] == "addstickers" {
|
||||||
return .stickerPack(pathComponents[1])
|
return .stickerPack(pathComponents[1])
|
||||||
|
} else if pathComponents[0] == "invoice" {
|
||||||
|
return .invoice(pathComponents[1])
|
||||||
} else if pathComponents[0] == "joinchat" || pathComponents[0] == "joinchannel" {
|
} else if pathComponents[0] == "joinchat" || pathComponents[0] == "joinchannel" {
|
||||||
return .join(pathComponents[1])
|
return .join(pathComponents[1])
|
||||||
} else if pathComponents[0] == "setlanguage" {
|
} else if pathComponents[0] == "setlanguage" {
|
||||||
@ -558,6 +561,18 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||||||
}
|
}
|
||||||
case let .stickerPack(name):
|
case let .stickerPack(name):
|
||||||
return .single(.stickerPack(name: name))
|
return .single(.stickerPack(name: name))
|
||||||
|
case let .invoice(slug):
|
||||||
|
return context.engine.payments.fetchBotPaymentInvoice(source: .slug(slug))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<TelegramMediaInvoice?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> map { invoice -> ResolvedUrl? in
|
||||||
|
guard let invoice = invoice else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .invoice(slug: slug, invoice: invoice)
|
||||||
|
}
|
||||||
case let .join(link):
|
case let .join(link):
|
||||||
return .single(.join(link))
|
return .single(.join(link))
|
||||||
case let .localization(identifier):
|
case let .localization(identifier):
|
||||||
|
|||||||
@ -22,6 +22,9 @@ swift_library(
|
|||||||
"//submodules/Svg:Svg",
|
"//submodules/Svg:Svg",
|
||||||
"//submodules/GZip:GZip",
|
"//submodules/GZip:GZip",
|
||||||
"//submodules/AppBundle:AppBundle",
|
"//submodules/AppBundle:AppBundle",
|
||||||
|
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||||
|
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||||
|
"//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -12,6 +12,9 @@ import FastBlur
|
|||||||
import Svg
|
import Svg
|
||||||
import GZip
|
import GZip
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
import AnimatedStickerNode
|
||||||
|
import TelegramAnimatedStickerNode
|
||||||
|
import HierarchyTrackingLayer
|
||||||
|
|
||||||
private let motionAmount: CGFloat = 32.0
|
private let motionAmount: CGFloat = 32.0
|
||||||
|
|
||||||
@ -428,6 +431,10 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
}
|
}
|
||||||
private static var cachedSharedPattern: (PatternKey, UIImage)?
|
private static var cachedSharedPattern: (PatternKey, UIImage)?
|
||||||
|
|
||||||
|
private var inlineAnimationNodes: [(AnimatedStickerNode, CGPoint)] = []
|
||||||
|
private let hierarchyTrackingLayer = HierarchyTrackingLayer()
|
||||||
|
private var activateInlineAnimationTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
private let _isReady = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let _isReady = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
var isReady: Signal<Bool, NoError> {
|
var isReady: Signal<Bool, NoError> {
|
||||||
return self._isReady.get()
|
return self._isReady.get()
|
||||||
@ -453,7 +460,47 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
self.addSubnode(self.contentNode)
|
self.addSubnode(self.contentNode)
|
||||||
self.addSubnode(self.patternImageNode)
|
self.addSubnode(self.patternImageNode)
|
||||||
|
|
||||||
//self.view.addSubview(self.bakedBackgroundView)
|
let animationList: [(String, CGPoint)] = [
|
||||||
|
("ptrnCAT_1162_1918", CGPoint(x: 1162 - 256, y: 1918 - 256)),
|
||||||
|
("ptrnDOG_0440_2284", CGPoint(x: 440 - 256, y: 2284 - 256)),
|
||||||
|
("ptrnGLOB_0438_1553", CGPoint(x: 438 - 256, y: 1553 - 256)),
|
||||||
|
("ptrnSLON_0906_1033", CGPoint(x: 906 - 256, y: 1033 - 256))
|
||||||
|
]
|
||||||
|
for (animation, relativePosition) in animationList {
|
||||||
|
let animationNode = AnimatedStickerNode()
|
||||||
|
animationNode.automaticallyLoadFirstFrame = true
|
||||||
|
animationNode.autoplay = true
|
||||||
|
self.inlineAnimationNodes.append((animationNode, relativePosition))
|
||||||
|
self.patternImageNode.addSubnode(animationNode)
|
||||||
|
animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animation), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layer.addSublayer(self.hierarchyTrackingLayer)
|
||||||
|
self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (animationNode, _) in strongSelf.inlineAnimationNodes {
|
||||||
|
animationNode.visibility = true
|
||||||
|
}
|
||||||
|
strongSelf.activateInlineAnimationTimer = SwiftSignalKit.Timer(timeout: 5.0, repeat: true, completion: {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.inlineAnimationNodes[Int.random(in: 0 ..< strongSelf.inlineAnimationNodes.count)].0.play()
|
||||||
|
}, queue: .mainQueue())
|
||||||
|
strongSelf.activateInlineAnimationTimer?.start()
|
||||||
|
}
|
||||||
|
self.hierarchyTrackingLayer.didExitHierarchy = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (animationNode, _) in strongSelf.inlineAnimationNodes {
|
||||||
|
animationNode.visibility = false
|
||||||
|
}
|
||||||
|
strongSelf.activateInlineAnimationTimer?.invalidate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -599,6 +646,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
self.patternImageNode.layer.compositingFilter = "softLightBlendMode"
|
self.patternImageNode.layer.compositingFilter = "softLightBlendMode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.patternImageNode.isHidden = false
|
self.patternImageNode.isHidden = false
|
||||||
let invertPattern = intensity < 0
|
let invertPattern = intensity < 0
|
||||||
if invertPattern {
|
if invertPattern {
|
||||||
@ -676,7 +724,23 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let generator = generator {
|
|
||||||
|
if var generator = generator {
|
||||||
|
generator = { arguments in
|
||||||
|
let scale = arguments.scale ?? UIScreenScale
|
||||||
|
let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true)
|
||||||
|
|
||||||
|
context.withFlippedContext { c in
|
||||||
|
if let path = getAppBundle().path(forResource: "PATTERN_static", ofType: "svg"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
|
||||||
|
if let image = drawSvgImage(data, CGSize(width: arguments.drawingSize.width * scale, height: arguments.drawingSize.height * scale), .clear, .black, false) {
|
||||||
|
c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: arguments.drawingSize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.validPatternImage = ValidPatternImage(wallpaper: wallpaper, generate: generator)
|
strongSelf.validPatternImage = ValidPatternImage(wallpaper: wallpaper, generate: generator)
|
||||||
strongSelf.validPatternGeneratedImage = nil
|
strongSelf.validPatternGeneratedImage = nil
|
||||||
if let size = strongSelf.validLayout {
|
if let size = strongSelf.validLayout {
|
||||||
@ -796,6 +860,13 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
|
|
||||||
self.loadPatternForSizeIfNeeded(size: size, transition: transition)
|
self.loadPatternForSizeIfNeeded(size: size, transition: transition)
|
||||||
|
|
||||||
|
for (animationNode, relativePosition) in self.inlineAnimationNodes {
|
||||||
|
let sizeNorm = CGSize(width: 1440, height: 2960)
|
||||||
|
let animationSize = CGSize(width: 512.0 / sizeNorm.width * size.width, height: 512.0 / sizeNorm.height * size.height)
|
||||||
|
animationNode.frame = CGRect(origin: CGPoint(x: relativePosition.x / sizeNorm.width * size.width, y: relativePosition.y / sizeNorm.height * size.height), size: animationSize)
|
||||||
|
animationNode.updateLayout(size: animationNode.frame.size)
|
||||||
|
}
|
||||||
|
|
||||||
if isFirstLayout && !self.frame.isEmpty {
|
if isFirstLayout && !self.frame.isEmpty {
|
||||||
self.updateScale()
|
self.updateScale()
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user