Paid media content

This commit is contained in:
Ilya Laktyushin 2024-06-18 18:57:32 +04:00
parent 2292e8cac8
commit 40a4183095
39 changed files with 823 additions and 240 deletions

View File

@ -12318,6 +12318,7 @@ Sorry for the inconvenience.";
"Stars.Transfer.PurchasedText" = "You acquired **%1$@** in **%2$@** for **%3$@**.";
"Stars.Transfer.Purchased.Stars_1" = "%@ Star";
"Stars.Transfer.Purchased.Stars_any" = "%@ Stars";
"Stars.Transfer.UnlockedText" = "You unlocked media for **%1$@**.";
"Stars.Transfer.UnlockInfo" = "Do you want to unlock media for **%1$@**?";
"Stars.Transfer.Balance" = "Balance";
@ -12351,6 +12352,7 @@ Sorry for the inconvenience.";
"Stars.BotRevenue.Revenue.Title" = "Revenue";
"Stars.BotRevenue.Proceeds.Title" = "Proceeds Overview";
"Stars.BotRevenue.Proceeds.Available" = "Available Balance";
"Stars.BotRevenue.Proceeds.Current" = "Total Balance";
"Stars.BotRevenue.Proceeds.Total" = "Total Lifetime Proceeds";
"Stars.BotRevenue.Proceeds.Info" = "Stars from your total balance become available for spending on ads and rewards 21 days after they are earned.";

View File

@ -337,6 +337,12 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
if messageText.isEmpty, case let .Loaded(content) = webpage.content {
messageText = content.displayUrl
}
case _ as TelegramMediaPaidContent:
if message.text.isEmpty {
messageText = "Paid Media"
} else {
messageText = "🖼 \(messageText)"
}
default:
break
}

View File

