diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index b6342426a5..80fb5a674b 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12383,6 +12383,7 @@ Sorry for the inconvenience."; "Stars.PaidContent.AmountTitle" = "ENTER UNLOCK COST"; "Stars.PaidContent.AmountPlaceholder" = "Stars to Unlock"; "Stars.PaidContent.AmountInfo" = "Users will have to transfer this amount of Stars to your channel in order to view this media.\n[More about Stars >]()"; +"Stars.PaidContent.AmountInfo_URL" = "https://telegram.org"; "Stars.PaidContent.Create" = "Make This Media Paid"; "MediaEditor.AddLink" = "LINK"; @@ -12420,7 +12421,7 @@ Sorry for the inconvenience."; "Monetization.BalanceStarsWithdraw" = "Withdraw via Fragment"; "Monetization.BalanceStarsWithdrawShort" = "Withdraw"; "Monetization.BalanceStarsBuyAds" = "Buy Ads"; -"Monetization.Balance.StarsInfo" = "You can withdraw Stars using Fragment, or use Stars to advertise your channel. [Learn More >]()"; +"Monetization.Balance.StarsInfo" = "You can collect rewards for Stars using Fragment, or use Stars to advertise your channel. [Learn More >]()"; "Monetization.Balance.StarsInfo_URL" = "https://telegram.org"; "Monetization.StarsProceeds.Title" = "Rewards Overview"; diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ExtendedMedia.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ExtendedMedia.swift new file mode 100644 index 0000000000..f45bf90806 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ExtendedMedia.swift @@ -0,0 +1,40 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit + +private func _internal_updateExtendedMediaById(account: Account, peerId: EnginePeer.Id, messageIds: [EngineMessage.Id]) -> Signal { + return account.postbox.transaction { transaction -> Peer? in + if let peer = transaction.getPeer(peerId) { + return peer + } else { + return nil + } + } + |> mapToSignal { peer -> Signal in + guard let peer = peer, let inputPeer = apiInputPeer(peer) else { + return .complete() + } + return account.network.request(Api.functions.messages.getExtendedMedia(peer: inputPeer, id: messageIds.map { $0.id })) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { updates -> Signal in + if let updates = updates { + account.stateManager.addUpdates(updates) + } + return .complete() + } + } +} + +func _internal_updateExtendedMedia(account: Account, messageIds: [EngineMessage.Id]) -> Signal { + var signals: [Signal] = [] + for (peerId, messageIds) in messagesIdsGroupedByPeerId(messageIds) { + signals.append(_internal_updateExtendedMediaById(account: account, peerId: peerId, messageIds: messageIds)) + } + return combineLatest(signals) + |> ignoreValues +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index d129b4709c..b8ddc75bde 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1431,6 +1431,10 @@ public extension TelegramEngine { return _internal_reportAdMessage(account: self.account, peerId: peerId, opaqueId: opaqueId, option: option) } + public func updateExtendedMedia(messageIds: [EngineMessage.Id]) -> Signal { + return _internal_updateExtendedMedia(account: self.account, messageIds: messageIds) + } + public func getAllLocalChannels(count: Int) -> Signal<[EnginePeer.Id], NoError> { return self.account.postbox.transaction { transaction -> [EnginePeer.Id] in var result: [EnginePeer.Id] = [] diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 4e3cb35706..43b9709bc9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -756,6 +756,8 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot return .fail(.paymentFailed) } else if error.errorDescription == "INVOICE_ALREADY_PAID" { return .fail(.alreadyPaid) + } else if error.errorDescription == "MEDIA_ALREADY_PAID" { + return .fail(.alreadyPaid) } return .fail(.generic) } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index aae8cef5df..a6e731d185 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -68,6 +68,7 @@ private final class SheetContent: CombinedComponent { private let context: AccountContext private let starsContext: StarsContext private let source: BotPaymentInvoiceSource + private let extendedMedia: [TelegramExtendedMedia] private let invoice: TelegramMediaInvoice private(set) var botPeer: EnginePeer? @@ -92,12 +93,14 @@ private final class SheetContent: CombinedComponent { context: AccountContext, starsContext: StarsContext, source: BotPaymentInvoiceSource, + extendedMedia: [TelegramExtendedMedia], invoice: TelegramMediaInvoice, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError> ) { self.context = context self.starsContext = starsContext self.source = source + self.extendedMedia = extendedMedia self.invoice = invoice super.init() @@ -150,7 +153,7 @@ private final class SheetContent: CombinedComponent { self.optionsDisposable?.dispose() } - func buy(requestTopUp: @escaping (@escaping () -> Void) -> Void, completion: @escaping () -> Void) { + func buy(requestTopUp: @escaping (@escaping () -> Void) -> Void, completion: @escaping (Bool) -> Void) { guard let form, let balance else { return } @@ -164,7 +167,20 @@ private final class SheetContent: CombinedComponent { let _ = (self.context.engine.payments.sendStarsPaymentForm(formId: form.id, source: self.source) |> deliverOnMainQueue).start(next: { _ in - completion() + completion(true) + }, error: { [weak self] error in + guard let self else { + return + } + switch error { + case .alreadyPaid: + if !self.extendedMedia.isEmpty, case let .message(messageId) = self.source { + let _ = self.context.engine.messages.updateExtendedMedia(messageIds: [messageId]).startStandalone() + } + default: + break + } + completion(false) }) } @@ -209,7 +225,7 @@ private final class SheetContent: CombinedComponent { } func makeState() -> State { - return State(context: self.context, starsContext: self.starsContext, source: self.source, invoice: self.invoice, inputData: self.inputData) + return State(context: self.context, starsContext: self.starsContext, source: self.source, extendedMedia: self.extendedMedia, invoice: self.invoice, inputData: self.inputData) } static var body: Body { @@ -481,37 +497,38 @@ private final class SheetContent: CombinedComponent { let alertController = textAlertController(context: accountContext, title: nil, text: presentationData.strings.Stars_Transfer_Unavailable, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) controller?.present(alertController, in: .window(.root)) } - }, completion: { [weak controller] in - let presentationData = accountContext.sharedContext.currentPresentationData.with { $0 } - - let text: String - if let _ = component.invoice.extendedMedia { - text = presentationData.strings.Stars_Transfer_UnlockedText( presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string - } else { - text = presentationData.strings.Stars_Transfer_PurchasedText(invoice.title, botTitle, presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string - } - - if let navigationController = controller?.navigationController { - Queue.mainQueue().after(0.5) { - if let lastController = navigationController.viewControllers.last as? ViewController { - let resultController = UndoOverlayController( - presentationData: presentationData, - content: .image( - image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!, - title: presentationData.strings.Stars_Transfer_PurchasedTitle, - text: text, - round: false, - undoText: nil - ), - elevatedLayout: lastController is ChatController, - action: { _ in return true} - ) - lastController.present(resultController, in: .window(.root)) + }, completion: { [weak controller] success in + if success { + let presentationData = accountContext.sharedContext.currentPresentationData.with { $0 } + let text: String + if let _ = component.invoice.extendedMedia { + text = presentationData.strings.Stars_Transfer_UnlockedText( presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string + } else { + text = presentationData.strings.Stars_Transfer_PurchasedText(invoice.title, botTitle, presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string + } + + if let navigationController = controller?.navigationController { + Queue.mainQueue().after(0.5) { + if let lastController = navigationController.viewControllers.last as? ViewController { + let resultController = UndoOverlayController( + presentationData: presentationData, + content: .image( + image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!, + title: presentationData.strings.Stars_Transfer_PurchasedTitle, + text: text, + round: false, + undoText: nil + ), + elevatedLayout: lastController is ChatController, + action: { _ in return true} + ) + lastController.present(resultController, in: .window(.root)) + } } } } - controller?.complete(paid: true) + controller?.complete(paid: success) controller?.dismissAnimated() starsContext.load(force: true) diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index cc283ed472..7ff4843dfd 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -67,6 +67,7 @@ private final class SheetContent: CombinedComponent { let state = context.state let theme = environment.theme.withModalBlocksBackground() + let strings = environment.strings let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let sideInset: CGFloat = 16.0 @@ -200,7 +201,18 @@ private final class SheetContent: CombinedComponent { } amountFooter = AnyComponent(MultilineTextComponent( text: .plain(amountInfoString), - maximumNumberOfLines: 0 + maximumNumberOfLines: 0, + highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { attributes, _ in + component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_PaidContent_AmountInfo_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) + } )) } else { amountFooter = nil