Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2021-04-11 15:16:56 +03:00
commit 7382d413a6
11 changed files with 3892 additions and 3763 deletions

View File

@ -6444,3 +6444,5 @@ Sorry for the inconvenience.";
"Checkout.OptionalTipItemPlaceholder" = "Enter Custom"; "Checkout.OptionalTipItemPlaceholder" = "Enter Custom";
"VoiceChat.ReminderNotify" = "We will notify you when it starts."; "VoiceChat.ReminderNotify" = "We will notify you when it starts.";
"Checkout.SuccessfulTooltip" = "You paid %1$@ for %2$@.";

View File

@ -80,6 +80,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: MessageId private let messageId: MessageId
private let completed: (String, MessageId?) -> Void
private var presentationData: PresentationData private var presentationData: PresentationData
@ -87,11 +88,12 @@ public final class BotCheckoutController: ViewController {
private let inputData: Promise<BotCheckoutController.InputData?> private let inputData: Promise<BotCheckoutController.InputData?>
public init(context: AccountContext, invoice: TelegramMediaInvoice, messageId: MessageId, inputData: Promise<BotCheckoutController.InputData?>) { public init(context: AccountContext, invoice: TelegramMediaInvoice, messageId: MessageId, inputData: Promise<BotCheckoutController.InputData?>, completed: @escaping (String, MessageId?) -> Void) {
self.context = context self.context = context
self.invoice = invoice self.invoice = invoice
self.messageId = messageId self.messageId = messageId
self.inputData = inputData self.inputData = inputData
self.completed = completed
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -121,7 +123,7 @@ public final class BotCheckoutController: ViewController {
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()
}) }, completed: self.completed)
//displayNode.enableInteractiveDismiss = true //displayNode.enableInteractiveDismiss = true

View File

