From e47b5a89ef875bf92f3c72a2bd1e5797354c88f2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 24 Jun 2024 04:06:57 +0400 Subject: [PATCH] Paid media improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 20 ++ .../Sources/ChatController.swift | 1 + .../Sources/FileMediaResourceStatus.swift | 4 +- .../PremiumUI/Sources/PremiumDemoScreen.swift | 5 +- .../Sources/StarsTransactionItem.swift | 4 +- .../Network/FetchedMediaResource.swift | 8 +- .../PendingMessageUploadedContent.swift | 124 +++++++++-- .../PendingMessages/RequestEditMessage.swift | 20 +- .../StandaloneSendMessage.swift | 2 +- .../Sources/State/PendingMessageManager.swift | 29 ++- .../TelegramEngine/Messages/Stories.swift | 4 +- .../Sources/ChatMessageBubbleItemNode.swift | 4 + .../ChatMessageInstantVideoItemNode.swift | 2 +- ...atMessageInteractiveInstantVideoNode.swift | 2 +- .../ChatMessageInteractiveMediaNode.swift | 12 +- .../Sources/ChatMessageItemView.swift | 6 +- .../ChatMessageMediaBubbleContentNode.swift | 3 +- .../ChatMessageStarsMediaInfoNode.swift | 5 +- .../Sources/ChatMessageUnlockMediaNode.swift | 198 ++++++++++++++++++ .../ChatRecentActionsControllerNode.swift | 2 +- .../ChatSendAudioMessageContextPreview.swift | 2 +- .../Sources/ChatControllerInteraction.swift | 4 +- .../Sources/PeerInfoScreen.swift | 2 +- .../Sources/StarsAvatarComponent.swift | 12 +- .../Sources/StarsImageComponent.swift | 11 +- .../Sources/StarsTransactionScreen.swift | 35 +--- .../StarsTransactionsListPanelComponent.swift | 7 +- .../Sources/StarsTransferScreen.swift | 25 +-- ...ChatMessageDisplaySendMessageOptions.swift | 2 + .../TelegramUI/Sources/ChatController.swift | 18 +- .../ChatPinnedMessageTitlePanelNode.swift | 2 +- .../OverlayAudioPlayerControllerNode.swift | 2 +- .../Sources/SharedAccountContext.swift | 2 +- .../TransformOutgoingMessageMedia.swift | 18 ++ 34 files changed, 475 insertions(+), 122 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index d4a7ab246d..5fb20b0aef 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12266,6 +12266,8 @@ Sorry for the inconvenience."; "Stars.Intro.Transaction.FragmentTopUp.Subtitle" = "via Fragment"; "Stars.Intro.Transaction.FragmentWithdrawal.Title" = "Withdrawal"; "Stars.Intro.Transaction.FragmentWithdrawal.Subtitle" = "via Fragment"; +"Stars.Intro.Transaction.TelegramAds.Title" = "Withdrawal"; +"Stars.Intro.Transaction.TelegramAds.Subtitle" = "via Telegram Ads"; "Stars.Intro.Transaction.Unsupported.Title" = "Unsupported"; "Stars.Intro.Transaction.Refund" = "Refund"; @@ -12310,9 +12312,19 @@ Sorry for the inconvenience."; "Stars.Transaction.FragmentTopUp.Subtitle" = "Fragment"; "Stars.Transaction.FragmentWithdrawal.Title" = "Stars Withdrawal"; "Stars.Transaction.FragmentWithdrawal.Subtitle" = "Fragment"; +"Stars.Transaction.TelegramAds.Title" = "Stars Withdrawal"; +"Stars.Transaction.TelegramAds.Subtitle" = "Telegram Ads"; "Stars.Transaction.Unsupported.Title" = "Unsupported"; "Stars.Transaction.Refund" = "Refund"; +"Stars.Transaction.Photos_1" = "%@ Photo"; +"Stars.Transaction.Photos_any" = "%@ Photos"; +"Stars.Transaction.Videos_1" = "%@ Video"; +"Stars.Transaction.Videos_any" = "%@ Videos"; +"Stars.Transaction.SinglePhoto" = "Photo"; +"Stars.Transaction.SingleVideo" = "Video"; +"Stars.Transaction.MediaAnd" = "%1$@ and %2$@"; + "Stars.Transfer.Title" = "Confirm Your Purchase"; "Stars.Transfer.Info" = "Do you want to buy **%1$@** in **%2$@** for **%3$@**?"; "Stars.Transfer.Info.Stars_1" = "%@ Star"; @@ -12329,6 +12341,14 @@ Sorry for the inconvenience."; "Stars.Transfer.Unavailable" = "Sorry, no star purchases are available from your country."; +"Stars.Transfer.Photos_1" = "%@ photo"; +"Stars.Transfer.Photos_any" = "%@ photos"; +"Stars.Transfer.Videos_1" = "%@ video"; +"Stars.Transfer.Videos_any" = "%@ videos"; +"Stars.Transfer.SinglePhoto" = "photo"; +"Stars.Transfer.SingleVideo" = "video"; +"Stars.Transfer.MediaAnd" = "%1$@ and %2$@"; + "Settings.Stars" = "Your Stars"; "Chat.MessageEffectMenu.TitleAddEffect" = "Add an animated effect"; diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index a19a5a0d82..0ed6b3c479 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -1063,6 +1063,7 @@ public enum FileMediaResourceMediaStatus: Equatable { } public protocol ChatMessageItemNodeProtocol: ListViewItemNode { + func makeProgress() -> Promise? func targetReactionView(value: MessageReaction.Reaction) -> UIView? func targetForStoryTransition(id: StoryId) -> UIView? func contentFrame() -> CGRect diff --git a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift index a8428853f7..5c7fb53fd9 100644 --- a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift +++ b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift @@ -67,7 +67,7 @@ public func messageFileMediaResourceStatus(context: AccountContext, file: Telegr } } } else if let pendingStatus = pendingStatus { - mediaStatus = .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress)) + mediaStatus = .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress.progress)) } else { mediaStatus = .fetchStatus(EngineMediaResource.FetchStatus(resourceStatus)) } @@ -104,7 +104,7 @@ public func messageImageMediaResourceStatus(context: AccountContext, image: Tele |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in let mediaStatus: FileMediaResourceMediaStatus if let pendingStatus = pendingStatus { - mediaStatus = .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress)) + mediaStatus = .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress.progress)) } else { mediaStatus = .fetchStatus(EngineMediaResource.FetchStatus(resourceStatus)) } diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index 855ef16b3a..fc876e6e81 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -1077,7 +1077,7 @@ private final class DemoSheetContent: CombinedComponent { ) ) ) - //TODO:localize + availableItems[.messageEffects] = DemoPagerComponent.Item( AnyComponentWithIdentity( id: PremiumDemoScreen.Subject.messageEffects, @@ -1193,8 +1193,7 @@ private final class DemoSheetContent: CombinedComponent { case .folderTags: text = strings.Premium_FolderTagsStandaloneInfo case .messageEffects: - //TODO:localize - text = "Add over 500 animated effects to private messages." + text = strings.Premium_MessageEffectsInfo default: text = "" } diff --git a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift index f148dc67b6..6a225c18dc 100644 --- a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift +++ b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift @@ -254,8 +254,8 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode { itemTitle = item.presentationData.strings.Stars_Intro_Transaction_PremiumBotTopUp_Title itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_PremiumBotTopUp_Subtitle case .ads: - itemTitle = "Withdrawal" - itemSubtitle = "via Telegram Ads" + itemTitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramAds_Title + itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramAds_Subtitle case .unsupported: itemTitle = item.presentationData.strings.Stars_Intro_Transaction_Unsupported_Title itemSubtitle = nil diff --git a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift index fd2ee56776..00f94cf884 100644 --- a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift +++ b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift @@ -153,7 +153,13 @@ private func areResourcesEqual(_ lhs: MediaResource, _ rhs: MediaResource) -> Bo } private func findMediaResource(media: Media, previousMedia: Media?, resource: MediaResource) -> TelegramMediaResource? { - if let image = media as? TelegramMediaImage { + if let paidContent = media as? TelegramMediaPaidContent { + for case let .full(fullMedia) in paidContent.extendedMedia { + if let resource = findMediaResource(media: fullMedia, previousMedia: previousMedia, resource: resource) { + return resource + } + } + } else if let image = media as? TelegramMediaImage { for representation in image.representations { if let updatedResource = representation.resource as? CloudPhotoSizeMediaResource, let previousResource = resource as? CloudPhotoSizeMediaResource { if updatedResource.photoId == previousResource.photoId && updatedResource.sizeSpec == previousResource.sizeSpec { diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 966aef3218..1a2a9c3295 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -23,8 +23,18 @@ struct PendingMessageUploadedContentAndReuploadInfo { let cacheReferenceKey: CachedSentMediaReferenceKey? } +struct PendingMessageUploadedContentProgress { + let progress: Float + let mediaProgress: [MediaId: Float] + + init(progress: Float, mediaProgress: [MediaId: Float] = [:]) { + self.progress = progress + self.mediaProgress = mediaProgress + } +} + enum PendingMessageUploadedContentResult { - case progress(Float) + case progress(PendingMessageUploadedContentProgress) case content(PendingMessageUploadedContentAndReuploadInfo) } @@ -90,7 +100,7 @@ func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Po } else if let media = media.first as? TelegramMediaStory { return .signal(postbox.transaction { transaction -> PendingMessageUploadedContentResult in guard let inputPeer = transaction.getPeer(media.storyId.peerId).flatMap(apiInputPeer) else { - return .progress(0.0) + return .progress(PendingMessageUploadedContentProgress(progress: 0.0)) } return .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaStory(peer: inputPeer, id: media.storyId.id), ""), reuploadInfo: nil, cacheReferenceKey: nil)) } @@ -121,7 +131,12 @@ func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Po func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, forceNoBigParts: Bool, peerId: PeerId, media: Media, text: String, autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?, messageId: MessageId?, attributes: [MessageAttribute], mediaReference: AnyMediaReference?) -> Signal? { if let paidContent = media as? TelegramMediaPaidContent { var signals: [Signal] = [] + var mediaIds: [MediaId] = [] for case let .full(media) in paidContent.extendedMedia { + guard let id = media.id else { + continue + } + mediaIds.append(id) if let image = media as? TelegramMediaImage { signals.append(uploadedMediaImageContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, forceReupload: forceReupload, isGrouped: false, peerId: peerId, image: image, messageId: messageId, text: "", attributes: [], autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, auxiliaryMethods: auxiliaryMethods)) } else if let file = media as? TelegramMediaFile { @@ -132,13 +147,16 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post |> map { results -> PendingMessageUploadedContentResult in var currentProgress: Float = 0.0 var media: [Api.InputMedia] = [] - for result in results { + var mediaProgress: [MediaId: Float] = [:] + for (mediaId, result) in zip(mediaIds, results) { switch result { case let .progress(progress): - currentProgress += progress + currentProgress += progress.progress + mediaProgress[mediaId] = progress.progress case let .content(content): if case let .media(resultMedia, _) = content.content { media.append(resultMedia) + mediaProgress[mediaId] = 1.0 } } } @@ -153,7 +171,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post cacheReferenceKey: nil )) } else { - return .progress(normalizedProgress) + return .progress(PendingMessageUploadedContentProgress(progress: normalizedProgress, mediaProgress: mediaProgress)) } } } @@ -464,7 +482,7 @@ if "".isEmpty { flags |= 1 << 1 } } - return .single(.progress(1.0)) + return .single(.progress(PendingMessageUploadedContentProgress(progress: 1.0))) |> then( .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: nil))) ) @@ -500,13 +518,43 @@ if "".isEmpty { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: nil, psaType: nil, flags: []) } var updatedAttributes = currentMessage.attributes - if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){ - let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute - updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia])) - } else { - updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: Int64.random(in: Int64.min ... Int64.max), flags: [.transformedMedia], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + + var markTransformedMedia = true + var updatedMedia = currentMessage.media + if let paidContent = updatedMedia.first(where: { $0 is TelegramMediaPaidContent }) as? TelegramMediaPaidContent { + var extendedMedia = paidContent.extendedMedia + if let index = extendedMedia.firstIndex(where: { media in + if case let .full(fullMedia) = media, fullMedia.id == id { + return true + } else { + return false + } + }) { + extendedMedia[index] = .full(media: media) + } + updatedMedia = [TelegramMediaPaidContent(amount: paidContent.amount, extendedMedia: extendedMedia)] + + if extendedMedia.contains(where: { media in + if case .preview = media { + return true + } else { + return false + } + }) { + markTransformedMedia = false + } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + + if markTransformedMedia { + if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){ + let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute + updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia])) + } else { + updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: Int64.random(in: Int64.min ... Int64.max), flags: [.transformedMedia], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + } + } + + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: updatedMedia)) }) } return .done(media) @@ -526,7 +574,7 @@ if "".isEmpty { |> mapToSignal { transformResult -> Signal in switch transformResult { case .pending: - return .single(.progress(0.0)) + return .single(.progress(PendingMessageUploadedContentProgress(progress: 0.0))) case let .done(transformedMedia): let transformedImage = (transformedMedia as? TelegramMediaImage) ?? image guard let largestRepresentation = largestImageRepresentation(transformedImage.representations) else { @@ -543,7 +591,7 @@ if "".isEmpty { |> mapToSignal { next -> Signal in switch next { case let .progress(progress): - return .single(.progress(progress)) + return .single(.progress(PendingMessageUploadedContentProgress(progress: progress))) case let .inputFile(file): var flags: Int32 = 0 var ttlSeconds: Int32? @@ -770,7 +818,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili } } - return .single(.progress(1.0)) + return .single(.progress(PendingMessageUploadedContentProgress(progress: 1.0))) |> then( .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil))) ) @@ -844,13 +892,43 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: nil, psaType: nil, flags: []) } var updatedAttributes = currentMessage.attributes - if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){ - let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute - updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia])) - } else { - updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: Int64.random(in: Int64.min ... Int64.max), flags: [.transformedMedia], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + + var markTransformedMedia = true + var updatedMedia = currentMessage.media + if let paidContent = updatedMedia.first(where: { $0 is TelegramMediaPaidContent }) as? TelegramMediaPaidContent { + var extendedMedia = paidContent.extendedMedia + if let index = extendedMedia.firstIndex(where: { media in + if case let .full(fullMedia) = media, fullMedia.id == id { + return true + } else { + return false + } + }) { + extendedMedia[index] = .full(media: media) + } + updatedMedia = [TelegramMediaPaidContent(amount: paidContent.amount, extendedMedia: extendedMedia)] + + if extendedMedia.contains(where: { media in + if case .preview = media { + return true + } else { + return false + } + }) { + markTransformedMedia = false + } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + + if markTransformedMedia { + if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){ + let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute + updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia])) + } else { + updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: Int64.random(in: Int64.min ... Int64.max), flags: [.transformedMedia], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + } + } + + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: updatedMedia)) }) } return .done(media) @@ -901,7 +979,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili |> mapToSignal { content, fileAndThumbnailResult, resourceStatus -> Signal in guard let content = content else { if let resourceStatus = resourceStatus, case let .Fetching(_, progress) = resourceStatus { - return .single(.progress(progress * 0.33)) + return .single(.progress(PendingMessageUploadedContentProgress(progress: progress * 0.33))) } return .complete() } @@ -911,7 +989,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili if passFetchProgress { progress = 0.33 + progress * 0.67 } - return .single(.progress(progress)) + return .single(.progress(PendingMessageUploadedContentProgress(progress: progress))) case let .inputFile(inputFile): if case let .done(file, thumbnail) = fileAndThumbnailResult { var flags: Int32 = 0 diff --git a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift index fecd4acc85..1a131673d1 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift @@ -54,7 +54,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, let uploadedMedia: Signal switch media { case .keep: - uploadedMedia = .single(.progress(0.0)) + uploadedMedia = .single(.progress(PendingMessageUploadedContentProgress(progress: 0.0))) |> then(.single(nil)) case let .update(media): let generateUploadSignal: (Bool) -> Signal? = { forceReupload in @@ -69,14 +69,14 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, forceNoBigParts: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: attributes, mediaReference: nil) } if let uploadSignal = generateUploadSignal(forceReupload) { - uploadedMedia = .single(.progress(0.027)) + uploadedMedia = .single(.progress(PendingMessageUploadedContentProgress(progress: 0.027))) |> then(uploadSignal) |> map { result -> PendingMessageUploadedContentResult? in switch result { - case let .progress(value): - return .progress(max(value, 0.027)) - case let .content(content): - return .content(content) + case let .progress(value): + return .progress(PendingMessageUploadedContentProgress(progress: max(value.progress, 0.027))) + case let .content(content): + return .content(content) } } |> `catch` { _ -> Signal in @@ -92,10 +92,10 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, var pendingMediaContent: PendingMessageUploadedContent? if let uploadedMediaResult = uploadedMediaResult { switch uploadedMediaResult { - case let .progress(value): - return .single(.progress(value)) - case let .content(content): - pendingMediaContent = content.content + case let .progress(value): + return .single(.progress(value.progress)) + case let .content(content): + pendingMediaContent = content.content } } return postbox.transaction { transaction -> (Peer?, Message?, SimpleDictionary) in diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index 7046de43f7..9a0baa37cf 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -206,7 +206,7 @@ public func standaloneSendEnqueueMessages( switch result.result { case let .progress(value): allDone = false - progressSum += value + progressSum += value.progress case let .content(content): allResults.append((content, result.media)) } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index cfcc2c8923..ff6734e87f 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -6,8 +6,23 @@ import MtProtoKit public struct PendingMessageStatus: Equatable { + public struct Progress: Equatable { + public let progress: Float + public let mediaProgress: [MediaId: Float] + + public init(progress: Float, mediaProgress: [MediaId: Float] = [:]) { + self.progress = progress + self.mediaProgress = mediaProgress + } + + init(_ contentProgress: PendingMessageUploadedContentProgress) { + self.progress = contentProgress.progress + self.mediaProgress = contentProgress.mediaProgress + } + } + public let isRunning: Bool - public let progress: Float + public let progress: Progress } private enum PendingMessageState { @@ -364,7 +379,7 @@ public final class PendingMessageManager { self.messageContexts[id] = messageContext } - let status = PendingMessageStatus(isRunning: false, progress: 0.0) + let status = PendingMessageStatus(isRunning: false, progress: PendingMessageStatus.Progress(progress: 0.0)) if status != messageContext.status { messageContext.status = status for subscriber in messageContext.statusSubscribers.copyItems() { @@ -618,7 +633,7 @@ public final class PendingMessageManager { switch next { case let .progress(progress): if let current = strongSelf.messageContexts[messageId] { - let status = PendingMessageStatus(isRunning: true, progress: progress) + let status = PendingMessageStatus(isRunning: true, progress: PendingMessageStatus.Progress(progress: progress)) current.status = status for subscriber in current.statusSubscribers.copyItems() { subscriber(current.status, current.error) @@ -636,7 +651,7 @@ public final class PendingMessageManager { private func beginUploadingMessage(messageContext: PendingMessageContext, id: MessageId, threadId: Int64?, groupId: Int64?, uploadSignal: Signal) { messageContext.state = .uploading(groupId: groupId) - let status = PendingMessageStatus(isRunning: true, progress: 0.0) + let status = PendingMessageStatus(isRunning: true, progress: PendingMessageStatus.Progress(progress: 0.0)) messageContext.status = status for subscriber in messageContext.statusSubscribers.copyItems() { subscriber(messageContext.status, messageContext.error) @@ -678,7 +693,7 @@ public final class PendingMessageManager { switch next { case let .progress(progress): if let current = strongSelf.messageContexts[id] { - let status = PendingMessageStatus(isRunning: true, progress: progress) + let status = PendingMessageStatus(isRunning: true, progress: PendingMessageStatus.Progress(progress)) current.status = status for subscriber in current.statusSubscribers.copyItems() { subscriber(current.status, current.error) @@ -712,7 +727,7 @@ public final class PendingMessageManager { if case let .waitingForUploadToStart(groupId, uploadSignal) = context.state { if self.canBeginUploadingMessage(id: contextId, type: context.contentType ?? .media) { context.state = .uploading(groupId: groupId) - let status = PendingMessageStatus(isRunning: true, progress: 0.0) + let status = PendingMessageStatus(isRunning: true, progress: PendingMessageStatus.Progress(progress: 0.0)) context.status = status for subscriber in context.statusSubscribers.copyItems() { subscriber(context.status, context.error) @@ -734,7 +749,7 @@ public final class PendingMessageManager { switch next { case let .progress(progress): if let current = strongSelf.messageContexts[contextId] { - let status = PendingMessageStatus(isRunning: true, progress: progress) + let status = PendingMessageStatus(isRunning: true, progress: PendingMessageStatus.Progress(progress)) current.status = status for subscriber in current.statusSubscribers.copyItems() { subscriber(context.status, context.error) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 91a7c0efdf..38c07f640b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1093,7 +1093,7 @@ func _internal_uploadStoryImpl( |> mapToSignal { result -> Signal in switch result { case let .progress(progress): - return .single(.progress(progress)) + return .single(.progress(progress.progress)) case let .content(content): return postbox.transaction { transaction -> Signal in let privacyRules = apiInputPrivacyRules(privacy: privacy, transaction: transaction) @@ -1278,7 +1278,7 @@ func _internal_editStory(account: Account, peerId: PeerId, id: Int32, media: Eng return contentSignal |> mapToSignal { result -> Signal in if let result = result, case let .progress(progress) = result { - return .single(.progress(progress)) + return .single(.progress(progress.progress)) } let inputMedia: Api.InputMedia? diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 6274b32845..44d2791a2d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -5911,6 +5911,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI item.controllerInteraction.openMessageContextMenu(item.message, true, self, subFrame, nil, nil) } + override public func makeProgress() -> Promise? { + return self.unlockButtonNode?.makeProgress() + } + override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? { if let result = self.reactionButtonsNode?.reactionTargetView(value: value) { return result diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift index 02ee9cf6c8..59b4284e66 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift @@ -1490,7 +1490,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco reactionButtonsNode.offset(value: value, animationCurve: animationCurve, duration: duration) } } - + override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? { if let result = self.reactionButtonsNode?.reactionTargetView(value: value) { return result diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift index 6e5ebdf780..bc11d74c99 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -473,7 +473,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { updatedPlaybackStatus = combineLatest(messageFileMediaResourceStatus(context: item.context, file: updatedFile, message: EngineMessage(item.message), isRecentActions: item.associatedData.isRecentActions), item.context.account.pendingMessageManager.pendingMessageStatus(item.message.id) |> map { $0.0 }) |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in if let pendingStatus = pendingStatus { - var progress = pendingStatus.progress + var progress = pendingStatus.progress.progress if pendingStatus.isRunning { progress = max(progress, 0.27) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 1ec0b22b75..15b32c25b3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -1475,7 +1475,11 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr updatedStatusSignal = combineLatest(chatMessagePhotoStatus(context: context, messageId: message.id, photoReference: .message(message: MessageReference(message), media: image)), context.account.pendingMessageManager.pendingMessageStatus(message.id) |> map { $0.0 }) |> map { resourceStatus, pendingStatus -> (MediaResourceStatus, MediaResourceStatus?) in if let pendingStatus = pendingStatus { - let adjustedProgress = max(pendingStatus.progress, 0.027) + var progress: Float = pendingStatus.progress.progress + if let id = media.id, let mediaProgress = pendingStatus.progress.mediaProgress[id] { + progress = mediaProgress + } + let adjustedProgress = max(progress, 0.027) return (.Fetching(isActive: pendingStatus.isRunning, progress: adjustedProgress), resourceStatus) } else { return (resourceStatus, nil) @@ -1491,7 +1495,11 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr updatedStatusSignal = combineLatest(messageMediaFileStatus(context: context, messageId: message.id, file: file, adjustForVideoThumbnail: true), context.account.pendingMessageManager.pendingMessageStatus(message.id) |> map { $0.0 }) |> map { resourceStatus, pendingStatus -> (MediaResourceStatus, MediaResourceStatus?) in if let pendingStatus = pendingStatus { - let adjustedProgress = max(pendingStatus.progress, 0.027) + var progress: Float = pendingStatus.progress.progress + if let id = media.id, let mediaProgress = pendingStatus.progress.mediaProgress[id] { + progress = mediaProgress + } + let adjustedProgress = max(progress, 0.027) return (.Fetching(isActive: pendingStatus.isRunning, progress: adjustedProgress), resourceStatus) } else { return (resourceStatus, nil) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index dd9121a8cd..a473d69ea6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -847,7 +847,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { item.controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes) } case .payment: - item.controllerInteraction.openCheckoutOrReceipt(item.message.id) + item.controllerInteraction.openCheckoutOrReceipt(item.message.id, nil) case let .urlAuth(url, buttonId): item.controllerInteraction.requestMessageActionUrlAuth(url, .message(id: item.message.id, buttonId: buttonId)) case .setupPoll: @@ -881,6 +881,10 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { open func openMessageContextMenu() { } + open func makeProgress() -> Promise? { + return nil + } + open func targetReactionView(value: MessageReaction.Reaction) -> UIView? { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift index 68dccc6c03..57b30db954 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift @@ -56,7 +56,8 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { case .automaticPlayback: openChatMessageMode = .automaticPlayback } - let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: openChatMessageMode, mediaIndex: self.mediaIndex)) + + let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: openChatMessageMode, mediaIndex: self.mediaIndex, progress: self.itemNode?.makeProgress())) } self.interactiveImageNode.activateAgeRestrictedMedia = { [weak self] in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift index 6293c100a2..f64d02df9a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift @@ -239,9 +239,12 @@ public class ChatMessageStarsMediaInfoNode: ASDisplayNode { } else { text = NSMutableAttributedString(string: "Purchased", font: textFont, textColor: .white) } + + var offset: CGFloat = 0.0 if let range = text.string.range(of: "⭐️") { text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: NSRange(range, in: text.string)) text.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: text.string)) + offset -= 1.0 } let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: arguments.constrainedSize.width, height: arguments.constrainedSize.height), alignment: .natural, cutout: nil, insets: .zero)) @@ -278,7 +281,7 @@ public class ChatMessageStarsMediaInfoNode: ASDisplayNode { node.contentBackgroundNode.frame = CGRect(origin: .zero, size: size) - let textFrame = CGRect(origin: CGPoint(x: padding, y: floorToScreenPixels((size.height - textLayout.size.height) / 2.0) + UIScreenPixel), size: textLayout.size) + let textFrame = CGRect(origin: CGPoint(x: padding + offset, y: floorToScreenPixels((size.height - textLayout.size.height) / 2.0) + UIScreenPixel), size: textLayout.size) textNode.textNode.frame = textFrame return node diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift index 8f97770655..f0481caa95 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift @@ -16,6 +16,7 @@ import AnimationCache import MultiAnimationRenderer import ComponentFlow import ChatControllerInteraction +import HierarchyTrackingLayer public class ChatMessageUnlockMediaNode: ASDisplayNode { public class Arguments { @@ -63,11 +64,14 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode { private let contentNode: HighlightTrackingButtonNode private let backgroundNode: NavigationBackgroundNode private var textNode: TextNodeWithEntities? + private var loadingView: LoadingEffectView? private var pressed = { } private var absolutePosition: (CGRect, CGSize)? + private var currentProgressDisposable: Disposable? + override public init() { self.contentNode = HighlightTrackingButtonNode() @@ -83,10 +87,41 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode { self.contentNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) } + deinit { + self.currentProgressDisposable?.dispose() + } + @objc private func buttonPressed() { self.pressed() } + public func makeProgress() -> Promise { + let progress = Promise() + self.currentProgressDisposable?.dispose() + self.currentProgressDisposable = (progress.get() + |> distinctUntilChanged + |> deliverOnMainQueue).start(next: { [weak self] hasProgress in + guard let self, let loadingView = self.loadingView else { + return + } + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + if hasProgress { + if loadingView.superview == nil { + loadingView.alpha = 0.0 + self.view.addSubview(loadingView) + transition.updateAlpha(layer: loadingView.layer, alpha: 1.0) + } + } else if loadingView.superview != nil { + transition.updateAlpha(layer: loadingView.layer, alpha: 0.0, beginWithCurrentState: true, completion: { finished in + if finished { + loadingView.removeFromSuperview() + } + }) + } + }) + return progress + } + public class func asyncLayout(_ maybeNode: ChatMessageUnlockMediaNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode) { let textNodeLayout = TextNodeWithEntities.asyncLayout(maybeNode?.textNode) @@ -139,8 +174,171 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode { node.backgroundNode.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate) node.contentNode.frame = CGRect(origin: CGPoint(), size: size) + let loadingView: LoadingEffectView + if let current = node.loadingView { + loadingView = current + } else { + loadingView = LoadingEffectView() + node.loadingView = loadingView + } + loadingView.frame = CGRect(origin: .zero, size: size) + loadingView.update(color: UIColor.white, rect: CGRect(origin: .zero, size: size)) + return node }) } } } + +private let shadowImage: UIImage? = { + UIImage(named: "Stories/PanelGradient") +}() + +public final class LoadingEffectView: UIView { + let hierarchyTrackingLayer: HierarchyTrackingLayer + + private let maskContentsView: UIView + private let maskHighlightNode: LinkHighlightingNode + + private let maskBorderContentsView: UIView + private let maskBorderHighlightNode: LinkHighlightingNode + + private let backgroundView: UIImageView + private let borderBackgroundView: UIImageView + + private var duration: Double + private var gradientWidth: CGFloat + + private var inHierarchy = false + private var size: CGSize? + + override public init(frame: CGRect) { + self.hierarchyTrackingLayer = HierarchyTrackingLayer() + + self.maskContentsView = UIView() + self.maskHighlightNode = LinkHighlightingNode(color: .black) + self.maskHighlightNode.useModernPathCalculation = true + + self.maskBorderContentsView = UIView() + self.maskBorderHighlightNode = LinkHighlightingNode(color: .black) + self.maskBorderHighlightNode.borderOnly = true + self.maskBorderHighlightNode.useModernPathCalculation = true + + self.maskBorderContentsView.addSubview(self.maskBorderHighlightNode.view) + + self.backgroundView = UIImageView() + self.borderBackgroundView = UIImageView() + + self.gradientWidth = 120.0 + self.duration = 1.0 + + super.init(frame: frame) + + self.isUserInteractionEnabled = false + + self.maskContentsView.mask = self.maskHighlightNode.view + self.maskContentsView.addSubview(self.backgroundView) + self.addSubview(self.maskContentsView) + + self.maskBorderContentsView.mask = self.maskBorderHighlightNode.view + self.maskBorderContentsView.addSubview(self.borderBackgroundView) + self.addSubview(self.maskBorderContentsView) + + let generateGradient: (CGFloat) -> UIImage? = { baseAlpha in + return generateImage(CGSize(width: self.gradientWidth, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let foregroundColor = UIColor(white: 1.0, alpha: min(1.0, baseAlpha * 4.0)) + + if let shadowImage { + UIGraphicsPushContext(context) + + for i in 0 ..< 2 { + let shadowFrame = CGRect(origin: CGPoint(x: CGFloat(i) * (size.width * 0.5), y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height)) + + context.saveGState() + context.translateBy(x: shadowFrame.midX, y: shadowFrame.midY) + context.rotate(by: CGFloat(i == 0 ? 1.0 : -1.0) * CGFloat.pi * 0.5) + let adjustedRect = CGRect(origin: CGPoint(x: -shadowFrame.height * 0.5, y: -shadowFrame.width * 0.5), size: CGSize(width: shadowFrame.height, height: shadowFrame.width)) + + context.clip(to: adjustedRect, mask: shadowImage.cgImage!) + context.setFillColor(foregroundColor.cgColor) + context.fill(adjustedRect) + + context.restoreGState() + } + + UIGraphicsPopContext() + } + })?.withRenderingMode(.alwaysTemplate) + } + + self.backgroundView.image = generateGradient(0.5) + self.borderBackgroundView.image = generateGradient(1.0) + + self.layer.addSublayer(self.hierarchyTrackingLayer) + self.hierarchyTrackingLayer.isInHierarchyUpdated = { [weak self] inHierarchy in + guard let self, let size = self.size else { + return + } + self.inHierarchy = inHierarchy + self.updateAnimations(size: size) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func updateAnimations(size: CGSize) { + if self.inHierarchy { + if self.backgroundView.layer.animation(forKey: "shimmer") != nil { + return + } + let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: (size.width + self.gradientWidth + size.width * 0.0) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: self.duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true) + animation.repeatCount = Float.infinity + self.backgroundView.layer.add(animation, forKey: "shimmer") + self.borderBackgroundView.layer.add(animation, forKey: "shimmer") + } else { + self.backgroundView.layer.removeAllAnimations() + self.borderBackgroundView.layer.removeAllAnimations() + } + } + + public func update(color: UIColor, rect: CGRect) { + let maskFrame = CGRect(origin: CGPoint(), size: rect.size).insetBy(dx: -4.0, dy: -4.0) + + self.gradientWidth = 260.0 + self.duration = 1.2 + + self.maskContentsView.backgroundColor = .clear + self.maskBorderContentsView.backgroundColor = color.withAlphaComponent(0.12) + +// self.backgroundView.alpha = 0.25 + self.backgroundView.tintColor = color + + self.borderBackgroundView.tintColor = color + + self.maskContentsView.frame = maskFrame + self.maskBorderContentsView.frame = maskFrame + + let rectsSet: [CGRect] = [rect] + + self.maskHighlightNode.outerRadius = rect.height / 2.0 + self.maskHighlightNode.updateRects(rectsSet) + self.maskHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize()) + + self.maskBorderHighlightNode.outerRadius = rect.height / 2.0 + self.maskBorderHighlightNode.updateRects(rectsSet) + self.maskBorderHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize()) + + if self.size != maskFrame.size { + self.size = maskFrame.size + + self.backgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height)) + self.borderBackgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height)) + + self.updateAnimations(size: maskFrame.size) + } + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index a67caf2d7e..7bdc5a1bde 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -557,7 +557,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { break } } - }, openCheckoutOrReceipt: { _ in + }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in }, canSetupReply: { _ in diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift index 9d15c612ec..5cd1237b3f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift @@ -428,7 +428,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess return nil }, chatControllerNode: { return nil - }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in + }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in }, canSetupReply: { _ in return .none }, canSendMessages: { diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 710517d8fb..22852dd3d1 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -207,7 +207,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let presentGlobalOverlayController: (ViewController, Any?) -> Void public let callPeer: (PeerId, Bool) -> Void public let longTap: (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void - public let openCheckoutOrReceipt: (MessageId) -> Void + public let openCheckoutOrReceipt: (MessageId, OpenMessageParams?) -> Void public let openSearch: () -> Void public let setupReply: (MessageId) -> Void public let canSetupReply: (Message) -> ChatControllerInteractionSwipeAction @@ -336,7 +336,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId, Bool) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void, - openCheckoutOrReceipt: @escaping (MessageId) -> Void, + openCheckoutOrReceipt: @escaping (MessageId, OpenMessageParams?) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 19c963e87f..cd3e327d0b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -3295,7 +3295,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro default: break } - }, openCheckoutOrReceipt: { _ in + }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in }, canSetupReply: { _ in diff --git a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift index 7e8f00cd2e..545bfc8409 100644 --- a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift @@ -245,12 +245,18 @@ public final class StarsAvatarComponent: Component { self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment") iconOffset = 2.0 case .ads: - self.backgroundView.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1b1f24)) + self.backgroundView.image = generateGradientFilledCircleImage( + diameter: size.width, + colors: [ + UIColor(rgb: 0xffa85c).cgColor, + UIColor(rgb: 0xffcd6a).cgColor + ], + direction: .mirroredDiagonal + ) self.backgroundView.isHidden = false self.iconView.isHidden = false self.avatarNode.isHidden = true - self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment") - iconOffset = 2.0 + self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white) case .premiumBot: iconInset = 7.0 self.backgroundView.image = generateGradientFilledCircleImage( diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift index 02746eb60d..c29b55363e 100644 --- a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift @@ -720,12 +720,15 @@ public final class StarsImageComponent: Component { iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment") iconOffset = 5.0 case .ads: - iconBackgroundView.image = generateFilledCircleImage( + iconBackgroundView.image = generateGradientFilledCircleImage( diameter: imageSize.width, - color: UIColor(rgb: 0x1b1f24) + colors: [ + UIColor(rgb: 0xffa85c).cgColor, + UIColor(rgb: 0xffcd6a).cgColor + ], + direction: .mirroredDiagonal ) - iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment") - iconOffset = 5.0 + iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white) case .premiumBot: iconInset = 15.0 iconBackgroundView.image = generateGradientFilledCircleImage( diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index 8bf58a689c..b73fa35bdb 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -216,17 +216,16 @@ private final class StarsTransactionSheetContent: CombinedComponent { via = strings.Stars_Transaction_FragmentWithdrawal_Subtitle } case .ads: - //TODO:localize - titleText = "Stars Withdrawal" - via = "Telegram Ads" + titleText = strings.Stars_Transaction_TelegramAds_Title + via = strings.Stars_Transaction_TelegramAds_Subtitle case .unsupported: titleText = strings.Stars_Transaction_Unsupported_Title via = nil } if !transaction.media.isEmpty { var description: String = "" - var photoCount: Int = 0 - var videoCount: Int = 0 + var photoCount: Int32 = 0 + var videoCount: Int32 = 0 for media in transaction.media { if let _ = media as? TelegramMediaFile { videoCount += 1 @@ -234,33 +233,22 @@ private final class StarsTransactionSheetContent: CombinedComponent { photoCount += 1 } } - //TODO:localize if photoCount > 0 && videoCount > 0 { - if photoCount > 1 { - description += "\(photoCount) photos**" - } else { - description += "**\(photoCount) photo**" - } - description += " and " - if videoCount > 1 { - description += "**\(videoCount) videos**" - } else { - description += "**\(videoCount) video**" - } + description += strings.Stars_Transaction_MediaAnd(strings.Stars_Transaction_Photos(photoCount), strings.Stars_Transaction_Videos(videoCount)).string } else if photoCount > 0 { if photoCount > 1 { - description += "**\(photoCount) photos**" + description += strings.Stars_Transaction_Photos(photoCount) } else { - description += "**Photo**" + description += strings.Stars_Transaction_SinglePhoto } } else if videoCount > 0 { if videoCount > 1 { - description += "**\(videoCount) videos**" + description += strings.Stars_Transaction_Videos(videoCount) } else { - description += "**Video**" + description += strings.Stars_Transaction_SingleVideo } } - descriptionText = description.replacingOccurrences(of: "**", with: "") + descriptionText = description } else { descriptionText = transaction.description ?? "" } @@ -416,7 +404,6 @@ private final class StarsTransactionSheetContent: CombinedComponent { } if let messageId { - //TODO:localize let peerName: String if case let .transaction(_, parentPeer) = component.subject { if parentPeer.id == component.context.account.peerId { @@ -432,7 +419,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { peerName = "" } tableItems.append(.init( - id: "via", + id: "media", title: strings.Stars_Transaction_Media, component: AnyComponent( Button( diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift index 85056b50d2..2e8c2b6441 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift @@ -209,8 +209,7 @@ final class StarsTransactionsListPanelComponent: Component { switch item.peer { case let .peer(peer): if !item.media.isEmpty { - //TODO:localize - itemTitle = "Media Purchase" + itemTitle = environment.strings.Stars_Intro_Transaction_MediaPurchase itemSubtitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) } else if let title = item.title { itemTitle = title @@ -237,8 +236,8 @@ final class StarsTransactionsListPanelComponent: Component { itemTitle = environment.strings.Stars_Intro_Transaction_PremiumBotTopUp_Title itemSubtitle = environment.strings.Stars_Intro_Transaction_PremiumBotTopUp_Subtitle case .ads: - itemTitle = "Withdrawal" - itemSubtitle = "via Telegram Ads" + itemTitle = environment.strings.Stars_Intro_Transaction_TelegramAds_Title + itemSubtitle = environment.strings.Stars_Intro_Transaction_TelegramAds_Subtitle case .unsupported: itemTitle = environment.strings.Stars_Intro_Transaction_Unsupported_Title itemSubtitle = nil diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index a6e731d185..6d81c79dd7 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -335,10 +335,9 @@ private final class SheetContent: CombinedComponent { let amount = component.invoice.totalAmount let infoText: String if !component.extendedMedia.isEmpty { - //TODO:localize var description: String = "" - var photoCount: Int = 0 - var videoCount: Int = 0 + var photoCount: Int32 = 0 + var videoCount: Int32 = 0 for media in component.extendedMedia { if case let .preview(_, _, videoDuration) = media, videoDuration != nil { videoCount += 1 @@ -347,28 +346,18 @@ private final class SheetContent: CombinedComponent { } } if photoCount > 0 && videoCount > 0 { - if photoCount > 1 { - description += "**\(photoCount) photos**" - } else { - description += "**\(photoCount) photo**" - } - description += " and " - if videoCount > 1 { - description += "**\(videoCount) videos**" - } else { - description += "**\(videoCount) video**" - } + description = strings.Stars_Transfer_MediaAnd("**\(strings.Stars_Transfer_Photos(photoCount))**", "**\(strings.Stars_Transfer_Videos(videoCount))**").string } else if photoCount > 0 { if photoCount > 1 { - description += "**\(photoCount) photos**" + description += "**\(strings.Stars_Transfer_Photos(photoCount))**" } else { - description += "**photo**" + description += "**\(strings.Stars_Transfer_SinglePhoto)**" } } else if videoCount > 0 { if videoCount > 1 { - description += "**\(videoCount) videos**" + description += "**\(strings.Stars_Transfer_Videos(videoCount))**" } else { - description += "**video**" + description += "**\(strings.Stars_Transfer_SingleVideo)**" } } infoText = strings.Stars_Transfer_UnlockInfo( diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift index a28d7590b5..6f040f3f40 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift @@ -96,6 +96,8 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no return true } else if let file = media as? TelegramMediaFile, file.isVideo { return true + } else if media is TelegramMediaPaidContent { + return true } return false }) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 431ad1407f..04a5bd6d9c 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -905,7 +905,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch extendedMedia { case .preview: if displayVoiceMessageDiscardAlert() { - strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id) + strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, params) return true } else { return false @@ -917,7 +917,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch extendedMedia { case .preview: if displayVoiceMessageDiscardAlert() { - strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id) + strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, nil) return true } else { return false @@ -2586,7 +2586,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let self { self.openLinkLongTap(action, params: params) } - }, openCheckoutOrReceipt: { [weak self] messageId in + }, openCheckoutOrReceipt: { [weak self] messageId, params in guard let strongSelf = self else { return } @@ -2610,6 +2610,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G for media in message.media { if let paidContent = media as? TelegramMediaPaidContent { + let progressSignal = Signal { _ in + params?.progress?.set(.single(true)) + return ActionDisposable { + params?.progress?.set(.single(false)) + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.25, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.startStrict() + strongSelf.chatDisplayNode.dismissInput() let inputData = Promise() inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .message(message.id)) @@ -2636,6 +2646,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: paidContent.amount, startParam: "", extendedMedia: .preview(dimensions:dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: nil), flags: [], version: 0) let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, starsContext: starsContext, invoice: invoice, source: .message(messageId), extendedMedia: paidContent.extendedMedia, inputData: starsInputData, completion: { _ in }) strongSelf.push(controller) + + progressDisposable.dispose() }) } } else if let invoice = media as? TelegramMediaInvoice { diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 77f9463ec8..9163d271b5 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -955,7 +955,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes) } case .payment: - controllerInteraction.openCheckoutOrReceipt(message.id) + controllerInteraction.openCheckoutOrReceipt(message.id, nil) case let .urlAuth(url, buttonId): controllerInteraction.requestMessageActionUrlAuth(url, .message(id: message.id, buttonId: buttonId)) case .setupPoll: diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 3dfc566c2b..e810e8711c 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -118,7 +118,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in - }, openCheckoutOrReceipt: { _ in + }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in }, canSetupReply: { _ in diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index b18bf299ed..d8ce813d31 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1722,7 +1722,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return nil }, chatControllerNode: { return nil - }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in + }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in }, canSetupReply: { _ in return .none }, canSendMessages: { diff --git a/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift b/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift index 5d5e7e15d4..51b7b85aac 100644 --- a/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift +++ b/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift @@ -8,6 +8,24 @@ import PhotoResources import ImageCompression public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, media: AnyMediaReference, opportunistic: Bool) -> Signal { + if let paidContent = media.media as? TelegramMediaPaidContent { + var signals: [Signal] = [] + for case let .full(fullMedia) in paidContent.extendedMedia { + signals.append(transformOutgoingMessageMedia(postbox: postbox, network: network, media: media.withUpdatedMedia(fullMedia), opportunistic: opportunistic)) + } + return combineLatest(signals) + |> mapToSignal { results -> Signal in + let mediaResults = results.compactMap { $0?.media } + if mediaResults.count == signals.count { + return .single(media.withUpdatedMedia(TelegramMediaPaidContent(amount: paidContent.amount, extendedMedia: mediaResults.map { .full(media: $0) }))) + } else if opportunistic { + return .single(nil) + } else { + return .complete() + } + } + } + switch media.media { case let file as TelegramMediaFile: let signal = Signal { subscriber in