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.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";

View File

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

View File

@ -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))
}

View File

@ -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 = ""
}

View File

@ -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

View File

@ -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 {

View File

@ -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<PendingMessageUploadedContentResult, PendingMessageUploadError>? {
if let paidContent = media as? TelegramMediaPaidContent {
var signals: [Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>] = []
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<PendingMessageUploadedContentResult, PendingMessageUploadError> 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<PendingMessageUploadedContentResult, PendingMessageUploadError> 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<PendingMessageUploadedContentResult, PendingMessageUploadError> 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

View File

@ -54,7 +54,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
let uploadedMedia: Signal<PendingMessageUploadedContentResult?, NoError>
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<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { 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<PendingMessageUploadedContentResult?, NoError> 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<PeerId, Peer>) in

View File

@ -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))
}

View File

@ -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<PendingMessageUploadedContentResult, PendingMessageUploadError>) {
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)

View File

@ -1093,7 +1093,7 @@ func _internal_uploadStoryImpl(
|> mapToSignal { result -> Signal<StoryUploadResult, NoError> 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<StoryUploadResult, NoError> 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<StoryUploadResult, NoError> in
if let result = result, case let .progress(progress) = result {
return .single(.progress(progress))
return .single(.progress(progress.progress))
}
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)
}
override public func makeProgress() -> Promise<Bool>? {
return self.unlockButtonNode?.makeProgress()
}
override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? {
if let result = self.reactionButtonsNode?.reactionTargetView(value: value) {
return result

View File

@ -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

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 })
|> 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)
}

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 })
|> 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)

View File

@ -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<Bool>? {
return nil
}
open func targetReactionView(value: MessageReaction.Reaction) -> UIView? {
return nil
}

View File

@ -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

View File

@ -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

View File

@ -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<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) {
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)
}
}
}

View File

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

View File

@ -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: {

View File

@ -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,

View File

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

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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

View File

@ -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(

View File

@ -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
})

View File

@ -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<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()
let inputData = Promise<BotCheckoutController.InputData?>()
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 {

View File

@ -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:

View File

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

View File

@ -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: {

View File

@ -8,6 +8,24 @@ import PhotoResources
import ImageCompression
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 {
case let file as TelegramMediaFile:
let signal = Signal<MediaResourceData, NoError> { subscriber in