Paid media improvements

This commit is contained in:
Ilya Laktyushin 2024-06-24 04:06:57 +04:00
parent 813a913bca
commit e47b5a89ef
34 changed files with 475 additions and 122 deletions

View File

@ -12266,6 +12266,8 @@ Sorry for the inconvenience.";
"Stars.Intro.Transaction.FragmentTopUp.Subtitle" = "via Fragment"; "Stars.Intro.Transaction.FragmentTopUp.Subtitle" = "via Fragment";
"Stars.Intro.Transaction.FragmentWithdrawal.Title" = "Withdrawal"; "Stars.Intro.Transaction.FragmentWithdrawal.Title" = "Withdrawal";
"Stars.Intro.Transaction.FragmentWithdrawal.Subtitle" = "via Fragment"; "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.Unsupported.Title" = "Unsupported";
"Stars.Intro.Transaction.Refund" = "Refund"; "Stars.Intro.Transaction.Refund" = "Refund";
@ -12310,9 +12312,19 @@ Sorry for the inconvenience.";
"Stars.Transaction.FragmentTopUp.Subtitle" = "Fragment"; "Stars.Transaction.FragmentTopUp.Subtitle" = "Fragment";
"Stars.Transaction.FragmentWithdrawal.Title" = "Stars Withdrawal"; "Stars.Transaction.FragmentWithdrawal.Title" = "Stars Withdrawal";
"Stars.Transaction.FragmentWithdrawal.Subtitle" = "Fragment"; "Stars.Transaction.FragmentWithdrawal.Subtitle" = "Fragment";
"Stars.Transaction.TelegramAds.Title" = "Stars Withdrawal";
"Stars.Transaction.TelegramAds.Subtitle" = "Telegram Ads";
"Stars.Transaction.Unsupported.Title" = "Unsupported"; "Stars.Transaction.Unsupported.Title" = "Unsupported";
"Stars.Transaction.Refund" = "Refund"; "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.Title" = "Confirm Your Purchase";
"Stars.Transfer.Info" = "Do you want to buy **%1$@** in **%2$@** for **%3$@**?"; "Stars.Transfer.Info" = "Do you want to buy **%1$@** in **%2$@** for **%3$@**?";
"Stars.Transfer.Info.Stars_1" = "%@ Star"; "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.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"; "Settings.Stars" = "Your Stars";
"Chat.MessageEffectMenu.TitleAddEffect" = "Add an animated effect"; "Chat.MessageEffectMenu.TitleAddEffect" = "Add an animated effect";

View File

@ -1063,6 +1063,7 @@ public enum FileMediaResourceMediaStatus: Equatable {
} }
public protocol ChatMessageItemNodeProtocol: ListViewItemNode { public protocol ChatMessageItemNodeProtocol: ListViewItemNode {
func makeProgress() -> Promise<Bool>?
func targetReactionView(value: MessageReaction.Reaction) -> UIView? func targetReactionView(value: MessageReaction.Reaction) -> UIView?
func targetForStoryTransition(id: StoryId) -> UIView? func targetForStoryTransition(id: StoryId) -> UIView?
func contentFrame() -> CGRect func contentFrame() -> CGRect

View File

@ -67,7 +67,7 @@ public func messageFileMediaResourceStatus(context: AccountContext, file: Telegr
} }
} }
} else if let pendingStatus = pendingStatus { } 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 { } else {
mediaStatus = .fetchStatus(EngineMediaResource.FetchStatus(resourceStatus)) mediaStatus = .fetchStatus(EngineMediaResource.FetchStatus(resourceStatus))
} }
@ -104,7 +104,7 @@ public func messageImageMediaResourceStatus(context: AccountContext, image: Tele
|> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in
let mediaStatus: FileMediaResourceMediaStatus let mediaStatus: FileMediaResourceMediaStatus
if let pendingStatus = pendingStatus { if let pendingStatus = pendingStatus {
mediaStatus = .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress)) mediaStatus = .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress.progress))
} else { } else {
mediaStatus = .fetchStatus(EngineMediaResource.FetchStatus(resourceStatus)) mediaStatus = .fetchStatus(EngineMediaResource.FetchStatus(resourceStatus))
} }

View File