@ -506,6 +506,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
private let messageId: MessageId private let messageId: MessageId
private let present: (ViewController, Any?) -> Void private let present: (ViewController, Any?) -> Void
private let dismissAnimated: () -> Void private let dismissAnimated: () -> Void
private let completed: (String, MessageId?) -> Void
private var stateValue = BotCheckoutControllerState() private var stateValue = BotCheckoutControllerState()
private let state = ValuePromise(BotCheckoutControllerState(), ignoreRepeated: true) private let state = ValuePromise(BotCheckoutControllerState(), ignoreRepeated: true)
@ -536,12 +537,13 @@ 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, updateNavigationOffset: @escaping (CGFloat) -> Void, context: AccountContext, invoice: TelegramMediaInvoice, messageId: MessageId, inputData: Promise<BotCheckoutController.InputData?>, present: @escaping (ViewController, Any?) -> Void, dismissAnimated: @escaping () -> Void) { init(controller: BotCheckoutController?, navigationBar: NavigationBar, updateNavigationOffset: @escaping (CGFloat) -> Void, context: AccountContext, invoice: TelegramMediaInvoice, messageId: MessageId, inputData: Promise<BotCheckoutController.InputData?>, present: @escaping (ViewController, Any?) -> Void, dismissAnimated: @escaping () -> Void, completed: @escaping (String, MessageId?) -> Void) {
self.controller = controller self.controller = controller
self.context = context self.context = context
self.messageId = messageId self.messageId = messageId
self.present = present self.present = present
self.dismissAnimated = dismissAnimated self.dismissAnimated = dismissAnimated
self.completed = completed
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -1213,6 +1215,9 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
tipAmount = 0 tipAmount = 0
} }
let totalAmount = currentTotalPrice(paymentForm: paymentForm, validatedFormInfo: self.currentValidatedFormInfo, currentShippingOptionId: self.currentShippingOptionId, currentTip: self.currentTipAmount)
let currencyValue = formatCurrencyAmount(totalAmount, currency: paymentForm.invoice.currency)
self.payDisposable.set((sendBotPaymentForm(account: self.context.account, 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((sendBotPaymentForm(account: self.context.account, messageId: self.messageId, 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
@ -1228,18 +1233,31 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
applePayController.presentingViewController?.dismiss(animated: true, completion: nil) applePayController.presentingViewController?.dismiss(animated: true, completion: nil)
} }
switch result { let proceedWithCompletion: (Bool, MessageId?) -> Void = { success, receiptMessageId in
case .done: guard let strongSelf = self else {
return
}
if success {
strongSelf.dismissAnimated() strongSelf.dismissAnimated()
strongSelf.completed(currencyValue, receiptMessageId)
} else {
strongSelf.dismissAnimated()
}
}
switch result {
case let .done(receiptMessageId):
proceedWithCompletion(true, receiptMessageId)
case let .externalVerificationRequired(url): case let .externalVerificationRequired(url):
strongSelf.updateActionButton() strongSelf.updateActionButton()
var dismissImpl: (() -> Void)? var dismissImpl: ((Bool) -> Void)?
let controller = BotCheckoutWebInteractionController(context: strongSelf.context, url: url, intent: .externalVerification({ _ in let controller = BotCheckoutWebInteractionController(context: strongSelf.context, url: url, intent: .externalVerification({ success in
dismissImpl?() dismissImpl?(success)
})) }))
dismissImpl = { [weak controller] in dismissImpl = { [weak controller] success in
controller?.dismiss() controller?.dismiss()
self?.dismissAnimated() proceedWithCompletion(success, nil)
} }
strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} }

View File

@ -986,6 +986,11 @@ public final class Transaction {
self.postbox?.scanMessages(peerId: peerId, namespace: namespace, tag: tag, f) self.postbox?.scanMessages(peerId: peerId, namespace: namespace, tag: tag, f)
} }
public func scanTopMessages(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (Message) -> Bool) {
assert(!self.disposed)
self.postbox?.scanTopMessages(peerId: peerId, namespace: namespace, limit: limit, f)
}
public func scanMessageAttributes(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (MessageId, [MessageAttribute]) -> Bool) { public func scanMessageAttributes(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (MessageId, [MessageAttribute]) -> Bool) {
self.postbox?.scanMessageAttributes(peerId: peerId, namespace: namespace, limit: limit, f) self.postbox?.scanMessageAttributes(peerId: peerId, namespace: namespace, limit: limit, f)
} }
@ -3412,6 +3417,26 @@ public final class Postbox {
} }
} }
fileprivate func scanTopMessages(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (Message) -> Bool) {
let lowerBound = MessageIndex.lowerBound(peerId: peerId, namespace: namespace)
var index = MessageIndex.upperBound(peerId: peerId, namespace: namespace)
var remainingLimit = limit
while remainingLimit > 0 {
let messages = self.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, threadId: nil, from: index, includeFrom: false, to: lowerBound, limit: 10)
remainingLimit -= 10
for message in messages {
if !f(self.renderIntermediateMessage(message)) {
break
}
}
if let last = messages.last {
index = last.index
} else {
break
}
}
}
fileprivate func scanMessageAttributes(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (MessageId, [MessageAttribute]) -> Bool) { fileprivate func scanMessageAttributes(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (MessageId, [MessageAttribute]) -> Bool) {
var remainingLimit = limit var remainingLimit = limit
var index = MessageIndex.upperBound(peerId: peerId, namespace: namespace) var index = MessageIndex.upperBound(peerId: peerId, namespace: namespace)

View File

@ -342,7 +342,7 @@ public enum SendBotPaymentFormError {
} }
public enum SendBotPaymentResult { public enum SendBotPaymentResult {
case done case done(receiptMessageId: MessageId?)
case externalVerificationRequired(url: String) case externalVerificationRequired(url: String)
} }
@ -384,7 +384,27 @@ public func sendBotPaymentForm(account: Account, messageId: MessageId, formId: I
switch result { switch result {
case let .paymentResult(updates): case let .paymentResult(updates):
account.stateManager.addUpdates(updates) account.stateManager.addUpdates(updates)
return .done var receiptMessageId: MessageId?
for apiMessage in updates.messages {
if let message = StoreMessage(apiMessage: apiMessage) {
for media in message.media {
if let action = media as? TelegramMediaAction {
if case .paymentSent = action.action {
for attribute in message.attributes {
if let reply = attribute as? ReplyMessageAttribute {
if reply.messageId == messageId {
if case let .Id(id) = message.id {
receiptMessageId = id
}
}
}
}
}
}
}
}
}
return .done(receiptMessageId: receiptMessageId)
case let .paymentVerificationNeeded(url): case let .paymentVerificationNeeded(url):
return .externalVerificationRequired(url: url) return .externalVerificationRequired(url: url)
} }
@ -402,13 +422,35 @@ public func sendBotPaymentForm(account: Account, messageId: MessageId, formId: I
} }
} }
public struct BotPaymentReceipt { public struct BotPaymentReceipt : Equatable {
public let invoice: BotPaymentInvoice public let invoice: BotPaymentInvoice
public let info: BotPaymentRequestedInfo? public let info: BotPaymentRequestedInfo?
public let shippingOption: BotPaymentShippingOption? public let shippingOption: BotPaymentShippingOption?
public let credentialsTitle: String public let credentialsTitle: String
public let invoiceMedia: TelegramMediaInvoice public let invoiceMedia: TelegramMediaInvoice
public let tipAmount: Int64? public let tipAmount: Int64?
public static func ==(lhs: BotPaymentReceipt, rhs: BotPaymentReceipt) -> Bool {
if lhs.invoice != rhs.invoice {
return false
}
if lhs.info != rhs.info {
return false
}
if lhs.shippingOption != rhs.shippingOption {
return false
}
if lhs.credentialsTitle != rhs.credentialsTitle {
return false
}
if !lhs.invoiceMedia.isEqual(to: rhs.invoiceMedia) {
return false
}
if lhs.tipAmount != rhs.tipAmount {
return false
}
return true
}
} }
public enum RequestBotPaymentReceiptError { public enum RequestBotPaymentReceiptError {

File diff suppressed because one or more lines are too long

View File

@ -1879,7 +1879,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|> `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), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) strongSelf.present(BotCheckoutController(context: strongSelf.context, invoice: invoice, messageId: messageId, inputData: inputData, completed: { currencyValue, receiptMessageId in
guard let strongSelf = self else {
return
}
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)
}), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} }
} }
} }

View File

@ -38,6 +38,7 @@ public enum UndoOverlayContent {
case sticker(account: Account, file: TelegramMediaFile, text: String) case sticker(account: Account, file: TelegramMediaFile, text: String)
case copy(text: String) case copy(text: String)
case mediaSaved(text: String) case mediaSaved(text: String)
case paymentSent(currencyValue: String, itemTitle: String)
} }
public enum UndoOverlayAction { public enum UndoOverlayAction {

View File

@ -265,6 +265,23 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
string.addAttribute(.font, value: Font.regular(14.0), range: range) string.addAttribute(.font, value: Font.regular(14.0), range: range)
} }
self.textNode.attributedText = string
displayUndo = false
self.originalRemainingSeconds = 5
case let .paymentSent(currencyValue, itemTitle):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_payment", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
let (rawString, attributes) = presentationData.strings.Checkout_SuccessfulTooltip(currencyValue, itemTitle)
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: rawString, font: Font.regular(14.0), textColor: .white))
for (_, range) in attributes {
string.addAttribute(.font, value: Font.semibold(14.0), range: range)
}
self.textNode.attributedText = string self.textNode.attributedText = string
displayUndo = false displayUndo = false
self.originalRemainingSeconds = 5 self.originalRemainingSeconds = 5
@ -738,7 +755,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
switch content { switch content {
case .removedChat: case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode) self.panelWrapperNode.addSubnode(self.timerTextNode)
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .sticker, .copy, .mediaSaved: case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .sticker, .copy, .mediaSaved, .paymentSent:
break break
case .dice: case .dice:
self.panelWrapperNode.clipsToBounds = true self.panelWrapperNode.clipsToBounds = true
@ -864,6 +881,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let factor: CGFloat = 0.07 let factor: CGFloat = 0.07
verticalOffset = -3.0 verticalOffset = -3.0
preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor)) preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
} else if case .paymentSent = self.content {
let factor: CGFloat = 0.08
preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
} else { } else {
preferredSize = iconSize preferredSize = iconSize
} }