@ -111,7 +111,10 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati
}
}
for media in message.media {
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
if let paidContent = media as? TelegramMediaPaidContent, let extendedMedia = paidContent.extendedMedia.first, case let .full(fullMedia) = extendedMedia {
standalone = true
galleryMedia = fullMedia
} else if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
standalone = true
galleryMedia = fullMedia
} else if let action = media as? TelegramMediaAction {

View File

@ -1622,7 +1622,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
var hasExternalShare = true
for media in currentMessage.media {
if let invoice = media as? TelegramMediaInvoice, let _ = invoice.extendedMedia {
if let _ = media as? TelegramMediaPaidContent {
hasExternalShare = false
break
} else if let invoice = media as? TelegramMediaInvoice, let _ = invoice.extendedMedia {
hasExternalShare = false
break
}

View File

@ -44,7 +44,9 @@ private func tagsForMessage(_ message: Message) -> MessageTags? {
}
private func galleryMediaForMedia(media: Media) -> Media? {
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
if let paidContent = media as? TelegramMediaPaidContent, let extendedMedia = paidContent.extendedMedia.first, case let .full(fullMedia) = extendedMedia {
return fullMedia
} else if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
return fullMedia
} else if let media = media as? TelegramMediaImage {
return media

View File

@ -140,7 +140,9 @@ class ChatImageGalleryItem: GalleryItem {
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop, translateToLanguage: self.translateToLanguage, peerIsCopyProtected: self.peerIsCopyProtected, isSecret: self.isSecret)
for media in self.message.media {
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia, let image = fullMedia as? TelegramMediaImage {
if let paidContent = media as? TelegramMediaPaidContent, let extendedMedia = paidContent.extendedMedia.first, case let .full(fullMedia) = extendedMedia, let image = fullMedia as? TelegramMediaImage {
node.setImage(userLocation: .peer(self.message.id.peerId), imageReference: .message(message: MessageReference(self.message), media: image))
} else if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia, let image = fullMedia as? TelegramMediaImage {
node.setImage(userLocation: .peer(self.message.id.peerId), imageReference: .message(message: MessageReference(self.message), media: image))
} else if let image = media as? TelegramMediaImage {
node.setImage(userLocation: .peer(self.message.id.peerId), imageReference: .message(message: MessageReference(self.message), media: image))

View File

@ -942,6 +942,7 @@
NSInteger num = 0;
bool grouping = selectionContext.grouping;
NSNumber *price;
bool hasAnyTimers = false;
if (editingContext != nil || grouping)
{
@ -950,6 +951,9 @@
if ([editingContext timerForItem:asset] != nil) {
hasAnyTimers = true;
}
if (price == nil) {
price = [editingContext priceForItem:asset];
}
id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:asset];
if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]]) {
TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments;
@ -1057,6 +1061,9 @@
else if (groupedId != nil && !hasAnyTimers)
dict[@"groupedId"] = groupedId;
if (price != nil)
dict[@"price"] = price;
if (spoiler) {
dict[@"spoiler"] = @true;
}
@ -1137,6 +1144,9 @@
else if (groupedId != nil && !hasAnyTimers)
dict[@"groupedId"] = groupedId;
if (price != nil)
dict[@"price"] = price;
if (spoiler) {
dict[@"spoiler"] = @true;
}
@ -1261,6 +1271,9 @@
if (groupedId != nil)
dict[@"groupedId"] = groupedId;
if (price != nil)
dict[@"price"] = price;
if (spoiler) {
dict[@"spoiler"] = @true;
}
@ -1334,6 +1347,9 @@
else if (groupedId != nil && !hasAnyTimers)
dict[@"groupedId"] = groupedId;
if (price != nil)
dict[@"price"] = price;
if (spoiler) {
dict[@"spoiler"] = @true;
}
@ -1415,6 +1431,9 @@
if (timer != nil)
dict[@"timer"] = timer;
if (price != nil)
dict[@"price"] = price;
if (spoiler) {
dict[@"spoiler"] = @true;
}

View File

@ -134,13 +134,15 @@ private final class LegacyAssetItemWrapper: NSObject {
let item: LegacyAssetItem
let timer: Int?
let spoiler: Bool?
let price: Int64?
let groupedId: Int64?
let uniqueId: String?
init(item: LegacyAssetItem, timer: Int?, spoiler: Bool?, groupedId: Int64?, uniqueId: String?) {
init(item: LegacyAssetItem, timer: Int?, spoiler: Bool?, price: Int64?, groupedId: Int64?, uniqueId: String?) {
self.item = item
self.timer = timer
self.spoiler = spoiler
self.price = price
self.groupedId = groupedId
self.uniqueId = uniqueId
@ -159,6 +161,9 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str
return nil
}
} ?? []
let price = dict["price"] as? Int64
if (dict["type"] as! NSString) == "editedPhoto" || (dict["type"] as! NSString) == "capturedPhoto" {
let image = dict["image"] as! UIImage
let thumbnail = dict["previewImage"] as? UIImage
@ -168,10 +173,10 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str
let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.path
if let url = url {
let dimensions = image.size
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: 4.0), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: false, asAnimation: true, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: 4.0), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: false, asAnimation: true, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
}
} else {
result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
}
return result
} else if (dict["type"] as! NSString) == "cloudPhoto" {
@ -192,9 +197,9 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str
name = customName
}
result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .asset(asset.backingAsset), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: nil, spoiler: nil, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .asset(asset.backingAsset), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: nil, spoiler: nil, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
} else {
result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .asset(asset.backingAsset), thumbnail: thumbnail, caption: caption, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .asset(asset.backingAsset), thumbnail: thumbnail, caption: caption, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
}
return result
} else if (dict["type"] as! NSString) == "file" {
@ -215,12 +220,12 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str
let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue!
let duration = (dict["duration"]! as AnyObject).doubleValue!
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: tempFileUrl.path, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: nil, caption: caption, asFile: false, asAnimation: true, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: tempFileUrl.path, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: nil, caption: caption, asFile: false, asAnimation: true, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
return result
}
var result: [AnyHashable: Any] = [:]
result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .tempFile(tempFileUrl.path), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .tempFile(tempFileUrl.path), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
return result
}
} else if (dict["type"] as! NSString) == "video" {
@ -232,13 +237,13 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str
if let asset = dict["asset"] as? TGMediaAsset {
var result: [AnyHashable: Any] = [:]
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
return result
} else if let url = (dict["url"] as? String) ?? (dict["url"] as? URL)?.absoluteString {
let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue!
let duration = (dict["duration"]! as AnyObject).doubleValue!
var result: [AnyHashable: Any] = [:]
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
return result
}
} else if (dict["type"] as! NSString) == "cameraVideo" {
@ -254,7 +259,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str
let dimensions = previewImage.pixelSize()
let duration = (dict["duration"]! as AnyObject).doubleValue!
var result: [AnyHashable: Any] = [:]
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, spoiler: (dict["spoiler"] as? NSNumber)?.boolValue, price: price, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value, uniqueId: uniqueId)
return result
}
}
@ -356,6 +361,15 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
return Signal { subscriber in
let disposable = SSignal.combineSignals(signals).start(next: { anyValues in
var messages: [LegacyAssetPickerEnqueueMessage] = []
struct EnqueuePaidMessage {
var price: Int64
var text: String
var entities: [MessageTextEntity]
var media: [Media]
}
var paidMessage: EnqueuePaidMessage?
outer: for item in (anyValues as! NSArray) {
if let item = (item as? NSDictionary)?.object(forKey: "item") as? LegacyAssetItemWrapper {
@ -436,7 +450,26 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
}
}
}
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: false))
if let price = item.price {
if var current = paidMessage {
if current.text.isEmpty && !text.string.isEmpty {
current.text = text.string
current.entities = entities
}
current.media.append(media)
paidMessage = current
} else {
paidMessage = EnqueuePaidMessage(
price: price,
text: text.string,
entities: entities,
media: [media]
)
}
} else {
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: false))
}
}
}
case let .asset(asset):
@ -510,7 +543,25 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
}
}
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: false))
if let price = item.price {
if var current = paidMessage {
if current.text.isEmpty && !text.string.isEmpty {
current.text = text.string
current.entities = entities
}
current.media.append(media)
paidMessage = current
} else {
paidMessage = EnqueuePaidMessage(
price: price,
text: text.string,
entities: entities,
media: [media]
)
}
} else {
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: false))
}
}
}
}
@ -560,7 +611,25 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
}
}
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: false))
if let price = item.price {
if var current = paidMessage {
if current.text.isEmpty && !text.string.isEmpty {
current.text = text.string
current.entities = entities
}
current.media.append(media)
paidMessage = current
} else {
paidMessage = EnqueuePaidMessage(
price: price,
text: text.string,
entities: entities,
media: [media]
)
}
} else {
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: false))
}
}
case .tempFile:
break
@ -612,7 +681,25 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
}
}
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: true))
if let price = item.price {
if var current = paidMessage {
if current.text.isEmpty && !text.string.isEmpty {
current.text = text.string
current.entities = entities
}
current.media.append(media)
paidMessage = current
} else {
paidMessage = EnqueuePaidMessage(
price: price,
text: text.string,
entities: entities,
media: [media]
)
}
} else {
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: true))
}
case let .asset(asset):
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
@ -647,7 +734,25 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
}
}
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: true))
if let price = item.price {
if var current = paidMessage {
if current.text.isEmpty && !text.string.isEmpty {
current.text = text.string
current.entities = entities
}
current.media.append(media)
paidMessage = current
} else {
paidMessage = EnqueuePaidMessage(
price: price,
text: text.string,
entities: entities,
media: [media]
)
}
} else {
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: true))
}
default:
break
}
@ -822,11 +927,58 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
}
}
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: asFile))
if let price = item.price {
if var current = paidMessage {
if current.text.isEmpty && !text.string.isEmpty {
current.text = text.string
current.entities = entities
}
current.media.append(media)
paidMessage = current
} else {
paidMessage = EnqueuePaidMessage(
price: price,
text: text.string,
entities: entities,
media: [media]
)
}
} else {
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: asFile))
}
}
}
}
if let paidMessage {
var attributes: [MessageAttribute] = []
if !paidMessage.entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: paidMessage.entities))
}
messages.append(
LegacyAssetPickerEnqueueMessage(
message: .message(
text: paidMessage.text,
attributes: attributes,
inlineStickers: [:],
mediaReference: .standalone(
media: TelegramMediaPaidContent(
amount: paidMessage.price,
extendedMedia: paidMessage.media.map { .full(media: $0) }
)),
threadId: nil,
replyToMessageId: nil,
replyToStoryId: nil,
localGroupingKey: nil,
correlationId: nil,
bubbleUpEmojiOrStickersets: []
),
uniqueId: nil,
isFile: false
)
)
}
subscriber.putNext(messages)
subscriber.putCompletion()
}, error: { _ in

View File

@ -552,9 +552,7 @@ final class MediaPickerGridItemNode: GridItemNode {
let priceSignal = Signal<Int64?, NoError> { subscriber in
if let signal = editingContext.priceSignal(forIdentifier: asset.localIdentifier) {
let disposable = signal.start(next: { next in
if let next = next as? Int64 {
subscriber.putNext(next)
}
subscriber.putNext(next as? Int64)
}, error: { _ in
}, completed: nil)!
@ -681,6 +679,10 @@ final class MediaPickerGridItemNode: GridItemNode {
}
backgroundNode.addSubnode(labelNode)
backgroundNode.addSubnode(iconNode)
self.priceBackgroundNode = backgroundNode
self.priceLabelNode = labelNode
self.priceIconNode = iconNode
}
labelNode.attributedText = NSAttributedString(string: "\(price)", font: Font.semibold(15.0), textColor: .white)

View File

@ -1838,6 +1838,21 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
}
if let selectionContext = self.interaction?.selectionState, let editingContext = self.interaction?.editingState {
var price: Int64?
for case let item as TGMediaEditableItem in selectionContext.selectedItems() {
if price == nil, let itemPrice = editingContext.price(for: item) as? Int64 {
price = itemPrice
break
}
}
if let price, let item = item as? TGMediaEditableItem {
editingContext.setPrice(NSNumber(value: price), for: item)
}
}
return true
}
@ -2013,7 +2028,6 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.updateSelectionState(count: Int32(selectionContext.count()))
if case let .assets(_, mode) = self.subject, case .createSticker = mode {
let _ = cutoutAvailability(context: context).startStandalone()
}
@ -2434,9 +2448,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
var hasSpoilers = false
var price: Int64?
var hasGeneric = false
if let selectionContext = self.interaction?.selectionState, let editingContext = self.interaction?.editingState {
for case let item as TGMediaEditableItem in selectionContext.selectedItems() {
if price == nil, let itemPrice = editingContext.price(for: item) as? Int64 {
price = itemPrice
}
if editingContext.spoiler(for: item) {
hasSpoilers = true
} else {
@ -2456,8 +2474,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
)
|> deliverOnMainQueue
|> map { [weak self] grouped, isCaptionAboveMediaAvailable -> ContextController.Items in
guard let self else {
return ContextController.Items(content: .list([]))
}
var items: [ContextMenuItem] = []
if !hasSpoilers {
if !hasSpoilers && price == nil {
items.append(.action(ContextMenuActionItem(text: selectionCount > 1 ? strings.Attachment_SendAsFiles : strings.Attachment_SendAsFile, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/File"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
@ -2466,42 +2487,45 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, parameters: nil, completion: {})
})))
}
if selectionCount > 1 {
// items.append(.action(ContextMenuActionItem(text: "Send Without Grouping", icon: { theme in
// return generateTintedImage(image: UIImage(bundleImageName: "Media Grid/GroupingOff"), color: theme.contextMenu.primaryColor)
// }, action: { [weak self] _, f in
// f(.default)
//
// self?.groupedValue = false
// self?.controllerNode.send(asFile: false, silently: false, scheduleTime: nil, animated: true, parameters: nil, completion: {})
// })))
if !items.isEmpty {
items.append(.separator)
}
items.append(.action(ContextMenuActionItem(text: strings.Attachment_Grouped, icon: { theme in
if !grouped {
return nil
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
if selectionCount > 1, price == nil {
items.append(.action(ContextMenuActionItem(text: "Send Without Grouping", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Media Grid/GroupingOff"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
self?.groupedValue = true
})))
items.append(.action(ContextMenuActionItem(text: strings.Attachment_Ungrouped, icon: { theme in
if grouped {
return nil
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
self?.groupedValue = false
self?.controllerNode.send(asFile: false, silently: false, scheduleTime: nil, animated: true, parameters: nil, completion: {})
})))
// if !items.isEmpty {
// items.append(.separator)
// }
// items.append(.action(ContextMenuActionItem(text: strings.Attachment_Grouped, icon: { theme in
// if !grouped {
// return nil
// }
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
// }, action: { [weak self] _, f in
// f(.default)
//
// self?.groupedValue = true
// })))
// items.append(.action(ContextMenuActionItem(text: strings.Attachment_Ungrouped, icon: { theme in
// if grouped {
// return nil
// }
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
// }, action: { [weak self] _, f in
// f(.default)
//
// self?.groupedValue = false
// })))
}
let isPaidAvailable = !"".isEmpty
var isPaidAvailable = false
if let peer = self.peer, case let .channel(channel) = peer, case .broadcast = channel.info {
isPaidAvailable = true
}
if isSpoilerAvailable || isPaidAvailable || (selectionCount > 0 && isCaptionAboveMediaAvailable) {
if !items.isEmpty {
items.append(.separator)
@ -2509,7 +2533,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
if isCaptionAboveMediaAvailable {
var mediaCaptionIsAbove = false
if let interaction = self?.interaction {
if let interaction = self.interaction {
mediaCaptionIsAbove = interaction.captionIsAboveMedia
}
@ -2526,7 +2550,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
})))
}
if isSpoilerAvailable {
if isSpoilerAvailable && price == nil {
items.append(.action(ContextMenuActionItem(text: hasGeneric ? strings.Attachment_EnableSpoiler : strings.Attachment_DisableSpoiler, icon: { _ in return nil }, iconAnimation: ContextMenuActionItem.IconAnimation(
name: "anim_spoiler",
loop: true
@ -2544,7 +2568,16 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
})))
}
if isPaidAvailable {
items.append(.action(ContextMenuActionItem(text: "Make This Content Paid", icon: { theme in
let title: String
let titleLayout: ContextMenuActionItemTextLayout
if let price {
title = "Edit Price"
titleLayout = .secondLineWithValue("\(price) Stars")
} else {
title = "Make This Content Paid"
titleLayout = .singleLine
}
items.append(.action(ContextMenuActionItem(text: title, textLayout: titleLayout, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Paid"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)

View File

@ -130,12 +130,27 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
}
}
self.spoilerDisposable.set((spoilerSignal
|> deliverOnMainQueue).start(next: { [weak self] hasSpoiler in
let priceSignal = Signal<Int64?, NoError> { subscriber in
if let signal = editingState.priceSignal(forIdentifier: asset.uniqueIdentifier) {
let disposable = signal.start(next: { next in
subscriber.putNext(next as? Int64)
}, error: { _ in
}, completed: nil)!
return ActionDisposable {
disposable.dispose()
}
} else {
return EmptyDisposable
}
}
self.spoilerDisposable.set((combineLatest(spoilerSignal, priceSignal)
|> deliverOnMainQueue).start(next: { [weak self] hasSpoiler, price in
guard let strongSelf = self else {
return
}
strongSelf.updateHasSpoiler(hasSpoiler)
strongSelf.updateHasSpoiler(hasSpoiler, price: price)
}))
}
@ -163,14 +178,14 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
}
private var didSetupSpoiler = false
private func updateHasSpoiler(_ hasSpoiler: Bool) {
private func updateHasSpoiler(_ hasSpoiler: Bool, price: Int64?) {
var animated = true
if !self.didSetupSpoiler {
animated = false
self.didSetupSpoiler = true
}
if hasSpoiler {
if hasSpoiler || price != nil {
if self.spoilerNode == nil {
let spoilerNode = SpoilerOverlayNode(enableAnimations: self.enableAnimations)
self.insertSubnode(spoilerNode, aboveSubnode: self.imageNode)

View File

@ -384,6 +384,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1759532989] = { return Api.InputMedia.parse_inputMediaGeoLive($0) }
dict[-104578748] = { return Api.InputMedia.parse_inputMediaGeoPoint($0) }
dict[1080028941] = { return Api.InputMedia.parse_inputMediaInvoice($0) }
dict[-1436147773] = { return Api.InputMedia.parse_inputMediaPaidMedia($0) }
dict[-1279654347] = { return Api.InputMedia.parse_inputMediaPhoto($0) }
dict[-440664550] = { return Api.InputMedia.parse_inputMediaPhotoExternal($0) }
dict[261416433] = { return Api.InputMedia.parse_inputMediaPoll($0) }
@ -599,6 +600,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-626162256] = { return Api.MessageMedia.parse_messageMediaGiveaway($0) }
dict[-963047320] = { return Api.MessageMedia.parse_messageMediaGiveawayResults($0) }
dict[-156940077] = { return Api.MessageMedia.parse_messageMediaInvoice($0) }
dict[-1467669359] = { return Api.MessageMedia.parse_messageMediaPaidMedia($0) }
dict[1766936791] = { return Api.MessageMedia.parse_messageMediaPhoto($0) }
dict[1272375192] = { return Api.MessageMedia.parse_messageMediaPoll($0) }
dict[1758159491] = { return Api.MessageMedia.parse_messageMediaStory($0) }
@ -874,7 +876,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
dict[2033461574] = { return Api.StarsRevenueStatus.parse_starsRevenueStatus($0) }
dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($0) }
dict[-1442789224] = { return Api.StarsTransaction.parse_starsTransaction($0) }
dict[-901959922] = { return Api.StarsTransaction.parse_starsTransaction($0) }
dict[-670195363] = { return Api.StarsTransactionPeer.parse_starsTransactionPeer($0) }
dict[-1269320843] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAppStore($0) }
dict[-382740222] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerFragment($0) }
@ -995,7 +997,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1442983757] = { return Api.Update.parse_updateLangPack($0) }
dict[1180041828] = { return Api.Update.parse_updateLangPackTooLong($0) }
dict[1448076945] = { return Api.Update.parse_updateLoginToken($0) }
dict[1517529484] = { return Api.Update.parse_updateMessageExtendedMedia($0) }
dict[-710666460] = { return Api.Update.parse_updateMessageExtendedMedia($0) }
dict[1318109142] = { return Api.Update.parse_updateMessageID($0) }
dict[-1398708869] = { return Api.Update.parse_updateMessagePoll($0) }
dict[619974263] = { return Api.Update.parse_updateMessagePollVote($0) }

View File

@ -9,6 +9,7 @@ public extension Api {
case inputMediaGeoLive(flags: Int32, geoPoint: Api.InputGeoPoint, heading: Int32?, period: Int32?, proximityNotificationRadius: Int32?)
case inputMediaGeoPoint(geoPoint: Api.InputGeoPoint)
case inputMediaInvoice(flags: Int32, title: String, description: String, photo: Api.InputWebDocument?, invoice: Api.Invoice, payload: Buffer, provider: String?, providerData: Api.DataJSON, startParam: String?, extendedMedia: Api.InputMedia?)
case inputMediaPaidMedia(starsAmount: Int64, extendedMedia: [Api.InputMedia])
case inputMediaPhoto(flags: Int32, id: Api.InputPhoto, ttlSeconds: Int32?)
case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?)
case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?)
@ -95,6 +96,17 @@ public extension Api {
if Int(flags) & Int(1 << 1) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {extendedMedia!.serialize(buffer, true)}
break
case .inputMediaPaidMedia(let starsAmount, let extendedMedia):
if boxed {
buffer.appendInt32(-1436147773)
}
serializeInt64(starsAmount, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(extendedMedia.count))
for item in extendedMedia {
item.serialize(buffer, true)
}
break
case .inputMediaPhoto(let flags, let id, let ttlSeconds):
if boxed {
buffer.appendInt32(-1279654347)
@ -210,6 +222,8 @@ public extension Api {
return ("inputMediaGeoPoint", [("geoPoint", geoPoint as Any)])
case .inputMediaInvoice(let flags, let title, let description, let photo, let invoice, let payload, let provider, let providerData, let startParam, let extendedMedia):
return ("inputMediaInvoice", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("payload", payload as Any), ("provider", provider as Any), ("providerData", providerData as Any), ("startParam", startParam as Any), ("extendedMedia", extendedMedia as Any)])
case .inputMediaPaidMedia(let starsAmount, let extendedMedia):
return ("inputMediaPaidMedia", [("starsAmount", starsAmount as Any), ("extendedMedia", extendedMedia as Any)])
case .inputMediaPhoto(let flags, let id, let ttlSeconds):
return ("inputMediaPhoto", [("flags", flags as Any), ("id", id as Any), ("ttlSeconds", ttlSeconds as Any)])
case .inputMediaPhotoExternal(let flags, let url, let ttlSeconds):
@ -399,6 +413,22 @@ public extension Api {
return nil
}
}
public static func parse_inputMediaPaidMedia(_ reader: BufferReader) -> InputMedia? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.InputMedia]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputMedia.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputMedia.inputMediaPaidMedia(starsAmount: _1!, extendedMedia: _2!)
}
else {
return nil
}
}
public static func parse_inputMediaPhoto(_ reader: BufferReader) -> InputMedia? {
var _1: Int32?
_1 = reader.readInt32()

View File

@ -718,6 +718,7 @@ public extension Api {
case messageMediaGiveaway(flags: Int32, channels: [Int64], countriesIso2: [String]?, prizeDescription: String?, quantity: Int32, months: Int32, untilDate: Int32)
case messageMediaGiveawayResults(flags: Int32, channelId: Int64, additionalPeersCount: Int32?, launchMsgId: Int32, winnersCount: Int32, unclaimedCount: Int32, winners: [Int64], months: Int32, prizeDescription: String?, untilDate: Int32)
case messageMediaInvoice(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: Api.MessageExtendedMedia?)
case messageMediaPaidMedia(starsAmount: Int64, extendedMedia: [Api.MessageExtendedMedia])
case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?)
case messageMediaPoll(poll: Api.Poll, results: Api.PollResults)
case messageMediaStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?)
@ -834,6 +835,17 @@ public extension Api {
serializeString(startParam, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 4) != 0 {extendedMedia!.serialize(buffer, true)}
break
case .messageMediaPaidMedia(let starsAmount, let extendedMedia):
if boxed {
buffer.appendInt32(-1467669359)
}
serializeInt64(starsAmount, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(extendedMedia.count))
for item in extendedMedia {
item.serialize(buffer, true)
}
break
case .messageMediaPhoto(let flags, let photo, let ttlSeconds):
if boxed {
buffer.appendInt32(1766936791)
@ -907,6 +919,8 @@ public extension Api {
return ("messageMediaGiveawayResults", [("flags", flags as Any), ("channelId", channelId as Any), ("additionalPeersCount", additionalPeersCount as Any), ("launchMsgId", launchMsgId as Any), ("winnersCount", winnersCount as Any), ("unclaimedCount", unclaimedCount as Any), ("winners", winners as Any), ("months", months as Any), ("prizeDescription", prizeDescription as Any), ("untilDate", untilDate as Any)])
case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam, let extendedMedia):
return ("messageMediaInvoice", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("receiptMsgId", receiptMsgId as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("startParam", startParam as Any), ("extendedMedia", extendedMedia as Any)])
case .messageMediaPaidMedia(let starsAmount, let extendedMedia):
return ("messageMediaPaidMedia", [("starsAmount", starsAmount as Any), ("extendedMedia", extendedMedia as Any)])
case .messageMediaPhoto(let flags, let photo, let ttlSeconds):
return ("messageMediaPhoto", [("flags", flags as Any), ("photo", photo as Any), ("ttlSeconds", ttlSeconds as Any)])
case .messageMediaPoll(let poll, let results):
@ -1149,6 +1163,22 @@ public extension Api {
return nil
}
}
public static func parse_messageMediaPaidMedia(_ reader: BufferReader) -> MessageMedia? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.MessageExtendedMedia]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageExtendedMedia.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageMedia.messageMediaPaidMedia(starsAmount: _1!, extendedMedia: _2!)
}
else {
return nil
}
}
public static func parse_messageMediaPhoto(_ reader: BufferReader) -> MessageMedia? {
var _1: Int32?
_1 = reader.readInt32()

View File

@ -708,13 +708,13 @@ public extension Api {
}
public extension Api {
enum StarsTransaction: TypeConstructorDescription {
case starsTransaction(flags: Int32, id: String, stars: Int64, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?)
case starsTransaction(flags: Int32, id: String, stars: Int64, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl):
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId):
if boxed {
buffer.appendInt32(-1442789224)
buffer.appendInt32(-901959922)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(id, buffer: buffer, boxed: false)
@ -726,14 +726,16 @@ public extension Api {
if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)}
if Int(flags) & Int(1 << 5) != 0 {serializeInt32(transactionDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 5) != 0 {serializeString(transactionUrl!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 7) != 0 {serializeBytes(botPayload!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 8) != 0 {serializeInt32(msgId!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl):
return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("stars", stars as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any)])
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId):
return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("stars", stars as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any), ("botPayload", botPayload as Any), ("msgId", msgId as Any)])
}
}
@ -762,6 +764,10 @@ public extension Api {
if Int(_1!) & Int(1 << 5) != 0 {_9 = reader.readInt32() }
var _10: String?
if Int(_1!) & Int(1 << 5) != 0 {_10 = parseString(reader) }
var _11: Buffer?
if Int(_1!) & Int(1 << 7) != 0 {_11 = parseBytes(reader) }
var _12: Int32?
if Int(_1!) & Int(1 << 8) != 0 {_12 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -772,8 +778,10 @@ public extension Api {
let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 5) == 0) || _10 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10)
let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil
let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 {
return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12)
}
else {
return nil

View File

@ -321,7 +321,7 @@ public extension Api {
case updateLangPack(difference: Api.LangPackDifference)
case updateLangPackTooLong(langCode: String)
case updateLoginToken
case updateMessageExtendedMedia(peer: Api.Peer, msgId: Int32, extendedMedia: Api.MessageExtendedMedia)
case updateMessageExtendedMedia(peer: Api.Peer, msgId: Int32, extendedMedia: [Api.MessageExtendedMedia])
case updateMessageID(id: Int32, randomId: Int64)
case updateMessagePoll(flags: Int32, pollId: Int64, poll: Api.Poll?, results: Api.PollResults)
case updateMessagePollVote(pollId: Int64, peer: Api.Peer, options: [Buffer], qts: Int32)
@ -1039,11 +1039,15 @@ public extension Api {
break
case .updateMessageExtendedMedia(let peer, let msgId, let extendedMedia):
if boxed {
buffer.appendInt32(1517529484)
buffer.appendInt32(-710666460)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
extendedMedia.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(extendedMedia.count))
for item in extendedMedia {
item.serialize(buffer, true)
}
break
case .updateMessageID(let id, let randomId):
if boxed {
@ -3241,9 +3245,9 @@ public extension Api {
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Api.MessageExtendedMedia?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.MessageExtendedMedia
var _3: [Api.MessageExtendedMedia]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageExtendedMedia.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil

View File

@ -120,7 +120,7 @@ enum AccountStateMutationOperation {
case UpdateAttachMenuBots
case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String)
case UpdateConfig
case UpdateExtendedMedia(MessageId, Api.MessageExtendedMedia)
case UpdateExtendedMedia(MessageId, [Api.MessageExtendedMedia])
case ResetForumTopic(topicId: MessageId, data: StoreMessageHistoryThreadData, pts: Int32)
case UpdateStory(peerId: PeerId, story: Api.StoryItem)
case UpdateReadStories(peerId: PeerId, maxId: Int32)
@ -652,7 +652,7 @@ struct AccountMutableState {
self.addOperation(.UpdateConfig)
}
mutating func updateExtendedMedia(_ messageId: MessageId, extendedMedia: Api.MessageExtendedMedia) {
mutating func updateExtendedMedia(_ messageId: MessageId, extendedMedia: [Api.MessageExtendedMedia]) {
self.addOperation(.UpdateExtendedMedia(messageId, extendedMedia))
}

View File

@ -226,6 +226,7 @@ private var declaredEncodables: Void = {
declareEncodable(OutgoingQuickReplyMessageAttribute.self, f: { OutgoingQuickReplyMessageAttribute(decoder: $0) })
declareEncodable(EffectMessageAttribute.self, f: { EffectMessageAttribute(decoder: $0) })
declareEncodable(FactCheckMessageAttribute.self, f: { FactCheckMessageAttribute(decoder: $0) })
declareEncodable(TelegramMediaPaidContent.self, f: { TelegramMediaPaidContent(decoder: $0) })
return
}()

View File

@ -391,33 +391,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
if (flags & (1 << 1)) != 0 {
parsedFlags.insert(.shippingAddressRequested)
}
let extendedMedia: TelegramExtendedMedia?
if let apiExtendedMedia = apiExtendedMedia {
switch apiExtendedMedia {
case let .messageExtendedMediaPreview(_, width, height, thumb, videoDuration):
var dimensions: PixelDimensions?
if let width = width, let height = height {
dimensions = PixelDimensions(width: width, height: height)
}
var immediateThumbnailData: Data?
if let thumb = thumb, case let .photoStrippedSize(_, bytes) = thumb {
immediateThumbnailData = bytes.makeData()
}
extendedMedia = .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: videoDuration)
case let .messageExtendedMedia(apiMedia):
let (media, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, peerId)
if let media = media {
extendedMedia = .full(media: media)
} else {
extendedMedia = nil
}
}
} else {
extendedMedia = nil
}
return (TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, extendedMedia: extendedMedia, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion), nil, nil, nil, nil)
return (TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, extendedMedia: apiExtendedMedia.flatMap({ TelegramExtendedMedia(apiExtendedMedia: $0, peerId: peerId) }), flags: parsedFlags, version: TelegramMediaInvoice.lastVersion), nil, nil, nil, nil)
case let .messageMediaPoll(poll, results):
switch poll {
case let .poll(id, flags, question, answers, closePeriod, _):
@ -464,6 +438,8 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
flags.insert(.refunded)
}
return (TelegramMediaGiveawayResults(flags: flags, launchMessageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: launchMsgId), additionalChannelsCount: additionalPeersCount ?? 0, winnersPeerIds: winners.map { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }, winnersCount: winnersCount, unclaimedCount: unclaimedCount, months: months, untilDate: untilDate, prizeDescription: prizeDescription), nil, nil, nil, nil)
case let .messageMediaPaidMedia(starsAmount, apiExtendedMedia):
return (TelegramMediaPaidContent(amount: starsAmount, extendedMedia: apiExtendedMedia.compactMap({ TelegramExtendedMedia(apiExtendedMedia: $0, peerId: peerId) })), nil, nil, nil, nil)
}
}

View File

@ -0,0 +1,27 @@
import Foundation
import Postbox
import TelegramApi
extension TelegramExtendedMedia {
init?(apiExtendedMedia: Api.MessageExtendedMedia, peerId: PeerId) {
switch apiExtendedMedia {
case let .messageExtendedMediaPreview(_, width, height, thumb, videoDuration):
var dimensions: PixelDimensions?
if let width = width, let height = height {
dimensions = PixelDimensions(width: width, height: height)
}
var immediateThumbnailData: Data?
if let thumb = thumb, case let .photoStrippedSize(_, bytes) = thumb {
immediateThumbnailData = bytes.makeData()
}
self = .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: videoDuration)
case let .messageExtendedMedia(apiMedia):
let (media, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, peerId)
if let media = media {
self = .full(media: media)
} else {
return nil
}
}
}
}

View File

@ -119,6 +119,44 @@ 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>] = []
for case let .full(media) in paidContent.extendedMedia {
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 {
signals.append(uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, forceNoBigParts: false, peerId: peerId, messageId: messageId, text: "", attributes: [], autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, file: file))
}
}
return combineLatest(signals)
|> map { results -> PendingMessageUploadedContentResult in
var currentProgress: Float = 0.0
var media: [Api.InputMedia] = []
for result in results {
switch result {
case let .progress(progress):
currentProgress += progress
case let .content(content):
if case let .media(resultMedia, _) = content.content {
media.append(resultMedia)
}
}
}
let normalizedProgress = currentProgress / Float(results.count)
if media.count == results.count {
return .content(PendingMessageUploadedContentAndReuploadInfo(
content: .media(.inputMediaPaidMedia(
starsAmount: paidContent.amount,
extendedMedia: media
), text),
reuploadInfo: nil,
cacheReferenceKey: nil
))
} else {
return .progress(normalizedProgress)
}
}
}
if let image = media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) {
if peerId.namespace == Namespaces.Peer.SecretChat, let resource = largest.resource as? SecretFileMediaResource {
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(.inputEncryptedFile(id: resource.fileId, accessHash: resource.accessHash), resource.decryptedSize, resource.key), reuploadInfo: nil, cacheReferenceKey: nil)))

View File

@ -4633,42 +4633,26 @@ func replayFinalState(
transaction.updateMessage(messageId, update: { currentMessage in
var media = currentMessage.media
let invoice = media.first(where: { $0 is TelegramMediaInvoice }) as? TelegramMediaInvoice
let currentExtendedMedia = invoice?.extendedMedia
let paidContent = media.first(where: { $0 is TelegramMediaPaidContent }) as? TelegramMediaPaidContent
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
let updatedExtendedMedia: TelegramExtendedMedia?
switch apiExtendedMedia {
case let .messageExtendedMediaPreview(_, width, height, thumb, videoDuration):
var dimensions: PixelDimensions?
if let width = width, let height = height {
dimensions = PixelDimensions(width: width, height: height)
}
var immediateThumbnailData: Data?
if let thumb = thumb, case let .photoStrippedSize(_, bytes) = thumb {
immediateThumbnailData = bytes.makeData()
}
updatedExtendedMedia = .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: videoDuration)
case let .messageExtendedMedia(apiMedia):
let (media, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, currentMessage.id.peerId)
if let media = media {
updatedExtendedMedia = .full(media: media)
} else {
updatedExtendedMedia = currentExtendedMedia
}
}
let updatedExtendedMedia = apiExtendedMedia.compactMap { TelegramExtendedMedia(apiExtendedMedia: $0, peerId: messageId.peerId) }
if let updatedExtendedMedia = updatedExtendedMedia, var invoice = invoice {
if let currentExtendedMedia = currentExtendedMedia, case .full = currentExtendedMedia, case .preview = updatedExtendedMedia {
} else {
if let first = updatedExtendedMedia.first, case .full = first {
if var invoice = invoice {
media = media.filter { !($0 is TelegramMediaInvoice) }
invoice = invoice.withUpdatedExtendedMedia(updatedExtendedMedia)
invoice = invoice.withUpdatedExtendedMedia(first)
media.append(invoice)
}
if var paidContent = paidContent {
media = media.filter { !($0 is TelegramMediaPaidContent) }
paidContent = paidContent.withUpdatedExtendedMedia(updatedExtendedMedia)
media.append(paidContent)
}
}
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: currentMessage.attributes, media: media))

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 182
return 183
}
public func parseMessage(_ data: Data!) -> Any! {

View File

@ -0,0 +1,59 @@
import Foundation
import Postbox
public final class TelegramMediaPaidContent: Media, Equatable {
public var peerIds: [PeerId] = []
public var id: MediaId? {
return nil
}
public let amount: Int64
public let extendedMedia: [TelegramExtendedMedia]
public init(amount: Int64, extendedMedia: [TelegramExtendedMedia]) {
self.amount = amount
self.extendedMedia = extendedMedia
}
public init(decoder: PostboxDecoder) {
self.amount = decoder.decodeInt64ForKey("a", orElse: 0)
self.extendedMedia = (try? decoder.decodeObjectArrayWithCustomDecoderForKey("m", decoder: { TelegramExtendedMedia(decoder: $0) })) ?? []
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt64(self.amount, forKey: "a")
encoder.encodeObjectArray(self.extendedMedia, forKey: "m")
}
public static func ==(lhs: TelegramMediaPaidContent, rhs: TelegramMediaPaidContent) -> Bool {
return lhs.isEqual(to: rhs)
}
public func isEqual(to other: Media) -> Bool {
guard let other = other as? TelegramMediaPaidContent else {
return false
}
if self.amount != other.amount {
return false
}
if self.extendedMedia != other.extendedMedia {
return false
}
return true
}
public func isSemanticallyEqual(to other: Media) -> Bool {
return self.isEqual(to: other)
}
public func withUpdatedExtendedMedia(_ extendedMedia: [TelegramExtendedMedia]) -> TelegramMediaPaidContent {
return TelegramMediaPaidContent(
amount: self.amount,
extendedMedia: extendedMedia
)
}
}

View File

@ -19,6 +19,7 @@ public enum EngineMedia: Equatable {
case story(TelegramMediaStory)
case giveaway(TelegramMediaGiveaway)
case giveawayResults(TelegramMediaGiveawayResults)
case paidContent(TelegramMediaPaidContent)
}
public extension EngineMedia {
@ -56,6 +57,8 @@ public extension EngineMedia {
return giveaway.id
case let .giveawayResults(giveawayResults):
return giveawayResults.id
case let .paidContent(paidContent):
return paidContent.id
}
}
}
@ -95,6 +98,8 @@ public extension EngineMedia {
self = .giveaway(giveaway)
case let giveawayResults as TelegramMediaGiveawayResults:
self = .giveawayResults(giveawayResults)
case let paidContent as TelegramMediaPaidContent:
self = .paidContent(paidContent)
default:
preconditionFailure()
}
@ -134,6 +139,8 @@ public extension EngineMedia {
return giveaway
case let .giveawayResults(giveawayResults):
return giveawayResults
case let .paidContent(paidContent):
return paidContent
}
}
}

View File

@ -199,7 +199,7 @@ private final class StarsContextImpl {
return
}
var transactions = state.transactions
transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil, transactionDate: nil, transactionUrl: nil), at: 0)
transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil, transactionDate: nil, transactionUrl: nil, paidMessageId: nil), at: 0)
self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: state.balance + balance, transactions: transactions, canLoadMore: state.canLoadMore, isLoading: state.isLoading))
}
@ -238,8 +238,9 @@ private final class StarsContextImpl {
private extension StarsContext.State.Transaction {
init?(apiTransaction: Api.StarsTransaction, transaction: Transaction) {
switch apiTransaction {
case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl):
case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId):
let parsedPeer: StarsContext.State.Transaction.Peer
var paidMessageId: MessageId?
switch transactionPeer {
case .starsTransactionPeerAppStore:
parsedPeer = .appStore
@ -256,13 +257,19 @@ private extension StarsContext.State.Transaction {
return nil
}
parsedPeer = .peer(EnginePeer(peer))
if let messageId {
paidMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: messageId)
}
}
var flags: Flags = []
if (apiFlags & (1 << 3)) != 0 {
flags.insert(.isRefund)
}
self.init(flags: flags, id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl)
if (apiFlags & (1 << 6)) != 0 {
flags.insert(.isFailed)
}
self.init(flags: flags, id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl, paidMessageId: paidMessageId)
}
}
}
@ -280,6 +287,7 @@ public final class StarsContext {
public static let isRefund = Flags(rawValue: 1 << 0)
public static let isLocal = Flags(rawValue: 1 << 1)
public static let isPending = Flags(rawValue: 1 << 2)
public static let isFailed = Flags(rawValue: 1 << 3)
}
public enum Peer: Equatable {
@ -301,6 +309,7 @@ public final class StarsContext {
public let photo: TelegramMediaWebFile?
public let transactionDate: Int32?
public let transactionUrl: String?
public let paidMessageId: MessageId?
public init(
flags: Flags,
@ -312,7 +321,8 @@ public final class StarsContext {
description: String?,
photo: TelegramMediaWebFile?,
transactionDate: Int32?,
transactionUrl: String?
transactionUrl: String?,
paidMessageId: MessageId?
) {
self.flags = flags
self.id = id
@ -324,6 +334,7 @@ public final class StarsContext {
self.photo = photo
self.transactionDate = transactionDate
self.transactionUrl = transactionUrl
self.paidMessageId = paidMessageId
}
}

View File

@ -29,6 +29,7 @@ public enum MessageContentKindKey {
case invoice
case story
case giveaway
case paidContent
}
public enum MessageContentKind: Equatable {
@ -54,6 +55,7 @@ public enum MessageContentKind: Equatable {
case invoice(String)
case story
case giveaway
case paidContent
public func isSemanticallyEqual(to other: MessageContentKind) -> Bool {
switch self {
@ -189,6 +191,12 @@ public enum MessageContentKind: Equatable {
} else {
return false
}
case .paidContent:
if case .paidContent = other {
return true
} else {
return false
}
}
}
@ -238,6 +246,8 @@ public enum MessageContentKind: Equatable {
return .story
case .giveaway:
return .giveaway
case .paidContent:
return .paidContent
}
}
}
@ -445,6 +455,8 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
return (NSAttributedString(string: strings.Message_Story), true)
case .giveaway:
return (NSAttributedString(string: strings.Message_Giveaway), true)
case .paidContent:
return (NSAttributedString(string: "Paid Media"), true)
}
}

View File

@ -406,6 +406,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
attributes,
contentMediaValue,
nil,
nil,
.full,
associatedData.automaticDownloadPeerType,
associatedData.automaticDownloadPeerId,

View File

@ -118,7 +118,7 @@ public enum ChatMessageBubbleContentPosition {
public enum ChatMessageBubblePreparePosition {
case linear(top: ChatMessageBubbleRelativePosition, bottom: ChatMessageBubbleRelativePosition)
case mosaic(top: ChatMessageBubbleRelativePosition, bottom: ChatMessageBubbleRelativePosition)
case mosaic(top: ChatMessageBubbleRelativePosition, bottom: ChatMessageBubbleRelativePosition, index: Int?)
}
public struct ChatMessageBubbleContentTapAction {
@ -208,6 +208,8 @@ open class ChatMessageBubbleContentNode: ASDisplayNode {
return false
}
open var index: Int?
public weak var itemNode: ChatMessageItemNodeProtocol?
public weak var bubbleBackgroundNode: ChatMessageBackground?
public weak var bubbleBackdropNode: ChatMessageBubbleBackdrop?

View File

@ -78,9 +78,17 @@ import TelegramAnimatedStickerNode
import LottieMetal
private struct BubbleItemAttributes {
var index: Int?
var isAttachment: Bool
var neighborType: ChatMessageBubbleRelativePosition.NeighbourType
var neighborSpacing: ChatMessageBubbleRelativePosition.NeighbourSpacing
init(index: Int? = nil, isAttachment: Bool, neighborType: ChatMessageBubbleRelativePosition.NeighbourType, neighborSpacing: ChatMessageBubbleRelativePosition.NeighbourSpacing) {
self.index = index
self.isAttachment = isAttachment
self.neighborType = neighborType
self.neighborSpacing = neighborSpacing
}
}
private final class ChatMessageBubbleClippingNode: ASDisplayNode {
@ -123,7 +131,13 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
var isFile = false
inner: for media in message.media {
if let _ = media as? TelegramMediaImage {
if let media = media as? TelegramMediaPaidContent {
var index = 0
for _ in media.extendedMedia {
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(index: index, isAttachment: true, neighborType: .media, neighborSpacing: .default)))
index += 1
}
} else if let _ = media as? TelegramMediaImage {
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), message.text.isEmpty {
messageWithCaptionToAdd = (message, itemAttributes)
}
@ -1334,10 +1348,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
var currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = []
var currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, Int?, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = []
for contentNode in self.contentNodes {
if let message = contentNode.item?.message {
currentContentClassesPropertiesAndLayouts.append((message, type(of: contentNode) as AnyClass, contentNode.supportsMosaic, contentNode.asyncLayoutContent()))
currentContentClassesPropertiesAndLayouts.append((message, type(of: contentNode) as AnyClass, contentNode.supportsMosaic, contentNode.index, contentNode.asyncLayoutContent()))
} else {
assertionFailure()
}
@ -1388,7 +1402,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
private static func beginLayout(
selfReference: Weak<ChatMessageBubbleItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool,
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))],
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, Int?, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))],
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
viaMeasureLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
@ -1699,15 +1713,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
maximumContentWidth = max(0.0, maximumContentWidth)
var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = []
var addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?
var addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode, Int?)]?
for contentNodeItemValue in contentNodeMessagesAndClasses {
let contentNodeItem = contentNodeItemValue as (message: Message, type: AnyClass, attributes: ChatMessageEntryAttributes, bubbleAttributes: BubbleItemAttributes)
var found = false
for currentNodeItemValue in currentContentClassesPropertiesAndLayouts {
let currentNodeItem = currentNodeItemValue as (message: Message, type: AnyClass, supportsMosaic: Bool, currentLayout: (ChatMessageBubbleContentItem, ChatMessageItemLayoutConstants, ChatMessageBubblePreparePosition, Bool?, CGSize, CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))
let currentNodeItem = currentNodeItemValue as (message: Message, type: AnyClass, supportsMosaic: Bool, index: Int?, currentLayout: (ChatMessageBubbleContentItem, ChatMessageItemLayoutConstants, ChatMessageBubblePreparePosition, Bool?, CGSize, CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))
if currentNodeItem.type == contentNodeItem.type && currentNodeItem.message.stableId == contentNodeItem.message.stableId {
if currentNodeItem.type == contentNodeItem.type && currentNodeItem.index == contentNodeItem.bubbleAttributes.index && currentNodeItem.message.stableId == contentNodeItem.message.stableId {
contentPropertiesAndPrepareLayouts.append((contentNodeItem.message, currentNodeItem.supportsMosaic, contentNodeItem.attributes, contentNodeItem.bubbleAttributes, currentNodeItem.currentLayout))
found = true
break
@ -1715,11 +1729,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
if !found {
let contentNode = (contentNodeItem.type as! ChatMessageBubbleContentNode.Type).init()
contentNode.index = contentNodeItem.bubbleAttributes.index
contentPropertiesAndPrepareLayouts.append((contentNodeItem.message, contentNode.supportsMosaic, contentNodeItem.attributes, contentNodeItem.bubbleAttributes, contentNode.asyncLayoutContent()))
if addedContentNodes == nil {
addedContentNodes = []
}
addedContentNodes!.append((contentNodeItem.message, contentNodeItem.bubbleAttributes.isAttachment, contentNode))
addedContentNodes!.append((contentNodeItem.message, contentNodeItem.bubbleAttributes.isAttachment, contentNode, contentNodeItem.bubbleAttributes.index))
}
}
@ -1924,7 +1939,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let prepareContentPosition: ChatMessageBubblePreparePosition
if let mosaicRange = mosaicRange, mosaicRange.contains(index) {
prepareContentPosition = .mosaic(top: .None(.None(.Incoming)), bottom: index == (mosaicRange.upperBound - 1) ? bottomPosition : .None(.None(.Incoming)))
let mosaicIndex = index - mosaicRange.lowerBound
prepareContentPosition = .mosaic(top: .None(.None(.Incoming)), bottom: index == (mosaicRange.upperBound - 1) ? bottomPosition : .None(.None(.Incoming)), index: mosaicIndex)
} else {
let refinedBottomPosition: ChatMessageBubbleRelativePosition
if index == contentPropertiesAndPrepareLayouts.count - 1 {
@ -3071,7 +3087,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
replyInfoOriginY: CGFloat,
removedContentNodeIndices: [Int]?,
updatedContentNodeOrder: Bool,
addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?,
addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode, Int?)]?,
contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)],
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)],
contentContainerNodeFrames: [(UInt32, CGRect, Bool?, CGFloat)],
@ -3876,7 +3892,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
if let addedContentNodes = addedContentNodes {
for (contentNodeMessage, isAttachment, contentNode) in addedContentNodes {
for (contentNodeMessage, isAttachment, contentNode, _ ) in addedContentNodes {
updatedContentNodes.append(contentNode)
let contextSourceNode: ContextExtractedContentContainingNode
@ -3908,18 +3924,17 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
var sortedContentNodes: [ChatMessageBubbleContentNode] = []
outer: for contentItemValue in contentNodeMessagesAndClasses {
let contentItem = contentItemValue as (message: Message, type: AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)
let contentItem = contentItemValue as (message: Message, type: AnyClass, ChatMessageEntryAttributes, attributes: BubbleItemAttributes)
if let addedContentNodes = addedContentNodes {
for (contentNodeMessage, _, contentNode) in addedContentNodes {
if type(of: contentNode) == contentItem.type && contentNodeMessage.stableId == contentItem.message.stableId {
for (contentNodeMessage, _, contentNode, index) in addedContentNodes {
if type(of: contentNode) == contentItem.type && index == contentItem.attributes.index && contentNodeMessage.stableId == contentItem.message.stableId {
sortedContentNodes.append(contentNode)
continue outer
}
}
}
for contentNode in updatedContentNodes {
if type(of: contentNode) == contentItem.type && contentNode.item?.message.stableId == contentItem.message.stableId {
if type(of: contentNode) == contentItem.type && contentNode.index == contentItem.attributes.index && contentNode.item?.message.stableId == contentItem.message.stableId {
sortedContentNodes.append(contentNode)
continue outer
}

View File

@ -437,6 +437,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
private var message: Message?
private var attributes: ChatMessageEntryAttributes?
private var media: Media?
private var mediaIndex: Int?
private var themeAndStrings: (PresentationTheme, PresentationStrings, String, Bool)?
private var sizeCalculation: InteractiveMediaNodeSizeCalculation?
private var wideLayout: Bool?
@ -719,6 +720,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
} else {
if let invoice = self.media as? TelegramMediaInvoice, let _ = invoice.extendedMedia {
self.activateLocalContent(.default)
} else if let _ = self.media as? TelegramMediaPaidContent {
self.activateLocalContent(.default)
} else if let storyMedia = media as? TelegramMediaStory, let storyItem = self.message?.associatedStories[storyMedia.storyId]?.get(Stories.StoredItem.self) {
if case let .item(item) = storyItem, let mediaValue = item.media {
let _ = mediaValue
@ -732,7 +735,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
}
}
public func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
public func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ mediaIndex: Int?, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
let currentMessage = self.message
let currentMedia = self.media
let imageLayout = self.imageNode.asyncLayout()
@ -746,7 +749,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
let currentAutomaticDownload = self.automaticDownload
let currentAutomaticPlayback = self.automaticPlayback
return { [weak self] context, presentationData, dateTimeFormat, message, associatedData, attributes, media, dateAndStatus, automaticDownload, peerType, peerId, sizeCalculation, layoutConstants, contentMode, presentationContext in
return { [weak self] context, presentationData, dateTimeFormat, message, associatedData, attributes, media, mediaIndex, dateAndStatus, automaticDownload, peerType, peerId, sizeCalculation, layoutConstants, contentMode, presentationContext in
let _ = peerType
var nativeSize: CGSize
@ -845,8 +848,18 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
case .color, .gradient, .emoticon:
unboundSize = CGSize(width: 128.0, height: 128.0)
}
} else if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
switch extendedMedia {
} else {
var extendedMedia: TelegramExtendedMedia?
if let invoice = media as? TelegramMediaInvoice, let selectedMedia = invoice.extendedMedia {
extendedMedia = selectedMedia
} else if let paidContent = media as? TelegramMediaPaidContent {
let selectedMediaIndex = mediaIndex ?? 0
if selectedMediaIndex < paidContent.extendedMedia.count {
extendedMedia = paidContent.extendedMedia[selectedMediaIndex]
}
}
if let extendedMedia {
switch extendedMedia {
case let .preview(dimensions, _, _):
if let dimensions = dimensions {
unboundSize = CGSize(width: max(10.0, floor(dimensions.cgSize.width * 0.5)), height: max(10.0, floor(dimensions.cgSize.height * 0.5)))
@ -880,9 +893,10 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
} else {
unboundSize = CGSize(width: 54.0, height: 54.0)
}
}
} else {
unboundSize = CGSize(width: 54.0, height: 54.0)
}
} else {
unboundSize = CGSize(width: 54.0, height: 54.0)
}
switch sizeCalculation {
@ -1090,7 +1104,18 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
if mediaUpdated || isSendingUpdated || automaticPlaybackUpdated {
var media = media
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
var extendedMedia: TelegramExtendedMedia?
if let invoice = media as? TelegramMediaInvoice, let selectedMedia = invoice.extendedMedia {
extendedMedia = selectedMedia
} else if let paidContent = media as? TelegramMediaPaidContent {
let selectedMediaIndex = mediaIndex ?? 0
if selectedMediaIndex < paidContent.extendedMedia.count {
extendedMedia = paidContent.extendedMedia[selectedMediaIndex]
}
}
if let extendedMedia {
switch extendedMedia {
case let .preview(_, immediateThumbnailData, _):
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
@ -1418,7 +1443,16 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
var isExtendedMedia = false
if statusUpdated {
var media = media
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
var extendedMedia: TelegramExtendedMedia?
if let invoice = media as? TelegramMediaInvoice, let selectedMedia = invoice.extendedMedia {
extendedMedia = selectedMedia
} else if let paidContent = media as? TelegramMediaPaidContent {
let selectedMediaIndex = mediaIndex ?? 0
if selectedMediaIndex < paidContent.extendedMedia.count {
extendedMedia = paidContent.extendedMedia[selectedMediaIndex]
}
}
if let extendedMedia, case let .full(fullMedia) = extendedMedia {
isExtendedMedia = true
media = fullMedia
}
@ -1481,6 +1515,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
strongSelf.message = message
strongSelf.attributes = attributes
strongSelf.media = media
strongSelf.mediaIndex = mediaIndex
strongSelf.wideLayout = wideLayout
strongSelf.themeAndStrings = (presentationData.theme.theme, presentationData.strings, dateTimeFormat.decimalSeparator, presentationData.isPreview)
strongSelf.sizeCalculation = sizeCalculation
@ -1719,7 +1754,16 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
let _ = strongSelf.fetchControls.swap(updatedFetchControls)
var media = media
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
var extendedMedia: TelegramExtendedMedia?
if let invoice = media as? TelegramMediaInvoice, let selectedMedia = invoice.extendedMedia {
extendedMedia = selectedMedia
} else if let paidContent = media as? TelegramMediaPaidContent {
let selectedMediaIndex = mediaIndex ?? 0
if selectedMediaIndex < paidContent.extendedMedia.count {
extendedMedia = paidContent.extendedMedia[selectedMediaIndex]
}
}
if let extendedMedia, case let .full(fullMedia) = extendedMedia {
media = fullMedia
}
if let storyMedia = media as? TelegramMediaStory, let storyItem = message.associatedStories[storyMedia.storyId]?.get(Stories.StoredItem.self) {
@ -1824,11 +1868,14 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
var game: TelegramMediaGame?
var webpage: TelegramMediaWebpage?
var invoice: TelegramMediaInvoice?
var paidContent: TelegramMediaPaidContent?
for media in message.media {
if let media = media as? TelegramMediaWebpage {
webpage = media
} else if let media = media as? TelegramMediaInvoice {
invoice = media
} else if let media = media as? TelegramMediaPaidContent {
paidContent = media
} else if let media = media as? TelegramMediaGame {
game = media
} else if let _ = media as? TelegramMediaStory {
@ -1836,9 +1883,6 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
automaticPlayback = false
}
}
if let media = self.media as? TelegramMediaInvoice {
invoice = media
}
var progressRequired = false
if let updatingMedia = attributes.updatingMedia, case .update = updatingMedia.media {
@ -1999,7 +2043,16 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
let formatting = DataSizeStringFormatting(strings: strings, decimalSeparator: decimalSeparator)
var media = self.media
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
var extendedMedia: TelegramExtendedMedia?
if let invoice = media as? TelegramMediaInvoice, let selectedMedia = invoice.extendedMedia {
extendedMedia = selectedMedia
} else if let paidContent = media as? TelegramMediaPaidContent {
let selectedMediaIndex = mediaIndex ?? 0
if selectedMediaIndex < paidContent.extendedMedia.count {
extendedMedia = paidContent.extendedMedia[selectedMediaIndex]
}
}
if let extendedMedia, case let .full(fullMedia) = extendedMedia {
media = fullMedia
}
if let storyMedia = media as? TelegramMediaStory, let storyItem = message.associatedStories[storyMedia.storyId]?.get(Stories.StoredItem.self) {
@ -2263,11 +2316,22 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
badgeNode.removeFromSupernode()
}
var icon: ExtendedMediaOverlayNode.Icon? = .lock
var icon: ExtendedMediaOverlayNode.Icon?
var displaySpoiler = false
if let invoice = invoice, let extendedMedia = invoice.extendedMedia, case .preview = extendedMedia {
if invoice.currency == "XTR" {
icon = nil
var extendedMedia: TelegramExtendedMedia?
if let invoice, let selectedMedia = invoice.extendedMedia {
extendedMedia = selectedMedia
} else if let paidContent {
let selectedMediaIndex = self.mediaIndex ?? 0
if selectedMediaIndex < paidContent.extendedMedia.count {
extendedMedia = paidContent.extendedMedia[selectedMediaIndex]
}
}
if let extendedMedia, case .preview = extendedMedia {
if let invoice, invoice.currency != "XTR" {
icon = .lock
}
displaySpoiler = true
} else if message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) {
@ -2331,8 +2395,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
}
}
}
if let invoice, invoice.currency == "XTR" && viewText.isEmpty {
viewText = "Unlock for ⭐️\(invoice.totalAmount)"
if let paidContent {
viewText = "⭐️\(paidContent.amount)"
}
self.extendedMediaOverlayNode?.update(size: self.imageNode.frame.size, text: viewText, imageSignal: self.currentBlurredImageSignal, imageFrame: self.imageNode.view.convert(self.imageNode.bounds, to: self.extendedMediaOverlayNode?.view), corners: self.currentImageArguments?.corners)
} else if let extendedMediaOverlayNode = self.extendedMediaOverlayNode {
@ -2361,12 +2425,12 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
self.extendedMediaOverlayNode?.reveal(animated: true)
}
public static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode))) {
public static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ mediaIndex: Int?, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode))) {
let currentAsyncLayout = node?.asyncLayout()
return { context, presentationData, dateTimeFormat, message, associatedData, attributes, media, dateAndStatus, automaticDownload, peerType, peerId, sizeCalculation, layoutConstants, contentMode, presentationContext in
return { context, presentationData, dateTimeFormat, message, associatedData, attributes, media, mediaIndex, dateAndStatus, automaticDownload, peerType, peerId, sizeCalculation, layoutConstants, contentMode, presentationContext in
var imageNode: ChatMessageInteractiveMediaNode
var imageLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)))
var imageLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ mediaIndex: Int?, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)))
if let node = node, let currentAsyncLayout = currentAsyncLayout {
imageNode = node
@ -2376,7 +2440,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
imageLayout = imageNode.asyncLayout()
}
let (unboundSize, initialWidth, continueLayout) = imageLayout(context, presentationData, dateTimeFormat, message, associatedData, attributes, media, dateAndStatus, automaticDownload, peerType, peerId, sizeCalculation, layoutConstants, contentMode, presentationContext)
let (unboundSize, initialWidth, continueLayout) = imageLayout(context, presentationData, dateTimeFormat, message, associatedData, attributes, media, mediaIndex, dateAndStatus, automaticDownload, peerType, peerId, sizeCalculation, layoutConstants, contentMode, presentationContext)
return (unboundSize, initialWidth, { constrainedSize, automaticPlayback, wideLayout, corners in
let (finalWidth, finalLayout) = continueLayout(constrainedSize, automaticPlayback, wideLayout, corners)
@ -2542,12 +2606,12 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
}
public func ignoreTapActionAtPoint(_ point: CGPoint) -> Bool {
if let extendedMediaOverlayNode = self.extendedMediaOverlayNode {
let convertedPoint = self.view.convert(point, to: extendedMediaOverlayNode.view)
if extendedMediaOverlayNode.buttonNode.frame.contains(convertedPoint) {
return true
}
}
// if let extendedMediaOverlayNode = self.extendedMediaOverlayNode {
// let convertedPoint = self.view.convert(point, to: extendedMediaOverlayNode.view)
// if extendedMediaOverlayNode.buttonNode.frame.contains(convertedPoint) {
// return true
// }
// }
return false
}
}

View File

@ -27,6 +27,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
private var highlightedState: Bool = false
private var media: Media?
private var mediaIndex: Int?
private var automaticPlayback: Bool?
override public var visibility: ListViewItemNodeVisibility {
@ -97,6 +98,8 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
return { item, layoutConstants, preparePosition, selection, constrainedSize, _ in
var selectedMedia: Media?
var selectedMediaIndex: Int?
var extendedMedia: TelegramExtendedMedia?
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
var automaticPlayback: Bool = false
var contentMode: InteractiveMediaNodeContentMode = .aspectFit
@ -107,15 +110,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
if selectedMedia == nil {
for media in item.message.media {
if let telegramImage = media as? TelegramMediaImage {
// #if DEBUG
// if item.message.text == "#" {
// selectedMedia = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: 100, startParam: "", extendedMedia: .preview(dimensions: telegramImage.representations.first?.dimensions ?? PixelDimensions(width: 1, height: 1), immediateThumbnailData: telegramImage.immediateThumbnailData, videoDuration: nil), flags: [], version: 0)
// } else {
// selectedMedia = telegramImage
// }
// #else
selectedMedia = telegramImage
// #endif
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramImage) {
automaticDownload = .full
}
@ -177,38 +172,50 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
contentMode = .aspectFill
} else if let invoice = media as? TelegramMediaInvoice {
selectedMedia = invoice
if let extendedMedia = invoice.extendedMedia, case let .full(media) = extendedMedia {
if let telegramImage = media as? TelegramMediaImage {
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramImage) {
automaticDownload = .full
}
} else if let telegramFile = media as? TelegramMediaFile {
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramFile) {
automaticDownload = .full
} else if shouldPredownloadMedia(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramFile) {
automaticDownload = .prefetch
}
if !item.message.containsSecretMedia {
if telegramFile.isAnimated && item.context.sharedContext.energyUsageSettings.autoplayGif {
if case .full = automaticDownload {
automaticPlayback = true
} else {
automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil
}
} else if (telegramFile.isVideo && !telegramFile.isAnimated) && item.context.sharedContext.energyUsageSettings.autoplayVideo {
if case .full = automaticDownload {
automaticPlayback = true
} else {
automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil
}
}
}
contentMode = .aspectFill
extendedMedia = invoice.extendedMedia
}
else if let paidContent = media as? TelegramMediaPaidContent {
selectedMedia = paidContent
if case let .mosaic(_, _, index) = preparePosition, let index {
extendedMedia = paidContent.extendedMedia[index]
selectedMediaIndex = index
} else {
extendedMedia = paidContent.extendedMedia.first
}
}
}
}
let _ = selectedMediaIndex
if let extendedMedia, case let .full(media) = extendedMedia {
if let telegramImage = media as? TelegramMediaImage {
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramImage) {
automaticDownload = .full
}
} else if let telegramFile = media as? TelegramMediaFile {
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramFile) {
automaticDownload = .full
} else if shouldPredownloadMedia(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramFile) {
automaticDownload = .prefetch
}
if !item.message.containsSecretMedia {
if telegramFile.isAnimated && item.context.sharedContext.energyUsageSettings.autoplayGif {
if case .full = automaticDownload {
automaticPlayback = true
} else {
automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil
}
} else if (telegramFile.isVideo && !telegramFile.isAnimated) && item.context.sharedContext.energyUsageSettings.autoplayVideo {
if case .full = automaticDownload {
automaticPlayback = true
} else {
automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil
}
}
}
contentMode = .aspectFill
}
}
@ -350,7 +357,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
)
}
let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.context, item.presentationData, item.presentationData.dateTimeFormat, item.message, item.associatedData, item.attributes, selectedMedia!, dateAndStatus, automaticDownload, item.associatedData.automaticDownloadPeerType, item.associatedData.automaticDownloadPeerId, sizeCalculation, layoutConstants, contentMode, item.controllerInteraction.presentationContext)
let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.context, item.presentationData, item.presentationData.dateTimeFormat, item.message, item.associatedData, item.attributes, selectedMedia!, selectedMediaIndex, dateAndStatus, automaticDownload, item.associatedData.automaticDownloadPeerType, item.associatedData.automaticDownloadPeerId, sizeCalculation, layoutConstants, contentMode, item.controllerInteraction.presentationContext)
let forceFullCorners = false
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 7.0, hidesBackground: .emptyWallpaper, forceFullCorners: forceFullCorners, forceAlignment: .none)
@ -386,6 +393,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
if let strongSelf = self {
strongSelf.item = item
strongSelf.media = selectedMedia
strongSelf.mediaIndex = selectedMediaIndex
strongSelf.automaticPlayback = automaticPlayback
let imageFrame = CGRect(origin: CGPoint(x: bubbleInsets.left, y: bubbleInsets.top), size: imageSize)
@ -441,6 +449,9 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
if let invoice = currentMedia as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
currentMedia = fullMedia
}
if let paidContent = currentMedia as? TelegramMediaPaidContent, case let .full(fullMedia) = paidContent.extendedMedia[self.mediaIndex ?? 0] {
currentMedia = fullMedia
}
if currentMedia.isSemanticallyEqual(to: media) {
return self.interactiveImageNode.transitionNode(adjustRect: adjustRect)
}
@ -455,7 +466,9 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
if let invoice = currentMedia as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia {
currentMedia = fullMedia
}
if let paidContent = currentMedia as? TelegramMediaPaidContent, case let .full(fullMedia) = paidContent.extendedMedia[self.mediaIndex ?? 0] {
currentMedia = fullMedia
}
if let currentMedia = currentMedia, let media = media {
for item in media {
if item.isSemanticallyEqual(to: currentMedia) {

View File

@ -619,7 +619,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
let prepareLayout = messageNode.asyncLayoutContent()
let prepareContentPosition: ChatMessageBubblePreparePosition = .mosaic(top: .None(.None(.Incoming)), bottom: i == (items.count - 1 - 1) ? bottomPosition : .None(.None(.Incoming)))
let prepareContentPosition: ChatMessageBubblePreparePosition = .mosaic(top: .None(.None(.Incoming)), bottom: i == (items.count - 1 - 1) ? bottomPosition : .None(.None(.Incoming)), index: i)
let (properties, unboundSize, maxNodeWidth, nodeLayout) = prepareLayout(items[i], layoutConstants, prepareContentPosition, nil, CGSize(width: maximumContentWidth, height: CGFloat.greatestFiniteMagnitude), 0.0)
maximumNodeWidth = min(maximumNodeWidth, maxNodeWidth)

View File

@ -430,6 +430,13 @@ final class StarsStatisticsScreenComponent: Component {
rate: starsState?.usdRate ?? 0.0
))),
AnyComponentWithIdentity(id: 1, component: AnyComponent(StarsOverviewItemComponent(
theme: environment.theme,
dateTimeFormat: environment.dateTimeFormat,
title: strings.Stars_BotRevenue_Proceeds_Current,
value: starsState?.balances.currentBalance ?? 0,
rate: starsState?.usdRate ?? 0.0
))),
AnyComponentWithIdentity(id: 2, component: AnyComponent(StarsOverviewItemComponent(
theme: environment.theme,
dateTimeFormat: environment.dateTimeFormat,
title: strings.Stars_BotRevenue_Proceeds_Total,

View File

@ -426,6 +426,14 @@ private final class SheetContent: CombinedComponent {
}
}, completion: { [weak controller] in
let presentationData = accountContext.sharedContext.currentPresentationData.with { $0 }
let text: String
if let _ = component.invoice.extendedMedia {
text = presentationData.strings.Stars_Transfer_UnlockedText( presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string
} else {
text = presentationData.strings.Stars_Transfer_PurchasedText(invoice.title, botTitle, presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string
}
if let navigationController = controller?.navigationController {
Queue.mainQueue().after(0.5) {
if let lastController = navigationController.viewControllers.last as? ViewController {
@ -434,7 +442,7 @@ private final class SheetContent: CombinedComponent {
content: .image(
image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!,
title: presentationData.strings.Stars_Transfer_PurchasedTitle,
text: presentationData.strings.Stars_Transfer_PurchasedText(invoice.title, botTitle, presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string,
text: text,
round: false,
undoText: nil
),

View File

@ -78,18 +78,6 @@ extension ChatControllerImpl {
}))
)
if canAddToReadingList {
items.append(
.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_AddToReadingList, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
if let link = URL(string: url) {
let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil)
}
}))
)
}
items.append(
.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
@ -103,7 +91,19 @@ extension ChatControllerImpl {
self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}))
)
if canAddToReadingList {
items.append(
.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_AddToReadingList, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
if let link = URL(string: url) {
let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil)
}
}))
)
}
self.canReadHistory.set(false)
let controller = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture, disableScreenshots: false)

