diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index aa5535ff96..7be7c589b5 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7665,3 +7665,5 @@ Sorry for the inconvenience."; "Premium.Limits.FoldersInfo" = "Organize your chats into 20 folders"; "Premium.Limits.ChatsPerFolderInfo" = "Add up to 200 chats into each of your folders"; "Premium.Limits.AccountsInfo" = "Connect 4 accounts with different mobile numbers"; + +"Bot.AccepRecurrentInfo" = "I accept [Terms of Service]() of **%1$@**"; diff --git a/submodules/BotPaymentsUI/BUILD b/submodules/BotPaymentsUI/BUILD index 30ad6de487..45cfed992b 100644 --- a/submodules/BotPaymentsUI/BUILD +++ b/submodules/BotPaymentsUI/BUILD @@ -25,6 +25,9 @@ swift_library( "//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/OverlayStatusController:OverlayStatusController", "//submodules/ShimmerEffect:ShimmerEffect", + "//submodules/CheckNode:CheckNode", + "//submodules/TextFormat:TextFormat", + "//submodules/Markdown:Markdown", ], visibility = [ "//visibility:public", diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift index b7aa1ea18d..1da8fd8434 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift @@ -6,8 +6,8 @@ import PassKit import ShimmerEffect enum BotCheckoutActionButtonState: Equatable { - case active(String) - case applePay + case active(text: String, isEnabled: Bool) + case applePay(isEnabled: Bool) case placeholder } @@ -17,6 +17,7 @@ final class BotCheckoutActionButton: HighlightableButtonNode { static var height: CGFloat = 52.0 private var activeFillColor: UIColor + private var inactiveFillColor: UIColor private var foregroundColor: UIColor private let activeBackgroundNode: ASImageNode @@ -28,17 +29,23 @@ final class BotCheckoutActionButton: HighlightableButtonNode { private var placeholderNode: ShimmerEffectNode? - init(activeFillColor: UIColor, foregroundColor: UIColor) { + private var activeImage: UIImage? + private var inactiveImage: UIImage? + + init(activeFillColor: UIColor, inactiveFillColor: UIColor, foregroundColor: UIColor) { self.activeFillColor = activeFillColor + self.inactiveFillColor = inactiveFillColor self.foregroundColor = foregroundColor - + let diameter: CGFloat = 20.0 + self.activeImage = generateStretchableFilledCircleImage(diameter: diameter, color: activeFillColor) + self.inactiveImage = generateStretchableFilledCircleImage(diameter: diameter, color: inactiveFillColor) self.activeBackgroundNode = ASImageNode() self.activeBackgroundNode.displaysAsynchronously = false self.activeBackgroundNode.displayWithoutProcessing = true self.activeBackgroundNode.isLayerBacked = true - self.activeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: diameter, color: activeFillColor) + self.activeBackgroundNode.image = self.activeImage self.labelNode = TextNode() self.labelNode.displaysAsynchronously = false @@ -75,7 +82,7 @@ final class BotCheckoutActionButton: HighlightableButtonNode { var labelSize = self.labelNode.bounds.size if let state = self.state { switch state { - case let .active(title): + case let .active(title, isEnabled): if let applePayButton = self.applePayButton { self.applePayButton = nil applePayButton.removeFromSuperview() @@ -85,12 +92,20 @@ final class BotCheckoutActionButton: HighlightableButtonNode { self.placeholderNode = nil placeholderNode.removeFromSupernode() } + + let image = isEnabled ? self.activeImage : self.inactiveImage + if let image = image, let currentImage = self.activeBackgroundNode.image, currentImage !== image { + self.activeBackgroundNode.image = image + self.activeBackgroundNode.layer.animate(from: currentImage.cgImage! as AnyObject, to: image.cgImage! as AnyObject, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2) + } else { + self.activeBackgroundNode.image = image + } let makeLayout = TextNode.asyncLayout(self.labelNode) let (labelLayout, labelApply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: self.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: size, alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let _ = labelApply() labelSize = labelLayout.size - case .applePay: + case let .applePay(isEnabled): if self.applePayButton == nil { if #available(iOSApplicationExtension 9.0, iOS 9.0, *) { let applePayButton: PKPaymentButton @@ -102,6 +117,7 @@ final class BotCheckoutActionButton: HighlightableButtonNode { applePayButton.addTarget(self, action: #selector(self.applePayButtonPressed), for: .touchUpInside) self.view.addSubview(applePayButton) self.applePayButton = applePayButton + applePayButton.isEnabled = isEnabled } } diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift index 2c8be1e5c4..39b3e3251e 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift @@ -15,13 +15,16 @@ public final class BotCheckoutController: ViewController { let form: BotPaymentForm let validatedFormInfo: BotPaymentValidatedFormInfo? + let botPeer: EnginePeer? private init( form: BotPaymentForm, - validatedFormInfo: BotPaymentValidatedFormInfo? + validatedFormInfo: BotPaymentValidatedFormInfo?, + botPeer: EnginePeer? ) { self.form = form self.validatedFormInfo = validatedFormInfo + self.botPeer = botPeer } public static func fetch(context: AccountContext, source: BotPaymentInvoiceSource) -> Signal { @@ -39,28 +42,35 @@ public final class BotCheckoutController: ViewController { return .generic } |> mapToSignal { paymentForm -> Signal in - if let current = paymentForm.savedInfo { - return context.engine.payments.validateBotPaymentForm(saveInfo: true, source: source, formInfo: current) - |> mapError { _ -> FetchError in - return .generic - } - |> map { result -> InputData in - return InputData( - form: paymentForm, - validatedFormInfo: result - ) - } - |> `catch` { _ -> Signal in + return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: paymentForm.paymentBotId)) + |> castError(FetchError.self) + |> mapToSignal { botPeer -> Signal in + if let current = paymentForm.savedInfo { + return context.engine.payments.validateBotPaymentForm(saveInfo: true, source: source, formInfo: current) + |> mapError { _ -> FetchError in + return .generic + } + |> map { result -> InputData in + return InputData( + form: paymentForm, + validatedFormInfo: result, + botPeer: botPeer + ) + } + |> `catch` { _ -> Signal in + return .single(InputData( + form: paymentForm, + validatedFormInfo: nil, + botPeer: botPeer + )) + } + } else { return .single(InputData( form: paymentForm, - validatedFormInfo: nil + validatedFormInfo: nil, + botPeer: botPeer )) } - } else { - return .single(InputData( - form: paymentForm, - validatedFormInfo: nil - )) } } } diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift index 9684509df6..8941aaccb8 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift @@ -17,6 +17,9 @@ import PasswordSetupUI import Stripe import LocalAuth import OverlayStatusController +import CheckNode +import TextFormat +import Markdown final class BotCheckoutControllerArguments { fileprivate let account: Account @@ -489,6 +492,160 @@ private func availablePaymentMethods(form: BotPaymentForm, current: BotCheckoutP return methods } +private final class RecurrentConfirmationNode: ASDisplayNode { + private let isAcceptedUpdated: (Bool) -> Void + private let openTerms: () -> Void + + private var checkNode: InteractiveCheckNode? + private let textNode: ImmediateTextNode + + init(isAcceptedUpdated: @escaping (Bool) -> Void, openTerms: @escaping () -> Void) { + self.isAcceptedUpdated = isAcceptedUpdated + self.openTerms = openTerms + + self.textNode = ImmediateTextNode() + self.textNode.maximumNumberOfLines = 0 + + super.init() + + self.textNode.highlightAttributeAction = { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + } + self.textNode.tapAttributeAction = { [weak self] attributes, _ in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + self?.openTerms() + } + } + + self.addSubnode(self.textNode) + + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + } + + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + guard let checkNode = self.checkNode else { + return + } + if case .ended = recognizer.state { + checkNode.setSelected(!checkNode.selected, animated: true) + checkNode.valueChanged?(checkNode.selected) + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.bounds.contains(point) { + return nil + } + + if let (_, attributes) = self.textNode.attributesAtPoint(self.view.convert(point, to: self.textNode.view)) { + if attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] == nil { + return self.view + } + } + + return super.hitTest(point, with: event) + } + + func update(presentationData: PresentationData, botName: String, width: CGFloat, sideInset: CGFloat) -> CGFloat { + let spacing: CGFloat = 16.0 + let topInset: CGFloat = 8.0 + + let checkNode: InteractiveCheckNode + if let current = self.checkNode { + checkNode = current + } else { + checkNode = InteractiveCheckNode(theme: CheckNodeTheme(backgroundColor: presentationData.theme.list.itemCheckColors.fillColor, strokeColor: presentationData.theme.list.itemCheckColors.foregroundColor, borderColor: presentationData.theme.list.itemCheckColors.strokeColor, overlayBorder: false, hasInset: false, hasShadow: false)) + checkNode.valueChanged = { [weak self] value in + self?.isAcceptedUpdated(value) + } + self.checkNode = checkNode + self.addSubnode(checkNode) + } + + let checkSize = CGSize(width: 22.0, height: 22.0) + + self.textNode.linkHighlightColor = presentationData.theme.list.itemAccentColor.withAlphaComponent(0.3) + + let attributedText = parseMarkdownIntoAttributedString( + presentationData.strings.Bot_AccepRecurrentInfo(botName).string, + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: presentationData.theme.list.freeTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: presentationData.theme.list.freeTextColor), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: presentationData.theme.list.itemAccentColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + ) + + self.textNode.attributedText = attributedText + let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0 - spacing - checkSize.width, height: .greatestFiniteMagnitude)) + + let height = textSize.height + 15.0 + + let contentWidth = checkSize.width + spacing + textSize.width + let contentOriginX = sideInset + floor((width - sideInset * 2.0 - contentWidth) / 2.0) + + checkNode.frame = CGRect(origin: CGPoint(x: contentOriginX, y: topInset + floor((height - checkSize.height) / 2.0)), size: checkSize) + + self.textNode.frame = CGRect(origin: CGPoint(x: contentOriginX + checkSize.width + spacing, y: topInset + floor((height - textSize.height) / 2.0)), size: textSize) + + return height + } +} + +private final class ActionButtonPanelNode: ASDisplayNode { + private(set) var isAccepted: Bool = false + var isAcceptedUpdated: (() -> Void)? + var openRecurrentTerms: (() -> Void)? + private var recurrentConfirmationNode: RecurrentConfirmationNode? + + func update(presentationData: PresentationData, layout: ContainerViewLayout, invoice: BotPaymentInvoice?, botName: String?) -> (CGFloat, CGFloat) { + let bottomPanelVerticalInset: CGFloat = 16.0 + + var height = max(layout.intrinsicInsets.bottom, layout.inputHeight ?? 0.0) + bottomPanelVerticalInset * 2.0 + BotCheckoutActionButton.height + var actionButtonOffset: CGFloat = bottomPanelVerticalInset + + if let invoice = invoice, let recurrentInfo = invoice.recurrentInfo, let botName = botName { + let recurrentConfirmationNode: RecurrentConfirmationNode + if let current = self.recurrentConfirmationNode { + recurrentConfirmationNode = current + } else { + recurrentConfirmationNode = RecurrentConfirmationNode(isAcceptedUpdated: { [weak self] value in + guard let strongSelf = self else { + return + } + strongSelf.isAccepted = value + strongSelf.isAcceptedUpdated?() + }, openTerms: { [weak self] in + self?.openRecurrentTerms?() + }) + self.recurrentConfirmationNode = recurrentConfirmationNode + self.addSubnode(recurrentConfirmationNode) + } + + let _ = recurrentInfo + + let recurrentConfirmationHeight = recurrentConfirmationNode.update(presentationData: presentationData, botName: botName, width: layout.size.width, sideInset: layout.safeInsets.left + 33.0) + recurrentConfirmationNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: recurrentConfirmationHeight)) + + actionButtonOffset += recurrentConfirmationHeight + } else if let recurrentConfirmationNode = self.recurrentConfirmationNode { + self.recurrentConfirmationNode = nil + + recurrentConfirmationNode.removeFromSupernode() + } + + height += actionButtonOffset - bottomPanelVerticalInset + + return (height, actionButtonOffset) + } +} + final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthorizationViewControllerDelegate { private weak var controller: BotCheckoutController? private let navigationBar: NavigationBar @@ -509,6 +666,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz private let paymentFormAndInfo = Promise<(BotPaymentForm, BotPaymentRequestedInfo, BotPaymentValidatedFormInfo?, String?, BotCheckoutPaymentMethod?, Int64?)?>(nil) private var paymentFormValue: BotPaymentForm? + private var botPeerValue: EnginePeer? private var currentFormInfo: BotPaymentRequestedInfo? private var currentValidatedFormInfo: BotPaymentValidatedFormInfo? private var currentShippingOptionId: String? @@ -516,7 +674,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz private var currentTipAmount: Int64? private var formRequestDisposable: Disposable? - private let actionButtonPanelNode: ASDisplayNode + private let actionButtonPanelNode: ActionButtonPanelNode private let actionButtonPanelSeparator: ASDisplayNode private let actionButton: BotCheckoutActionButton private let inProgressDimNode: ASDisplayNode @@ -585,13 +743,11 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz return (ItemListPresentationData(presentationData), (nodeState, arguments)) } - self.actionButtonPanelNode = ASDisplayNode() - self.actionButtonPanelNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.opaqueBackgroundColor + self.actionButtonPanelNode = ActionButtonPanelNode() self.actionButtonPanelSeparator = ASDisplayNode() - self.actionButtonPanelSeparator.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor - self.actionButton = BotCheckoutActionButton(activeFillColor: self.presentationData.theme.list.itemAccentColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor) + self.actionButton = BotCheckoutActionButton(activeFillColor: self.presentationData.theme.list.itemAccentColor, inactiveFillColor: self.presentationData.theme.list.itemDisabledTextColor.mixedWith(self.presentationData.theme.list.blocksBackgroundColor, alpha: 0.7), foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor) self.actionButton.setState(.placeholder) self.inProgressDimNode = ASDisplayNode() @@ -603,6 +759,22 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz self.arguments = arguments + self.actionButtonPanelNode.isAcceptedUpdated = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.updateActionButton() + } + + self.actionButtonPanelNode.openRecurrentTerms = { [weak self] in + guard let strongSelf = self, let paymentForm = strongSelf.paymentFormValue, let recurrentInfo = paymentForm.invoice.recurrentInfo else { + return + } + strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: recurrentInfo.termsUrl, forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: { + self?.view.endEditing(true) + }) + } + openInfoImpl = { [weak self] focus in if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let currentFormInfo = strongSelf.currentFormInfo { strongSelf.controller?.view.endEditing(true) @@ -924,6 +1096,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz savedInfo = BotPaymentRequestedInfo(name: nil, phone: nil, email: nil, shippingAddress: nil) } strongSelf.paymentFormValue = formAndValidatedInfo.form + strongSelf.botPeerValue = formAndValidatedInfo.botPeer strongSelf.currentFormInfo = savedInfo strongSelf.currentValidatedFormInfo = formAndValidatedInfo.validatedFormInfo if let savedCredentials = formAndValidatedInfo.form.savedCredentials { @@ -959,6 +1132,38 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz } } }) + + self.actionButtonPanelSeparator.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor + self.actionButtonPanelNode.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor + self.visibleBottomContentOffsetChanged = { [weak self] offset in + guard let strongSelf = self else { + return + } + + let panelColor: UIColor + let separatorColor: UIColor + switch offset { + case let .known(value): + if value > 10.0 { + panelColor = strongSelf.presentationData.theme.rootController.navigationBar.opaqueBackgroundColor + separatorColor = strongSelf.presentationData.theme.rootController.navigationBar.separatorColor + } else { + panelColor = .clear + separatorColor = .clear + } + default: + panelColor = strongSelf.presentationData.theme.rootController.navigationBar.opaqueBackgroundColor + separatorColor = strongSelf.presentationData.theme.rootController.navigationBar.separatorColor + } + + let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear) + if strongSelf.actionButtonPanelNode.backgroundColor != panelColor { + transition.updateBackgroundColor(node: strongSelf.actionButtonPanelNode, color: panelColor) + } + if strongSelf.actionButtonPanelSeparator.backgroundColor != separatorColor { + transition.updateBackgroundColor(node: strongSelf.actionButtonPanelSeparator, color: separatorColor) + } + } } deinit { @@ -971,22 +1176,34 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz private func updateActionButton() { let totalAmount = currentTotalPrice(paymentForm: self.paymentFormValue, validatedFormInfo: self.currentValidatedFormInfo, currentShippingOptionId: self.currentShippingOptionId, currentTip: self.currentTipAmount) let payString: String + var isButtonEnabled = true if let paymentForm = self.paymentFormValue, totalAmount > 0 { payString = self.presentationData.strings.Checkout_PayPrice(formatCurrencyAmount(totalAmount, currency: paymentForm.invoice.currency)).string + + if let _ = paymentForm.invoice.recurrentInfo { + if !self.actionButtonPanelNode.isAccepted { + isButtonEnabled = false + } + } } else { payString = self.presentationData.strings.CheckoutInfo_Pay } + + self.actionButton.isEnabled = isButtonEnabled + if let currentPaymentMethod = self.currentPaymentMethod { switch currentPaymentMethod { case .applePay: - self.actionButton.setState(.applePay) + self.actionButton.setState(.applePay(isEnabled: isButtonEnabled)) default: - self.actionButton.setState(.active(payString)) + self.actionButton.setState(.active(text: payString, isEnabled: isButtonEnabled)) } } else { - self.actionButton.setState(.active(payString)) + self.actionButton.setState(.active(text: payString, isEnabled: isButtonEnabled)) } self.actionButtonPanelNode.isHidden = false + + self.controller?.requestLayout(transition: .immediate) } private func updateIsInProgress(_ value: Bool) { @@ -1006,13 +1223,18 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz var updatedInsets = layout.intrinsicInsets let bottomPanelHorizontalInset: CGFloat = 16.0 - let bottomPanelVerticalInset: CGFloat = 16.0 - let bottomPanelHeight = max(updatedInsets.bottom, layout.inputHeight ?? 0.0) + bottomPanelVerticalInset * 2.0 + BotCheckoutActionButton.height + + var botName: String? + if let botPeer = self.botPeerValue { + botName = botPeer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) + } + + let (bottomPanelHeight, actionButtonOffset) = self.actionButtonPanelNode.update(presentationData: self.presentationData, layout: layout, invoice: self.paymentFormValue?.invoice, botName: botName) transition.updateFrame(node: self.actionButtonPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: layout.size.width, height: bottomPanelHeight))) transition.updateFrame(node: self.actionButtonPanelSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - let actionButtonFrame = CGRect(origin: CGPoint(x: bottomPanelHorizontalInset, y: bottomPanelVerticalInset), size: CGSize(width: layout.size.width - bottomPanelHorizontalInset * 2.0, height: BotCheckoutActionButton.height)) + let actionButtonFrame = CGRect(origin: CGPoint(x: bottomPanelHorizontalInset, y: actionButtonOffset), size: CGSize(width: layout.size.width - bottomPanelHorizontalInset * 2.0, height: BotCheckoutActionButton.height)) transition.updateFrame(node: self.actionButton, frame: actionButtonFrame) self.actionButton.updateLayout(absoluteRect: actionButtonFrame.offsetBy(dx: self.actionButtonPanelNode.frame.minX, dy: self.actionButtonPanelNode.frame.minY), containerSize: layout.size, transition: transition) diff --git a/submodules/BotPaymentsUI/Sources/BotReceiptControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotReceiptControllerNode.swift index fb7524d351..137d2fac26 100644 --- a/submodules/BotPaymentsUI/Sources/BotReceiptControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotReceiptControllerNode.swift @@ -305,8 +305,8 @@ final class BotReceiptControllerNode: ItemListControllerNode { self.actionButtonPanelSeparator = ASDisplayNode() self.actionButtonPanelSeparator.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor - self.actionButton = BotCheckoutActionButton(activeFillColor: self.presentationData.theme.list.itemAccentColor, foregroundColor: self.presentationData.theme.list.plainBackgroundColor) - self.actionButton.setState(.active(self.presentationData.strings.Common_Done)) + self.actionButton = BotCheckoutActionButton(activeFillColor: self.presentationData.theme.list.itemAccentColor, inactiveFillColor: self.presentationData.theme.list.itemDisabledTextColor, foregroundColor: self.presentationData.theme.list.plainBackgroundColor) + self.actionButton.setState(.active(text: self.presentationData.strings.Common_Done, isEnabled: true)) super.init(controller: controller, navigationBar: navigationBar, state: signal) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index 3f71144f59..06ff9acc56 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -44,12 +44,17 @@ public struct BotPaymentInvoice : Equatable { public var max: Int64 public var suggested: [Int64] } + + public struct RecurrentInfo: Equatable { + public var termsUrl: String + } public let isTest: Bool public let requestedFields: BotPaymentInvoiceFields public let currency: String public let prices: [BotPaymentPrice] public let tip: Tip? + public let recurrentInfo: RecurrentInfo? } public struct BotPaymentNativeProvider : Equatable { @@ -124,39 +129,43 @@ public enum BotPaymentFormRequestError { extension BotPaymentInvoice { init(apiInvoice: Api.Invoice) { switch apiInvoice { - case let .invoice(flags, currency, prices, maxTipAmount, suggestedTipAmounts, _): - var fields = BotPaymentInvoiceFields() - if (flags & (1 << 1)) != 0 { - fields.insert(.name) + case let .invoice(flags, currency, prices, maxTipAmount, suggestedTipAmounts, recurrentTermsUrl): + var fields = BotPaymentInvoiceFields() + if (flags & (1 << 1)) != 0 { + fields.insert(.name) + } + if (flags & (1 << 2)) != 0 { + fields.insert(.phone) + } + if (flags & (1 << 3)) != 0 { + fields.insert(.email) + } + if (flags & (1 << 4)) != 0 { + fields.insert(.shippingAddress) + } + if (flags & (1 << 5)) != 0 { + fields.insert(.flexibleShipping) + } + if (flags & (1 << 6)) != 0 { + fields.insert(.phoneAvailableToProvider) + } + if (flags & (1 << 7)) != 0 { + fields.insert(.emailAvailableToProvider) + } + var recurrentInfo: BotPaymentInvoice.RecurrentInfo? + if let recurrentTermsUrl = recurrentTermsUrl { + recurrentInfo = BotPaymentInvoice.RecurrentInfo(termsUrl: recurrentTermsUrl) + } + var parsedTip: BotPaymentInvoice.Tip? + if let maxTipAmount = maxTipAmount, let suggestedTipAmounts = suggestedTipAmounts { + parsedTip = BotPaymentInvoice.Tip(max: maxTipAmount, suggested: suggestedTipAmounts) + } + self.init(isTest: (flags & (1 << 0)) != 0, requestedFields: fields, currency: currency, prices: prices.map { + switch $0 { + case let .labeledPrice(label, amount): + return BotPaymentPrice(label: label, amount: amount) } - if (flags & (1 << 2)) != 0 { - fields.insert(.phone) - } - if (flags & (1 << 3)) != 0 { - fields.insert(.email) - } - if (flags & (1 << 4)) != 0 { - fields.insert(.shippingAddress) - } - if (flags & (1 << 5)) != 0 { - fields.insert(.flexibleShipping) - } - if (flags & (1 << 6)) != 0 { - fields.insert(.phoneAvailableToProvider) - } - if (flags & (1 << 7)) != 0 { - fields.insert(.emailAvailableToProvider) - } - var parsedTip: BotPaymentInvoice.Tip? - if let maxTipAmount = maxTipAmount, let suggestedTipAmounts = suggestedTipAmounts { - parsedTip = BotPaymentInvoice.Tip(max: maxTipAmount, suggested: suggestedTipAmounts) - } - self.init(isTest: (flags & (1 << 0)) != 0, requestedFields: fields, currency: currency, prices: prices.map { - switch $0 { - case let .labeledPrice(label, amount): - return BotPaymentPrice(label: label, amount: amount) - } - }, tip: parsedTip) + }, tip: parsedTip, recurrentInfo: recurrentInfo) } } }