@ -1077,7 +1077,7 @@ private final class DemoSheetContent: CombinedComponent {
) )
) )
) )
//TODO:localize
availableItems[.messageEffects] = DemoPagerComponent.Item( availableItems[.messageEffects] = DemoPagerComponent.Item(
AnyComponentWithIdentity( AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.messageEffects, id: PremiumDemoScreen.Subject.messageEffects,
@ -1193,8 +1193,7 @@ private final class DemoSheetContent: CombinedComponent {
case .folderTags: case .folderTags:
text = strings.Premium_FolderTagsStandaloneInfo text = strings.Premium_FolderTagsStandaloneInfo
case .messageEffects: case .messageEffects:
//TODO:localize text = strings.Premium_MessageEffectsInfo
text = "Add over 500 animated effects to private messages."
default: default:
text = "" text = ""
} }

View File

@ -254,8 +254,8 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
itemTitle = item.presentationData.strings.Stars_Intro_Transaction_PremiumBotTopUp_Title itemTitle = item.presentationData.strings.Stars_Intro_Transaction_PremiumBotTopUp_Title
itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_PremiumBotTopUp_Subtitle itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_PremiumBotTopUp_Subtitle
case .ads: case .ads:
itemTitle = "Withdrawal" itemTitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramAds_Title
itemSubtitle = "via Telegram Ads" itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramAds_Subtitle
case .unsupported: case .unsupported:
itemTitle = item.presentationData.strings.Stars_Intro_Transaction_Unsupported_Title itemTitle = item.presentationData.strings.Stars_Intro_Transaction_Unsupported_Title
itemSubtitle = nil itemSubtitle = nil

View File

@ -153,7 +153,13 @@ private func areResourcesEqual(_ lhs: MediaResource, _ rhs: MediaResource) -> Bo
} }
private func findMediaResource(media: Media, previousMedia: Media?, resource: MediaResource) -> TelegramMediaResource? { 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 { for representation in image.representations {
if let updatedResource = representation.resource as? CloudPhotoSizeMediaResource, let previousResource = resource as? CloudPhotoSizeMediaResource { if let updatedResource = representation.resource as? CloudPhotoSizeMediaResource, let previousResource = resource as? CloudPhotoSizeMediaResource {
if updatedResource.photoId == previousResource.photoId && updatedResource.sizeSpec == previousResource.sizeSpec { if updatedResource.photoId == previousResource.photoId && updatedResource.sizeSpec == previousResource.sizeSpec {

View File

@ -23,8 +23,18 @@ struct PendingMessageUploadedContentAndReuploadInfo {
let cacheReferenceKey: CachedSentMediaReferenceKey? 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 { enum PendingMessageUploadedContentResult {
case progress(Float) case progress(PendingMessageUploadedContentProgress)
case content(PendingMessageUploadedContentAndReuploadInfo) case content(PendingMessageUploadedContentAndReuploadInfo)
} }
@ -90,7 +100,7 @@ func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Po
} else if let media = media.first as? TelegramMediaStory { } else if let media = media.first as? TelegramMediaStory {
return .signal(postbox.transaction { transaction -> PendingMessageUploadedContentResult in return .signal(postbox.transaction { transaction -> PendingMessageUploadedContentResult in
guard let inputPeer = transaction.getPeer(media.storyId.peerId).flatMap(apiInputPeer) else { 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)) 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<PendingMessageUploadedContentResult, PendingMessageUploadError>? { 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<PendingMessageUploadedContentResult, PendingMessageUploadError>? {
if let paidContent = media as? TelegramMediaPaidContent { if let paidContent = media as? TelegramMediaPaidContent {
var signals: [Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>] = [] var signals: [Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>] = []
var mediaIds: [MediaId] = []
for case let .full(media) in paidContent.extendedMedia { for case let .full(media) in paidContent.extendedMedia {
guard let id = media.id else {
continue
}
mediaIds.append(id)
if let image = media as? TelegramMediaImage { 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)) 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 { } else if let file = media as? TelegramMediaFile {
@ -132,13 +147,16 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
|> map { results -> PendingMessageUploadedContentResult in |> map { results -> PendingMessageUploadedContentResult in
var currentProgress: Float = 0.0 var currentProgress: Float = 0.0
var media: [Api.InputMedia] = [] var media: [Api.InputMedia] = []
for result in results { var mediaProgress: [MediaId: Float] = [:]
for (mediaId, result) in zip(mediaIds, results) {
switch result { switch result {
case let .progress(progress): case let .progress(progress):
currentProgress += progress currentProgress += progress.progress
mediaProgress[mediaId] = progress.progress
case let .content(content): case let .content(content):
if case let .media(resultMedia, _) = content.content { if case let .media(resultMedia, _) = content.content {
media.append(resultMedia) media.append(resultMedia)
mediaProgress[mediaId] = 1.0
} }
} }
} }
@ -153,7 +171,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
cacheReferenceKey: nil cacheReferenceKey: nil
)) ))
} else { } else {
return .progress(normalizedProgress) return .progress(PendingMessageUploadedContentProgress(progress: normalizedProgress, mediaProgress: mediaProgress))
} }
} }
} }
@ -464,7 +482,7 @@ if "".isEmpty {
flags |= 1 << 1 flags |= 1 << 1
} }
} }
return .single(.progress(1.0)) return .single(.progress(PendingMessageUploadedContentProgress(progress: 1.0)))
|> then( |> 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))) .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: []) 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 var updatedAttributes = currentMessage.attributes
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
}
}
if markTransformedMedia {
if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){ if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){
let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute
updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia])) updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia]))
} else { } else {
updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: Int64.random(in: Int64.min ... Int64.max), flags: [.transformedMedia], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: [])) 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: currentMessage.media)) }
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) return .done(media)
@ -526,7 +574,7 @@ if "".isEmpty {
|> mapToSignal { transformResult -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { transformResult -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
switch transformResult { switch transformResult {
case .pending: case .pending:
return .single(.progress(0.0)) return .single(.progress(PendingMessageUploadedContentProgress(progress: 0.0)))
case let .done(transformedMedia): case let .done(transformedMedia):
let transformedImage = (transformedMedia as? TelegramMediaImage) ?? image let transformedImage = (transformedMedia as? TelegramMediaImage) ?? image
guard let largestRepresentation = largestImageRepresentation(transformedImage.representations) else { guard let largestRepresentation = largestImageRepresentation(transformedImage.representations) else {
@ -543,7 +591,7 @@ if "".isEmpty {
|> mapToSignal { next -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { next -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
switch next { switch next {
case let .progress(progress): case let .progress(progress):
return .single(.progress(progress)) return .single(.progress(PendingMessageUploadedContentProgress(progress: progress)))
case let .inputFile(file): case let .inputFile(file):
var flags: Int32 = 0 var flags: Int32 = 0
var ttlSeconds: Int32? 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( |> 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))) .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: []) 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 var updatedAttributes = currentMessage.attributes
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
}
}
if markTransformedMedia {
if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){ if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){
let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute
updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia])) updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia]))
} else { } else {
updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: Int64.random(in: Int64.min ... Int64.max), flags: [.transformedMedia], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: [])) 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: currentMessage.media)) }
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) return .done(media)
@ -901,7 +979,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|> mapToSignal { content, fileAndThumbnailResult, resourceStatus -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { content, fileAndThumbnailResult, resourceStatus -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
guard let content = content else { guard let content = content else {
if let resourceStatus = resourceStatus, case let .Fetching(_, progress) = resourceStatus { 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() return .complete()
} }
@ -911,7 +989,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
if passFetchProgress { if passFetchProgress {
progress = 0.33 + progress * 0.67 progress = 0.33 + progress * 0.67
} }
return .single(.progress(progress)) return .single(.progress(PendingMessageUploadedContentProgress(progress: progress)))
case let .inputFile(inputFile): case let .inputFile(inputFile):
if case let .done(file, thumbnail) = fileAndThumbnailResult { if case let .done(file, thumbnail) = fileAndThumbnailResult {
var flags: Int32 = 0 var flags: Int32 = 0

View File

@ -54,7 +54,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
let uploadedMedia: Signal<PendingMessageUploadedContentResult?, NoError> let uploadedMedia: Signal<PendingMessageUploadedContentResult?, NoError>
switch media { switch media {
case .keep: case .keep:
uploadedMedia = .single(.progress(0.0)) uploadedMedia = .single(.progress(PendingMessageUploadedContentProgress(progress: 0.0)))
|> then(.single(nil)) |> then(.single(nil))
case let .update(media): case let .update(media):
let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in
@ -69,12 +69,12 @@ 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) 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) { if let uploadSignal = generateUploadSignal(forceReupload) {
uploadedMedia = .single(.progress(0.027)) uploadedMedia = .single(.progress(PendingMessageUploadedContentProgress(progress: 0.027)))
|> then(uploadSignal) |> then(uploadSignal)
|> map { result -> PendingMessageUploadedContentResult? in |> map { result -> PendingMessageUploadedContentResult? in
switch result { switch result {
case let .progress(value): case let .progress(value):
return .progress(max(value, 0.027)) return .progress(PendingMessageUploadedContentProgress(progress: max(value.progress, 0.027)))
case let .content(content): case let .content(content):
return .content(content) return .content(content)
} }
@ -93,7 +93,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
if let uploadedMediaResult = uploadedMediaResult { if let uploadedMediaResult = uploadedMediaResult {
switch uploadedMediaResult { switch uploadedMediaResult {
case let .progress(value): case let .progress(value):
return .single(.progress(value)) return .single(.progress(value.progress))
case let .content(content): case let .content(content):
pendingMediaContent = content.content pendingMediaContent = content.content
} }

View File

@ -206,7 +206,7 @@ public func standaloneSendEnqueueMessages(
switch result.result { switch result.result {
case let .progress(value): case let .progress(value):
allDone = false allDone = false
progressSum += value progressSum += value.progress
case let .content(content): case let .content(content):
allResults.append((content, result.media)) allResults.append((content, result.media))
} }

View File

@ -6,8 +6,23 @@ import MtProtoKit
public struct PendingMessageStatus: Equatable { public struct PendingMessageStatus: Equatable {
public let isRunning: Bool public struct Progress: Equatable {
public let progress: Float 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: Progress
} }
private enum PendingMessageState { private enum PendingMessageState {
@ -364,7 +379,7 @@ public final class PendingMessageManager {
self.messageContexts[id] = messageContext 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 { if status != messageContext.status {
messageContext.status = status messageContext.status = status
for subscriber in messageContext.statusSubscribers.copyItems() { for subscriber in messageContext.statusSubscribers.copyItems() {
@ -618,7 +633,7 @@ public final class PendingMessageManager {
switch next { switch next {
case let .progress(progress): case let .progress(progress):
if let current = strongSelf.messageContexts[messageId] { 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 current.status = status
for subscriber in current.statusSubscribers.copyItems() { for subscriber in current.statusSubscribers.copyItems() {
subscriber(current.status, current.error) 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<PendingMessageUploadedContentResult, PendingMessageUploadError>) { private func beginUploadingMessage(messageContext: PendingMessageContext, id: MessageId, threadId: Int64?, groupId: Int64?, uploadSignal: Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>) {
messageContext.state = .uploading(groupId: groupId) 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 messageContext.status = status
for subscriber in messageContext.statusSubscribers.copyItems() { for subscriber in messageContext.statusSubscribers.copyItems() {
subscriber(messageContext.status, messageContext.error) subscriber(messageContext.status, messageContext.error)
@ -678,7 +693,7 @@ public final class PendingMessageManager {
switch next { switch next {
case let .progress(progress): case let .progress(progress):
if let current = strongSelf.messageContexts[id] { 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 current.status = status
for subscriber in current.statusSubscribers.copyItems() { for subscriber in current.statusSubscribers.copyItems() {
subscriber(current.status, current.error) subscriber(current.status, current.error)
@ -712,7 +727,7 @@ public final class PendingMessageManager {
if case let .waitingForUploadToStart(groupId, uploadSignal) = context.state { if case let .waitingForUploadToStart(groupId, uploadSignal) = context.state {
if self.canBeginUploadingMessage(id: contextId, type: context.contentType ?? .media) { if self.canBeginUploadingMessage(id: contextId, type: context.contentType ?? .media) {
context.state = .uploading(groupId: groupId) 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 context.status = status
for subscriber in context.statusSubscribers.copyItems() { for subscriber in context.statusSubscribers.copyItems() {
subscriber(context.status, context.error) subscriber(context.status, context.error)
@ -734,7 +749,7 @@ public final class PendingMessageManager {
switch next { switch next {
case let .progress(progress): case let .progress(progress):
if let current = strongSelf.messageContexts[contextId] { 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 current.status = status
for subscriber in current.statusSubscribers.copyItems() { for subscriber in current.statusSubscribers.copyItems() {
subscriber(context.status, context.error) subscriber(context.status, context.error)

View File

@ -1093,7 +1093,7 @@ func _internal_uploadStoryImpl(
|> mapToSignal { result -> Signal<StoryUploadResult, NoError> in |> mapToSignal { result -> Signal<StoryUploadResult, NoError> in
switch result { switch result {
case let .progress(progress): case let .progress(progress):
return .single(.progress(progress)) return .single(.progress(progress.progress))
case let .content(content): case let .content(content):
return postbox.transaction { transaction -> Signal<StoryUploadResult, NoError> in return postbox.transaction { transaction -> Signal<StoryUploadResult, NoError> in
let privacyRules = apiInputPrivacyRules(privacy: privacy, transaction: transaction) let privacyRules = apiInputPrivacyRules(privacy: privacy, transaction: transaction)
@ -1278,7 +1278,7 @@ func _internal_editStory(account: Account, peerId: PeerId, id: Int32, media: Eng
return contentSignal return contentSignal
|> mapToSignal { result -> Signal<StoryUploadResult, NoError> in |> mapToSignal { result -> Signal<StoryUploadResult, NoError> in
if let result = result, case let .progress(progress) = result { if let result = result, case let .progress(progress) = result {
return .single(.progress(progress)) return .single(.progress(progress.progress))
} }
let inputMedia: Api.InputMedia? let inputMedia: Api.InputMedia?

View File

@ -5911,6 +5911,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
item.controllerInteraction.openMessageContextMenu(item.message, true, self, subFrame, nil, nil) item.controllerInteraction.openMessageContextMenu(item.message, true, self, subFrame, nil, nil)
} }
override public func makeProgress() -> Promise<Bool>? {
return self.unlockButtonNode?.makeProgress()
}
override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? { override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? {
if let result = self.reactionButtonsNode?.reactionTargetView(value: value) { if let result = self.reactionButtonsNode?.reactionTargetView(value: value) {
return result return result

View File

@ -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 }) 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 |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in
if let pendingStatus = pendingStatus { if let pendingStatus = pendingStatus {
var progress = pendingStatus.progress var progress = pendingStatus.progress.progress
if pendingStatus.isRunning { if pendingStatus.isRunning {
progress = max(progress, 0.27) progress = max(progress, 0.27)
} }

View File

@ -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 }) 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 |> map { resourceStatus, pendingStatus -> (MediaResourceStatus, MediaResourceStatus?) in
if let pendingStatus = pendingStatus { 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) return (.Fetching(isActive: pendingStatus.isRunning, progress: adjustedProgress), resourceStatus)
} else { } else {
return (resourceStatus, nil) 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 }) 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 |> map { resourceStatus, pendingStatus -> (MediaResourceStatus, MediaResourceStatus?) in
if let pendingStatus = pendingStatus { 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) return (.Fetching(isActive: pendingStatus.isRunning, progress: adjustedProgress), resourceStatus)
} else { } else {
return (resourceStatus, nil) return (resourceStatus, nil)

View File

@ -847,7 +847,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
item.controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes) item.controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes)
} }
case .payment: case .payment:
item.controllerInteraction.openCheckoutOrReceipt(item.message.id) item.controllerInteraction.openCheckoutOrReceipt(item.message.id, nil)
case let .urlAuth(url, buttonId): case let .urlAuth(url, buttonId):
item.controllerInteraction.requestMessageActionUrlAuth(url, .message(id: item.message.id, buttonId: buttonId)) item.controllerInteraction.requestMessageActionUrlAuth(url, .message(id: item.message.id, buttonId: buttonId))
case .setupPoll: case .setupPoll:
@ -881,6 +881,10 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
open func openMessageContextMenu() { open func openMessageContextMenu() {
} }
open func makeProgress() -> Promise<Bool>? {
return nil
}
open func targetReactionView(value: MessageReaction.Reaction) -> UIView? { open func targetReactionView(value: MessageReaction.Reaction) -> UIView? {
return nil return nil
} }

View File

@ -56,7 +56,8 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
case .automaticPlayback: case .automaticPlayback:
openChatMessageMode = .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 self.interactiveImageNode.activateAgeRestrictedMedia = { [weak self] in

View File

@ -239,9 +239,12 @@ public class ChatMessageStarsMediaInfoNode: ASDisplayNode {
} else { } else {
text = NSMutableAttributedString(string: "Purchased", font: textFont, textColor: .white) text = NSMutableAttributedString(string: "Purchased", font: textFont, textColor: .white)
} }
var offset: CGFloat = 0.0
if let range = text.string.range(of: "⭐️") { 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(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)) 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)) 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) 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 textNode.textNode.frame = textFrame
return node return node

View File

@ -16,6 +16,7 @@ import AnimationCache
import MultiAnimationRenderer import MultiAnimationRenderer
import ComponentFlow import ComponentFlow
import ChatControllerInteraction import ChatControllerInteraction
import HierarchyTrackingLayer
public class ChatMessageUnlockMediaNode: ASDisplayNode { public class ChatMessageUnlockMediaNode: ASDisplayNode {
public class Arguments { public class Arguments {
@ -63,11 +64,14 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode {
private let contentNode: HighlightTrackingButtonNode private let contentNode: HighlightTrackingButtonNode
private let backgroundNode: NavigationBackgroundNode private let backgroundNode: NavigationBackgroundNode
private var textNode: TextNodeWithEntities? private var textNode: TextNodeWithEntities?
private var loadingView: LoadingEffectView?
private var pressed = { } private var pressed = { }
private var absolutePosition: (CGRect, CGSize)? private var absolutePosition: (CGRect, CGSize)?
private var currentProgressDisposable: Disposable?
override public init() { override public init() {
self.contentNode = HighlightTrackingButtonNode() self.contentNode = HighlightTrackingButtonNode()
@ -83,10 +87,41 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode {
self.contentNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.contentNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
} }
deinit {
self.currentProgressDisposable?.dispose()
}
@objc private func buttonPressed() { @objc private func buttonPressed() {
self.pressed() self.pressed()
} }
public func makeProgress() -> Promise<Bool> {
let progress = Promise<Bool>()
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) { public class func asyncLayout(_ maybeNode: ChatMessageUnlockMediaNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode) {
let textNodeLayout = TextNodeWithEntities.asyncLayout(maybeNode?.textNode) 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.backgroundNode.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate)
node.contentNode.frame = CGRect(origin: CGPoint(), size: size) 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 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)
}
}
}

View File

@ -557,7 +557,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
break break
} }
} }
}, openCheckoutOrReceipt: { _ in }, openCheckoutOrReceipt: { _, _ in
}, openSearch: { }, openSearch: {
}, setupReply: { _ in }, setupReply: { _ in
}, canSetupReply: { _ in }, canSetupReply: { _ in

View File

@ -428,7 +428,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
return nil return nil
}, chatControllerNode: { }, chatControllerNode: {
return nil 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 }, canSetupReply: { _ in
return .none return .none
}, canSendMessages: { }, canSendMessages: {

View File

@ -207,7 +207,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public let presentGlobalOverlayController: (ViewController, Any?) -> Void public let presentGlobalOverlayController: (ViewController, Any?) -> Void
public let callPeer: (PeerId, Bool) -> Void public let callPeer: (PeerId, Bool) -> Void
public let longTap: (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void public let longTap: (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void
public let openCheckoutOrReceipt: (MessageId) -> Void public let openCheckoutOrReceipt: (MessageId, OpenMessageParams?) -> Void
public let openSearch: () -> Void public let openSearch: () -> Void
public let setupReply: (MessageId) -> Void public let setupReply: (MessageId) -> Void
public let canSetupReply: (Message) -> ChatControllerInteractionSwipeAction public let canSetupReply: (Message) -> ChatControllerInteractionSwipeAction
@ -336,7 +336,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void,
callPeer: @escaping (PeerId, Bool) -> Void, callPeer: @escaping (PeerId, Bool) -> Void,
longTap: @escaping (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void,
openCheckoutOrReceipt: @escaping (MessageId) -> Void, openCheckoutOrReceipt: @escaping (MessageId, OpenMessageParams?) -> Void,
openSearch: @escaping () -> Void, openSearch: @escaping () -> Void,
setupReply: @escaping (MessageId) -> Void, setupReply: @escaping (MessageId) -> Void,
canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction,

View File

@ -3295,7 +3295,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
default: default:
break break
} }
}, openCheckoutOrReceipt: { _ in }, openCheckoutOrReceipt: { _, _ in
}, openSearch: { }, openSearch: {
}, setupReply: { _ in }, setupReply: { _ in
}, canSetupReply: { _ in }, canSetupReply: { _ in

View File

@ -245,12 +245,18 @@ public final class StarsAvatarComponent: Component {
self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment") self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
iconOffset = 2.0 iconOffset = 2.0
case .ads: 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.backgroundView.isHidden = false
self.iconView.isHidden = false self.iconView.isHidden = false
self.avatarNode.isHidden = true self.avatarNode.isHidden = true
self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment") self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white)
iconOffset = 2.0
case .premiumBot: case .premiumBot:
iconInset = 7.0 iconInset = 7.0
self.backgroundView.image = generateGradientFilledCircleImage( self.backgroundView.image = generateGradientFilledCircleImage(

View File

@ -720,12 +720,15 @@ public final class StarsImageComponent: Component {
iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment") iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
iconOffset = 5.0 iconOffset = 5.0
case .ads: case .ads:
iconBackgroundView.image = generateFilledCircleImage( iconBackgroundView.image = generateGradientFilledCircleImage(
diameter: imageSize.width, 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") iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white)
iconOffset = 5.0
case .premiumBot: case .premiumBot:
iconInset = 15.0 iconInset = 15.0
iconBackgroundView.image = generateGradientFilledCircleImage( iconBackgroundView.image = generateGradientFilledCircleImage(

View File

@ -216,17 +216,16 @@ private final class StarsTransactionSheetContent: CombinedComponent {
via = strings.Stars_Transaction_FragmentWithdrawal_Subtitle via = strings.Stars_Transaction_FragmentWithdrawal_Subtitle
} }
case .ads: case .ads:
//TODO:localize titleText = strings.Stars_Transaction_TelegramAds_Title
titleText = "Stars Withdrawal" via = strings.Stars_Transaction_TelegramAds_Subtitle
via = "Telegram Ads"
case .unsupported: case .unsupported:
titleText = strings.Stars_Transaction_Unsupported_Title titleText = strings.Stars_Transaction_Unsupported_Title
via = nil via = nil
} }
if !transaction.media.isEmpty { if !transaction.media.isEmpty {
var description: String = "" var description: String = ""
var photoCount: Int = 0 var photoCount: Int32 = 0
var videoCount: Int = 0 var videoCount: Int32 = 0
for media in transaction.media { for media in transaction.media {
if let _ = media as? TelegramMediaFile { if let _ = media as? TelegramMediaFile {
videoCount += 1 videoCount += 1
@ -234,33 +233,22 @@ private final class StarsTransactionSheetContent: CombinedComponent {
photoCount += 1 photoCount += 1
} }
} }
//TODO:localize
if photoCount > 0 && videoCount > 0 { if photoCount > 0 && videoCount > 0 {
if photoCount > 1 { description += strings.Stars_Transaction_MediaAnd(strings.Stars_Transaction_Photos(photoCount), strings.Stars_Transaction_Videos(videoCount)).string
description += "\(photoCount) photos**"
} else {
description += "**\(photoCount) photo**"
}
description += " and "
if videoCount > 1 {
description += "**\(videoCount) videos**"
} else {
description += "**\(videoCount) video**"
}
} else if photoCount > 0 { } else if photoCount > 0 {
if photoCount > 1 { if photoCount > 1 {
description += "**\(photoCount) photos**" description += strings.Stars_Transaction_Photos(photoCount)
} else { } else {
description += "**Photo**" description += strings.Stars_Transaction_SinglePhoto
} }
} else if videoCount > 0 { } else if videoCount > 0 {
if videoCount > 1 { if videoCount > 1 {
description += "**\(videoCount) videos**" description += strings.Stars_Transaction_Videos(videoCount)
} else { } else {
description += "**Video**" description += strings.Stars_Transaction_SingleVideo
} }
} }
descriptionText = description.replacingOccurrences(of: "**", with: "") descriptionText = description
} else { } else {
descriptionText = transaction.description ?? "" descriptionText = transaction.description ?? ""
} }
@ -416,7 +404,6 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} }
if let messageId { if let messageId {
//TODO:localize
let peerName: String let peerName: String
if case let .transaction(_, parentPeer) = component.subject { if case let .transaction(_, parentPeer) = component.subject {
if parentPeer.id == component.context.account.peerId { if parentPeer.id == component.context.account.peerId {
@ -432,7 +419,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
peerName = "" peerName = ""
} }
tableItems.append(.init( tableItems.append(.init(
id: "via", id: "media",
title: strings.Stars_Transaction_Media, title: strings.Stars_Transaction_Media,
component: AnyComponent( component: AnyComponent(
Button( Button(

View File

@ -209,8 +209,7 @@ final class StarsTransactionsListPanelComponent: Component {
switch item.peer { switch item.peer {
case let .peer(peer): case let .peer(peer):
if !item.media.isEmpty { if !item.media.isEmpty {
//TODO:localize itemTitle = environment.strings.Stars_Intro_Transaction_MediaPurchase
itemTitle = "Media Purchase"
itemSubtitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) itemSubtitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
} else if let title = item.title { } else if let title = item.title {
itemTitle = title itemTitle = title
@ -237,8 +236,8 @@ final class StarsTransactionsListPanelComponent: Component {
itemTitle = environment.strings.Stars_Intro_Transaction_PremiumBotTopUp_Title itemTitle = environment.strings.Stars_Intro_Transaction_PremiumBotTopUp_Title
itemSubtitle = environment.strings.Stars_Intro_Transaction_PremiumBotTopUp_Subtitle itemSubtitle = environment.strings.Stars_Intro_Transaction_PremiumBotTopUp_Subtitle
case .ads: case .ads:
itemTitle = "Withdrawal" itemTitle = environment.strings.Stars_Intro_Transaction_TelegramAds_Title
itemSubtitle = "via Telegram Ads" itemSubtitle = environment.strings.Stars_Intro_Transaction_TelegramAds_Subtitle
case .unsupported: case .unsupported:
itemTitle = environment.strings.Stars_Intro_Transaction_Unsupported_Title itemTitle = environment.strings.Stars_Intro_Transaction_Unsupported_Title
itemSubtitle = nil itemSubtitle = nil

View File

@ -335,10 +335,9 @@ private final class SheetContent: CombinedComponent {
let amount = component.invoice.totalAmount let amount = component.invoice.totalAmount
let infoText: String let infoText: String
if !component.extendedMedia.isEmpty { if !component.extendedMedia.isEmpty {
//TODO:localize
var description: String = "" var description: String = ""
var photoCount: Int = 0 var photoCount: Int32 = 0
var videoCount: Int = 0 var videoCount: Int32 = 0
for media in component.extendedMedia { for media in component.extendedMedia {
if case let .preview(_, _, videoDuration) = media, videoDuration != nil { if case let .preview(_, _, videoDuration) = media, videoDuration != nil {
videoCount += 1 videoCount += 1
@ -347,28 +346,18 @@ private final class SheetContent: CombinedComponent {
} }
} }
if photoCount > 0 && videoCount > 0 { if photoCount > 0 && videoCount > 0 {
if photoCount > 1 { description = strings.Stars_Transfer_MediaAnd("**\(strings.Stars_Transfer_Photos(photoCount))**", "**\(strings.Stars_Transfer_Videos(videoCount))**").string
description += "**\(photoCount) photos**"
} else {
description += "**\(photoCount) photo**"
}
description += " and "
if videoCount > 1 {
description += "**\(videoCount) videos**"
} else {
description += "**\(videoCount) video**"
}
} else if photoCount > 0 { } else if photoCount > 0 {
if photoCount > 1 { if photoCount > 1 {
description += "**\(photoCount) photos**" description += "**\(strings.Stars_Transfer_Photos(photoCount))**"
} else { } else {
description += "**photo**" description += "**\(strings.Stars_Transfer_SinglePhoto)**"
} }
} else if videoCount > 0 { } else if videoCount > 0 {
if videoCount > 1 { if videoCount > 1 {
description += "**\(videoCount) videos**" description += "**\(strings.Stars_Transfer_Videos(videoCount))**"
} else { } else {
description += "**video**" description += "**\(strings.Stars_Transfer_SingleVideo)**"
} }
} }
infoText = strings.Stars_Transfer_UnlockInfo( infoText = strings.Stars_Transfer_UnlockInfo(

View File

@ -96,6 +96,8 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
return true return true
} else if let file = media as? TelegramMediaFile, file.isVideo { } else if let file = media as? TelegramMediaFile, file.isVideo {
return true return true
} else if media is TelegramMediaPaidContent {
return true
} }
return false return false
}) })

View File

@ -905,7 +905,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch extendedMedia { switch extendedMedia {
case .preview: case .preview:
if displayVoiceMessageDiscardAlert() { if displayVoiceMessageDiscardAlert() {
strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id) strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, params)
return true return true
} else { } else {
return false return false
@ -917,7 +917,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch extendedMedia { switch extendedMedia {
case .preview: case .preview:
if displayVoiceMessageDiscardAlert() { if displayVoiceMessageDiscardAlert() {
strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id) strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, nil)
return true return true
} else { } else {
return false return false
@ -2586,7 +2586,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let self { if let self {
self.openLinkLongTap(action, params: params) self.openLinkLongTap(action, params: params)
} }
}, openCheckoutOrReceipt: { [weak self] messageId in }, openCheckoutOrReceipt: { [weak self] messageId, params in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -2610,6 +2610,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
for media in message.media { for media in message.media {
if let paidContent = media as? TelegramMediaPaidContent { if let paidContent = media as? TelegramMediaPaidContent {
let progressSignal = Signal<Never, NoError> { _ 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() strongSelf.chatDisplayNode.dismissInput()
let inputData = Promise<BotCheckoutController.InputData?>() let inputData = Promise<BotCheckoutController.InputData?>()
inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .message(message.id)) 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 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 }) 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) strongSelf.push(controller)
progressDisposable.dispose()
}) })
} }
} else if let invoice = media as? TelegramMediaInvoice { } else if let invoice = media as? TelegramMediaInvoice {

View File

@ -955,7 +955,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes) controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes)
} }
case .payment: case .payment:
controllerInteraction.openCheckoutOrReceipt(message.id) controllerInteraction.openCheckoutOrReceipt(message.id, nil)
case let .urlAuth(url, buttonId): case let .urlAuth(url, buttonId):
controllerInteraction.requestMessageActionUrlAuth(url, .message(id: message.id, buttonId: buttonId)) controllerInteraction.requestMessageActionUrlAuth(url, .message(id: message.id, buttonId: buttonId))
case .setupPoll: case .setupPoll:

View File

@ -118,7 +118,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
}, presentGlobalOverlayController: { _, _ in }, presentGlobalOverlayController: { _, _ in
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, longTap: { _, _ in }, longTap: { _, _ in
}, openCheckoutOrReceipt: { _ in }, openCheckoutOrReceipt: { _, _ in
}, openSearch: { }, openSearch: {
}, setupReply: { _ in }, setupReply: { _ in
}, canSetupReply: { _ in }, canSetupReply: { _ in

View File

@ -1722,7 +1722,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return nil return nil
}, chatControllerNode: { }, chatControllerNode: {
return nil 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 }, canSetupReply: { _ in
return .none return .none
}, canSendMessages: { }, canSendMessages: {

View File

@ -8,6 +8,24 @@ import PhotoResources
import ImageCompression import ImageCompression
public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, media: AnyMediaReference, opportunistic: Bool) -> Signal<AnyMediaReference?, NoError> { public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, media: AnyMediaReference, opportunistic: Bool) -> Signal<AnyMediaReference?, NoError> {
if let paidContent = media.media as? TelegramMediaPaidContent {
var signals: [Signal<AnyMediaReference?, NoError>] = []
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<AnyMediaReference?, NoError> 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 { switch media.media {
case let file as TelegramMediaFile: case let file as TelegramMediaFile:
let signal = Signal<MediaResourceData, NoError> { subscriber in let signal = Signal<MediaResourceData, NoError> { subscriber in