View File

@ -937,7 +937,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
// }
// #endif
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
if let paidContent = media as? TelegramMediaPaidContent, let extendedMedia = paidContent.extendedMedia.first {
switch extendedMedia {
case .preview:
if displayVoiceMessageDiscardAlert() {
strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id)
return true
} else {
return false
}
case .full:
break
}
} else if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
switch extendedMedia {
case .preview:
if displayVoiceMessageDiscardAlert() {
@ -2633,7 +2645,36 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
for media in message.media {
if let invoice = media as? TelegramMediaInvoice {
if let paidContent = media as? TelegramMediaPaidContent {
strongSelf.chatDisplayNode.dismissInput()
let inputData = Promise<BotCheckoutController.InputData?>()
inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .message(message.id))
|> map(Optional.init)
|> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
return .single(nil)
})
if let starsContext = strongSelf.context.starsContext {
let starsInputData = combineLatest(
inputData.get(),
starsContext.state
)
|> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in
if let data, let state {
return (state, data.form, data.botPeer)
} else {
return nil
}
}
let _ = (starsInputData |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let strongSelf = self, let extendedMedia = paidContent.extendedMedia.first, case let .preview(dimensions, immediateThumbnailData, _) = extendedMedia else {
return
}
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), inputData: starsInputData, completion: { _ in })
strongSelf.push(controller)
})
}
} else if let invoice = media as? TelegramMediaInvoice {
strongSelf.chatDisplayNode.dismissInput()
if let receiptMessageId = invoice.receiptMessageId {
if invoice.currency == "XTR" {

View File

@ -2655,6 +2655,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
if invoice.version != TelegramMediaInvoice.lastVersion {
contentRequiredValidation = true
}
} else if let paidContent = media as? TelegramMediaPaidContent, let extendedMedia = paidContent.extendedMedia.first, case .preview = extendedMedia {
messageIdsWithInactiveExtendedMedia.insert(message.id)
} else if let _ = media as? TelegramMediaStory {
storiesRequiredValidation = true
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, let _ = content.story {

View File

@ -1711,7 +1711,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
clearCacheAsDelete = true
}
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, canEditFactCheck(appConfig: appConfig) {
if message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, canEditFactCheck(appConfig: appConfig) {
var canAddFactCheck = true
if message.media.contains(where: { $0 is TelegramMediaAction || $0 is TelegramMediaGiveaway }) {
canAddFactCheck = false
@ -2190,6 +2190,8 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
for media in message.media {
if let invoice = media as? TelegramMediaInvoice, let _ = invoice.extendedMedia {
isShareProtected = true
} else if let _ = media as? TelegramMediaPaidContent {
isShareProtected = true
} else if let file = media as? TelegramMediaFile, file.isSticker {
for case let .Sticker(_, packReference, _) in file.attributes {
if let _ = packReference {