diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 80fb5a674b..56a724fa2e 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12364,7 +12364,7 @@ Sorry for the inconvenience."; "Stars.BotRevenue.Withdraw.WithdrawShort" = "Withdraw"; "Stars.BotRevenue.Withdraw.BuyAds" = "Buy Ads"; "Stars.BotRevenue.Withdraw.Info" = "You can collect rewards for Stars using Fragment, or use Stars to advertise your bot. [Learn More >]()"; -"Stars.BotRevenue.Withdraw.Info_URL" = "https://telegram.org/tos"; +"Stars.BotRevenue.Withdraw.Info_URL" = "https://telegram.org/tos/bot-developers#6-2-2-tpa-balance"; "Stars.BotRevenue.Transactions.Title" = "Transaction History"; @@ -12383,7 +12383,7 @@ Sorry for the inconvenience."; "Stars.PaidContent.AmountTitle" = "ENTER UNLOCK COST"; "Stars.PaidContent.AmountPlaceholder" = "Stars to Unlock"; "Stars.PaidContent.AmountInfo" = "Users will have to transfer this amount of Stars to your channel in order to view this media.\n[More about Stars >]()"; -"Stars.PaidContent.AmountInfo_URL" = "https://telegram.org"; +"Stars.PaidContent.AmountInfo_URL" = "https://telegram.org/tos/stars"; "Stars.PaidContent.Create" = "Make This Media Paid"; "MediaEditor.AddLink" = "LINK"; diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 2d69388461..6beea637a0 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -817,7 +817,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll self.currentMessage = message var displayInfo = displayInfo - if Namespaces.Message.allNonRegular.contains(message.id.namespace) { + if Namespaces.Message.allNonRegular.contains(message.id.namespace) || message.timestamp == 0 { displayInfo = false } diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index ae09d73a81..65f8cd64bc 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -189,7 +189,18 @@ class ChatImageGalleryItem: GalleryItem { } func thumbnailItem() -> (Int64, GalleryThumbnailItem)? { - if let id = self.message.groupInfo?.stableId { + if let paidContent = self.message.paidContent { + var mediaReference: AnyMediaReference? + let mediaIndex = self.mediaIndex ?? 0 + if case let .full(fullMedia) = paidContent.extendedMedia[Int(mediaIndex)], let m = fullMedia as? TelegramMediaImage { + mediaReference = .message(message: MessageReference(self.message), media: m) + } + if let mediaReference = mediaReference { + if let item = ChatMediaGalleryThumbnailItem(account: self.context.account, userLocation: .peer(self.message.id.peerId), mediaReference: mediaReference) { + return (0, item) + } + } + } else if let id = self.message.groupInfo?.stableId { var mediaReference: AnyMediaReference? for m in self.message.media { if let m = m as? TelegramMediaImage { diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index d0f101d8e6..d03402182c 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -119,7 +119,18 @@ public class UniversalVideoGalleryItem: GalleryItem { return nil } if case let .message(message) = contentInfo { - if let id = message.groupInfo?.stableId { + if let paidContent = message.paidContent { + var mediaReference: AnyMediaReference? + let mediaIndex = 0//self.mediaIndex ?? 0 + if case let .full(fullMedia) = paidContent.extendedMedia[Int(mediaIndex)], let m = fullMedia as? TelegramMediaFile { + mediaReference = .message(message: MessageReference(message), media: m) + } + if let mediaReference = mediaReference { + if let item = ChatMediaGalleryThumbnailItem(account: self.context.account, userLocation: .peer(message.id.peerId), mediaReference: mediaReference) { + return (0, item) + } + } + } else if let id = message.groupInfo?.stableId { var mediaReference: AnyMediaReference? for m in message.media { if let m = m as? TelegramMediaImage { diff --git a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift index 5bba89ca47..7e8f00cd2e 100644 --- a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift @@ -59,7 +59,7 @@ public final class StarsAvatarComponent: Component { private var imageFrameNode: UIView? private var secondImageNode: TransformImageNode? - private let fetchDisposable = MetaDisposable() + private let fetchDisposable = DisposableSet() private var component: StarsAvatarComponent? private weak var state: EmptyComponentState? @@ -98,24 +98,30 @@ public final class StarsAvatarComponent: Component { case let .peer(peer): if !component.media.isEmpty { let imageNode: TransformImageNode + var isFirstTime = false if let current = self.imageNode { imageNode = current } else { + isFirstTime = true imageNode = TransformImageNode() imageNode.contentAnimations = [.subsequentUpdates] self.addSubview(imageNode.view) self.imageNode = imageNode - - if let image = component.media.first as? TelegramMediaImage { - if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { - dimensions = imageDimensions.cgSize.aspectFilled(size) - } + } + + if let image = component.media.first as? TelegramMediaImage { + if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { + dimensions = imageDimensions.cgSize.aspectFilled(size) + } + if isFirstTime { imageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false)) - self.fetchDisposable.set(chatMessagePhotoInteractiveFetched(context: component.context, userLocation: .other, photoReference: .standalone(media: image), displayAtSize: nil, storeToDownloadsPeerId: nil).startStrict()) - } else if let file = component.media.first as? TelegramMediaFile { - if let videoDimensions = file.dimensions { - dimensions = videoDimensions.cgSize.aspectFilled(size) - } + self.fetchDisposable.add(chatMessagePhotoInteractiveFetched(context: component.context, userLocation: .other, photoReference: .standalone(media: image), displayAtSize: nil, storeToDownloadsPeerId: nil).startStrict()) + } + } else if let file = component.media.first as? TelegramMediaFile { + if let videoDimensions = file.dimensions { + dimensions = videoDimensions.cgSize.aspectFilled(size) + } + if isFirstTime { imageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), autoFetchFullSizeThumbnail: true)) } } @@ -130,10 +136,13 @@ public final class StarsAvatarComponent: Component { if component.media.count > 1 { let secondImageNode: TransformImageNode let imageFrameNode: UIView + var secondDimensions = size + var isFirstTime = false if let current = self.secondImageNode, let currentFrame = self.imageFrameNode { secondImageNode = current imageFrameNode = currentFrame } else { + isFirstTime = true secondImageNode = TransformImageNode() secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] self.insertSubview(secondImageNode.view, belowSubview: imageNode.view) @@ -143,21 +152,27 @@ public final class StarsAvatarComponent: Component { imageFrameNode.layer.cornerRadius = 8.0 self.insertSubview(imageFrameNode, belowSubview: imageNode.view) self.imageFrameNode = imageFrameNode - - if let image = component.media[1] as? TelegramMediaImage { - if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { - dimensions = imageDimensions.cgSize.aspectFilled(size) - } + } + + if let image = component.media[1] as? TelegramMediaImage { + if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { + secondDimensions = imageDimensions.cgSize.aspectFilled(size) + } + if isFirstTime { secondImageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false)) - } else if let file = component.media[1] as? TelegramMediaFile { - if let videoDimensions = file.dimensions { - dimensions = videoDimensions.cgSize.aspectFilled(size) - } + self.fetchDisposable.add(chatMessagePhotoInteractiveFetched(context: component.context, userLocation: .other, photoReference: .standalone(media: image), displayAtSize: nil, storeToDownloadsPeerId: nil).startStrict()) + } + } else if let file = component.media[1] as? TelegramMediaFile { + if let videoDimensions = file.dimensions { + secondDimensions = videoDimensions.cgSize.aspectFilled(size) + } + if isFirstTime { secondImageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), useLargeThumbnail: true, autoFetchFullSizeThumbnail: true)) } } + imageFrameNode.backgroundColor = component.backgroundColor - secondImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: dimensions, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + secondImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: secondDimensions, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() secondImageNode.frame = imageFrame.offsetBy(dx: 4.0, dy: -4.0) imageFrameNode.frame = imageFrame.insetBy(dx: -1.0 - UIScreenPixel, dy: -1.0 - UIScreenPixel) } @@ -176,7 +191,7 @@ public final class StarsAvatarComponent: Component { self.imageNode = imageNode imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo)) - self.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: component.context.account, userLocation: .other, image: photo).startStrict()) + self.fetchDisposable.add(chatMessageWebFileInteractiveFetched(account: component.context.account, userLocation: .other, image: photo).startStrict()) } imageNode.frame = CGRect(origin: .zero, size: size) diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift index 18a7b0b362..02746eb60d 100644 --- a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import AsyncDisplayKit import Display import SwiftSignalKit import Postbox @@ -292,19 +293,22 @@ public final class StarsImageComponent: Component { public let theme: PresentationTheme public let diameter: CGFloat public let backgroundColor: UIColor + public let action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? public init( context: AccountContext, subject: Subject, theme: PresentationTheme, diameter: CGFloat, - backgroundColor: UIColor + backgroundColor: UIColor, + action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? = nil ) { self.context = context self.subject = subject self.theme = theme self.diameter = diameter self.backgroundColor = backgroundColor + self.action = action } public static func ==(lhs: StarsImageComponent, rhs: StarsImageComponent) -> Bool { @@ -328,10 +332,12 @@ public final class StarsImageComponent: Component { public final class View: UIView { private var component: StarsImageComponent? + private var state: EmptyComponentState? private var smallParticlesView: StarsParticlesView? private var largeParticlesView: StarsParticlesView? + private var containerNode: ASDisplayNode? private var imageNode: TransformImageNode? private var imageFrameNode: UIView? private var secondImageNode: TransformImageNode? @@ -339,10 +345,14 @@ public final class StarsImageComponent: Component { private var iconBackgroundView: UIImageView? private var iconView: UIImageView? private var dustNode: MediaDustNode? + private var button: UIControl? private var countView = ComponentView() private let fetchDisposable = MetaDisposable() + private var hiddenMediaDisposable: Disposable? + + private var hiddenMedia: [Media] = [] public override init(frame: CGRect) { super.init(frame: frame) @@ -353,10 +363,41 @@ public final class StarsImageComponent: Component { } deinit { self.fetchDisposable.dispose() + self.hiddenMediaDisposable?.dispose() } - func update(component: StarsImageComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { + @objc private func buttonPressed() { + guard let component = self.component else { + return + } + component.action?({ [weak self] media in + guard let self else { + return nil + } + return self.transitionNode(media) + }, { [weak self] view in + guard let self else { + return + } + self.superview?.addSubview(view) + }) + } + + public func transitionNode(_ transitionMedia: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + guard let component = self.component, let containerNode = self.containerNode else { + return nil + } + if case let .media(media) = component.subject, media.first?.id == transitionMedia.id { + return (containerNode, containerNode.bounds, { [weak containerNode] in + return (containerNode?.view.snapshotContentTree(unhide: true), nil) + }) + } + return nil + } + + func update(component: StarsImageComponent, state: EmptyComponentState, availableSize: CGSize, transition: ComponentTransition) -> CGSize { self.component = component + self.state = state let smallParticlesView: StarsParticlesView if let current = self.smallParticlesView { @@ -382,14 +423,26 @@ public final class StarsImageComponent: Component { largeParticlesView.update(size: availableSize) largeParticlesView.frame = CGRect(origin: .zero, size: availableSize) + let containerNode: ASDisplayNode + if let current = self.containerNode { + containerNode = current + } else { + containerNode = ASDisplayNode() + + self.addSubview(containerNode.view) + self.containerNode = containerNode + } + var imageSize = CGSize(width: component.diameter, height: component.diameter) + let containerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - imageSize.height) / 2.0)), size: imageSize) + containerNode.frame = containerFrame + if case let .media(media) = component.subject, media.count > 1 { imageSize = CGSize(width: component.diameter - 6.0, height: component.diameter - 6.0) } else if case let .extendedMedia(media) = component.subject, media.count > 1 { imageSize = CGSize(width: component.diameter - 6.0, height: component.diameter - 6.0) } - - let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - imageSize.height) / 2.0)), size: imageSize) + let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((containerFrame.width - imageSize.width) / 2.0), y: floorToScreenPixels((containerFrame.height - imageSize.height) / 2.0)), size: imageSize) switch component.subject { case .none: @@ -401,7 +454,7 @@ public final class StarsImageComponent: Component { } else { imageNode = TransformImageNode() imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] - self.addSubview(imageNode.view) + containerNode.view.addSubview(imageNode.view) self.imageNode = imageNode imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo)) @@ -413,60 +466,77 @@ public final class StarsImageComponent: Component { case let .media(media): let imageNode: TransformImageNode var dimensions = imageSize + var isFirstTime = false if let current = self.imageNode { imageNode = current } else { + isFirstTime = true imageNode = TransformImageNode() imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] - self.addSubview(imageNode.view) + containerNode.view.addSubview(imageNode.view) self.imageNode = imageNode - - if let image = media.first as? TelegramMediaImage { - if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { - dimensions = imageDimensions.cgSize.aspectFilled(imageSize) - } + } + if let image = media.first as? TelegramMediaImage { + if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { + dimensions = imageDimensions.cgSize.aspectFilled(imageSize) + } + if isFirstTime { imageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false)) - } else if let file = media.first as? TelegramMediaFile { - if let videoDimensions = file.dimensions { - dimensions = videoDimensions.cgSize.aspectFilled(imageSize) - } + } + } else if let file = media.first as? TelegramMediaFile { + if let videoDimensions = file.dimensions { + dimensions = videoDimensions.cgSize.aspectFilled(imageSize) + } + if isFirstTime { imageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), useLargeThumbnail: true, autoFetchFullSizeThumbnail: true)) } } imageNode.frame = imageFrame imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: dimensions, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + if let firstMedia = media.first, self.hiddenMedia.contains(where: { $0.id == firstMedia.id }) { + containerNode.isHidden = true + } else { + containerNode.isHidden = false + } + if media.count > 1 { let secondImageNode: TransformImageNode let imageFrameNode: UIView + var secondDimensions = imageSize if let current = self.secondImageNode, let currentFrame = self.imageFrameNode { secondImageNode = current imageFrameNode = currentFrame } else { secondImageNode = TransformImageNode() secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] - self.insertSubview(secondImageNode.view, belowSubview: imageNode.view) + containerNode.view.insertSubview(secondImageNode.view, belowSubview: imageNode.view) self.secondImageNode = secondImageNode imageFrameNode = UIView() imageFrameNode.layer.cornerRadius = 17.0 - self.insertSubview(imageFrameNode, belowSubview: imageNode.view) + containerNode.view.insertSubview(imageFrameNode, belowSubview: imageNode.view) self.imageFrameNode = imageFrameNode - - if let image = media[1] as? TelegramMediaImage { - if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { - dimensions = imageDimensions.cgSize.aspectFilled(imageSize) - } + } + + if let image = media[1] as? TelegramMediaImage { + if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { + secondDimensions = imageDimensions.cgSize.aspectFilled(imageSize) + } + if isFirstTime { secondImageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false)) - } else if let file = media[1] as? TelegramMediaFile { - if let videoDimensions = file.dimensions { - dimensions = videoDimensions.cgSize.aspectFilled(imageSize) - } + } + } else if let file = media[1] as? TelegramMediaFile { + if let videoDimensions = file.dimensions { + secondDimensions = videoDimensions.cgSize.aspectFilled(imageSize) + } + if isFirstTime { secondImageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), useLargeThumbnail: true, autoFetchFullSizeThumbnail: true)) } } + imageFrameNode.backgroundColor = component.backgroundColor - secondImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + secondImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: secondDimensions, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() secondImageNode.frame = imageFrame.offsetBy(dx: 6.0, dy: -6.0) imageFrameNode.frame = imageFrame.insetBy(dx: -2.0, dy: -2.0) @@ -481,7 +551,7 @@ public final class StarsImageComponent: Component { let countFrame = CGRect(origin: CGPoint(x: imageFrame.minX + floorToScreenPixels((imageFrame.width - countSize.width) / 2.0), y: imageFrame.minY + floorToScreenPixels((imageFrame.height - countSize.height) / 2.0)), size: countSize) if let countView = self.countView.view { if countView.superview == nil { - self.addSubview(countView) + containerNode.view.addSubview(countView) } countView.frame = countFrame } @@ -489,72 +559,88 @@ public final class StarsImageComponent: Component { case let .extendedMedia(extendedMedia): let imageNode: TransformImageNode let dustNode: MediaDustNode + var dimensions = imageSize + var isFirstTime = false if let current = self.imageNode, let currentDust = self.dustNode { imageNode = current dustNode = currentDust } else { + isFirstTime = true imageNode = TransformImageNode() imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] - self.addSubview(imageNode.view) + containerNode.view.addSubview(imageNode.view) self.imageNode = imageNode - - let media: TelegramMediaImage - switch extendedMedia.first { - case let .preview(_, immediateThumbnailData, _): - let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) - media = thumbnailMedia - default: - fatalError() - } - - imageNode.setSignal(chatSecretPhoto(account: component.context.account, userLocation: .other, photoReference: .standalone(media: media), ignoreFullSize: true, synchronousLoad: true)) - + dustNode = MediaDustNode(enableAnimations: true) dustNode.isUserInteractionEnabled = false - self.addSubview(dustNode.view) + containerNode.view.addSubview(dustNode.view) self.dustNode = dustNode } + + let media: TelegramMediaImage + switch extendedMedia.first { + case let .preview(imageDimensions, immediateThumbnailData, _): + let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + media = thumbnailMedia + if let imageDimensions { + dimensions = imageDimensions.cgSize.aspectFilled(imageSize) + } + default: + fatalError() + } + if isFirstTime { + imageNode.setSignal(chatSecretPhoto(account: component.context.account, userLocation: .other, photoReference: .standalone(media: media), ignoreFullSize: true, synchronousLoad: true)) + } + + imageNode.frame = imageFrame + imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: dimensions, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + + dustNode.frame = imageFrame + dustNode.update(size: imageFrame.size, color: .white, transition: .immediate) if extendedMedia.count > 1 { let secondImageNode: TransformImageNode let imageFrameNode: UIView + var secondDimensions = imageSize + var isFirstTime = false if let current = self.secondImageNode, let currentFrame = self.imageFrameNode { secondImageNode = current imageFrameNode = currentFrame } else { + isFirstTime = true secondImageNode = TransformImageNode() secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] - self.insertSubview(secondImageNode.view, belowSubview: imageNode.view) + containerNode.view.insertSubview(secondImageNode.view, belowSubview: imageNode.view) self.secondImageNode = secondImageNode imageFrameNode = UIView() imageFrameNode.layer.cornerRadius = 17.0 - self.insertSubview(imageFrameNode, belowSubview: imageNode.view) + containerNode.view.insertSubview(imageFrameNode, belowSubview: imageNode.view) self.imageFrameNode = imageFrameNode - - let media: TelegramMediaImage - switch extendedMedia[1] { - case let .preview(_, immediateThumbnailData, _): - let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) - media = thumbnailMedia - default: - fatalError() + } + + let media: TelegramMediaImage + switch extendedMedia[1] { + case let .preview(imageDimensions, immediateThumbnailData, _): + let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + media = thumbnailMedia + if let imageDimensions { + secondDimensions = imageDimensions.cgSize.aspectFilled(imageSize) } - + default: + fatalError() + } + + if isFirstTime { secondImageNode.setSignal(chatSecretPhoto(account: component.context.account, userLocation: .other, photoReference: .standalone(media: media), ignoreFullSize: true, synchronousLoad: true)) } + imageFrameNode.backgroundColor = component.backgroundColor - secondImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + secondImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: secondDimensions, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() secondImageNode.frame = imageFrame.offsetBy(dx: 6.0, dy: -6.0) imageFrameNode.frame = imageFrame.insetBy(dx: -2.0, dy: -2.0) } - imageNode.frame = imageFrame - imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() - - dustNode.frame = imageFrame - dustNode.update(size: imageFrame.size, color: .white, transition: .immediate) - if extendedMedia.count > 1 { let countSize = self.countView.update( transition: .immediate, @@ -567,7 +653,7 @@ public final class StarsImageComponent: Component { let countFrame = CGRect(origin: CGPoint(x: imageFrame.minX + floorToScreenPixels((imageFrame.width - countSize.width) / 2.0), y: imageFrame.minY + floorToScreenPixels((imageFrame.height - countSize.height) / 2.0)), size: countSize) if let countView = self.countView.view { if countView.superview == nil { - self.addSubview(countView) + containerNode.view.addSubview(countView) } countView.frame = countFrame } @@ -580,7 +666,7 @@ public final class StarsImageComponent: Component { } else { avatarNode = ImageNode() avatarNode.displaysAsynchronously = false - self.addSubview(avatarNode.view) + containerNode.view.addSubview(avatarNode.view) self.avatarNode = avatarNode avatarNode.setSignal(peerAvatarCompleteImage(account: component.context.account, peer: peer, size: imageSize, font: avatarPlaceholderFont(size: 43.0), fullSize: true)) @@ -596,8 +682,8 @@ public final class StarsImageComponent: Component { iconBackgroundView = UIImageView() iconView = UIImageView() - self.addSubview(iconBackgroundView) - self.addSubview(iconView) + containerNode.view.addSubview(iconBackgroundView) + containerNode.view.addSubview(iconView) self.iconBackgroundView = iconBackgroundView self.iconView = iconView @@ -670,6 +756,40 @@ public final class StarsImageComponent: Component { iconView.frame = imageFrame.insetBy(dx: iconInset, dy: iconInset).offsetBy(dx: 0.0, dy: iconOffset) } } + + if let _ = component.action { + if self.button == nil { + let button = UIControl(frame: imageFrame) + button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) + containerNode.view.addSubview(button) + self.button = button + } + } else if let button = self.button { + self.button = nil + button.removeFromSuperview() + } + + if case .media = component.subject { + if self.hiddenMediaDisposable == nil { + self.hiddenMediaDisposable = component.context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in + guard let self, let component = self.component else { + return + } + var hiddenMedia: [Media] = [] + for id in ids { + if case let .chat(accountId, _, media) = id, accountId == component.context.account.id { + hiddenMedia.append(media) + } + } + self.hiddenMedia = hiddenMedia + self.state?.updated() + }).strict() + } + } else if let hiddenMediaDisposable = self.hiddenMediaDisposable { + self.hiddenMediaDisposable = nil + hiddenMediaDisposable.dispose() + } + return availableSize } } @@ -679,6 +799,6 @@ public final class StarsImageComponent: Component { } public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, transition: transition) + return view.update(component: self, state: state, availableSize: availableSize, transition: transition) } } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD index 8a80a98499..f392b1ca5e 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD @@ -32,6 +32,7 @@ swift_library( "//submodules/Components/SolidRoundedButtonComponent", "//submodules/AvatarNode", "//submodules/TelegramUI/Components/Stars/StarsImageComponent", + "//submodules/GalleryUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index e6a81027c7..1fb311b351 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -21,6 +21,7 @@ import TextFormat import TelegramStringFormatting import UndoUI import StarsImageComponent +import GalleryUI private final class StarsTransactionSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -31,6 +32,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { let cancel: (Bool) -> Void let openPeer: (EnginePeer) -> Void let openMessage: (EngineMessage.Id) -> Void + let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void let copyTransactionId: () -> Void init( @@ -40,6 +42,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { cancel: @escaping (Bool) -> Void, openPeer: @escaping (EnginePeer) -> Void, openMessage: @escaping (EngineMessage.Id) -> Void, + openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void, copyTransactionId: @escaping () -> Void ) { self.context = context @@ -48,6 +51,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { self.cancel = cancel self.openPeer = openPeer self.openMessage = openMessage + self.openMedia = openMedia self.copyTransactionId = copyTransactionId } @@ -337,7 +341,10 @@ private final class StarsTransactionSheetContent: CombinedComponent { subject: imageSubject, theme: theme, diameter: 90.0, - backgroundColor: theme.actionSheet.opaqueItemBackgroundColor + backgroundColor: theme.actionSheet.opaqueItemBackgroundColor, + action: !media.isEmpty ? { transitionNode, addToTransitionSurface in + component.openMedia(media, transitionNode, addToTransitionSurface) + } : nil ), availableSize: CGSize(width: context.availableSize.width, height: 200.0), transition: .immediate @@ -642,6 +649,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent { let action: () -> Void let openPeer: (EnginePeer) -> Void let openMessage: (EngineMessage.Id) -> Void + let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void let copyTransactionId: () -> Void init( @@ -650,6 +658,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent { action: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void, openMessage: @escaping (EngineMessage.Id) -> Void, + openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void, copyTransactionId: @escaping () -> Void ) { self.context = context @@ -657,6 +666,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent { self.action = action self.openPeer = openPeer self.openMessage = openMessage + self.openMedia = openMedia self.copyTransactionId = copyTransactionId } @@ -700,6 +710,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent { }, openPeer: context.component.openPeer, openMessage: context.component.openMessage, + openMedia: context.component.openMedia, copyTransactionId: context.component.copyTransactionId )), backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), @@ -787,6 +798,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer { var openPeerImpl: ((EnginePeer) -> Void)? var openMessageImpl: ((EngineMessage.Id) -> Void)? + var openMediaImpl: (([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? var copyTransactionIdImpl: (() -> Void)? super.init( context: context, @@ -800,6 +812,9 @@ public class StarsTransactionScreen: ViewControllerComponentContainer { openMessage: { messageId in openMessageImpl?(messageId) }, + openMedia: { media, transitionNode, addToTransitionSurface in + openMediaImpl?(media, transitionNode, addToTransitionSurface) + }, copyTransactionId: { copyTransactionIdImpl?() } @@ -846,6 +861,47 @@ public class StarsTransactionScreen: ViewControllerComponentContainer { }) } + openMediaImpl = { [weak self] media, transitionNode, addToTransitionSurface in + guard let self else { + return + } + + let message = Message( + stableId: 0, + stableVersion: 0, + id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), namespace: Namespaces.Message.Local, id: 0), + globallyUniqueId: 0, + groupingKey: nil, + groupInfo: nil, + threadId: nil, + timestamp: 0, + flags: [], + tags: [], + globalTags: [], + localTags: [], + customTags: [], + forwardInfo: nil, + author: nil, + text: "", + attributes: [], + media: [TelegramMediaPaidContent(amount: 0, extendedMedia: media.map { .full(media: $0) })], + peers: SimpleDictionary(), + associatedMessages: SimpleDictionary(), + associatedMessageIds: [], + associatedMedia: [:], + associatedThreadInfo: nil, + associatedStories: [:] + ) + let gallery = GalleryController(context: self.context, source: .standaloneMessage(message, 0), replaceRootController: { _, _ in + }, baseNavigationController: nil) + self.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { messageId, media in + if let transitionNode = transitionNode(media) { + return GalleryTransitionArguments(transitionNode: transitionNode, addToTransitionSurface: addToTransitionSurface) + } + return nil + })) + } + copyTransactionIdImpl = { [weak self] in guard let self else { return