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