Paid media improvements

This commit is contained in:
Ilya Laktyushin 2024-06-23 04:50:08 +04:00
parent 9fe5a800e9
commit 63d380a534
8 changed files with 307 additions and 93 deletions

View File

@ -12364,7 +12364,7 @@ Sorry for the inconvenience.";
"Stars.BotRevenue.Withdraw.WithdrawShort" = "Withdraw"; "Stars.BotRevenue.Withdraw.WithdrawShort" = "Withdraw";
"Stars.BotRevenue.Withdraw.BuyAds" = "Buy Ads"; "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" = "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"; "Stars.BotRevenue.Transactions.Title" = "Transaction History";
@ -12383,7 +12383,7 @@ Sorry for the inconvenience.";
"Stars.PaidContent.AmountTitle" = "ENTER UNLOCK COST"; "Stars.PaidContent.AmountTitle" = "ENTER UNLOCK COST";
"Stars.PaidContent.AmountPlaceholder" = "Stars to Unlock"; "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" = "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"; "Stars.PaidContent.Create" = "Make This Media Paid";
"MediaEditor.AddLink" = "LINK"; "MediaEditor.AddLink" = "LINK";

View File

@ -817,7 +817,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
self.currentMessage = message self.currentMessage = message
var displayInfo = displayInfo var displayInfo = displayInfo
if Namespaces.Message.allNonRegular.contains(message.id.namespace) { if Namespaces.Message.allNonRegular.contains(message.id.namespace) || message.timestamp == 0 {
displayInfo = false displayInfo = false
} }

View File

@ -189,7 +189,18 @@ class ChatImageGalleryItem: GalleryItem {
} }
func thumbnailItem() -> (Int64, GalleryThumbnailItem)? { 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? var mediaReference: AnyMediaReference?
for m in self.message.media { for m in self.message.media {
if let m = m as? TelegramMediaImage { if let m = m as? TelegramMediaImage {

View File

@ -119,7 +119,18 @@ public class UniversalVideoGalleryItem: GalleryItem {
return nil return nil
} }
if case let .message(message) = contentInfo { 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? var mediaReference: AnyMediaReference?
for m in message.media { for m in message.media {
if let m = m as? TelegramMediaImage { if let m = m as? TelegramMediaImage {

View File

@ -59,7 +59,7 @@ public final class StarsAvatarComponent: Component {
private var imageFrameNode: UIView? private var imageFrameNode: UIView?
private var secondImageNode: TransformImageNode? private var secondImageNode: TransformImageNode?
private let fetchDisposable = MetaDisposable() private let fetchDisposable = DisposableSet()
private var component: StarsAvatarComponent? private var component: StarsAvatarComponent?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
@ -98,24 +98,30 @@ public final class StarsAvatarComponent: Component {
case let .peer(peer): case let .peer(peer):
if !component.media.isEmpty { if !component.media.isEmpty {
let imageNode: TransformImageNode let imageNode: TransformImageNode
var isFirstTime = false
if let current = self.imageNode { if let current = self.imageNode {
imageNode = current imageNode = current
} else { } else {
isFirstTime = true
imageNode = TransformImageNode() imageNode = TransformImageNode()
imageNode.contentAnimations = [.subsequentUpdates] imageNode.contentAnimations = [.subsequentUpdates]
self.addSubview(imageNode.view) self.addSubview(imageNode.view)
self.imageNode = imageNode self.imageNode = imageNode
}
if let image = component.media.first as? TelegramMediaImage {
if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { if let image = component.media.first as? TelegramMediaImage {
dimensions = imageDimensions.cgSize.aspectFilled(size) 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)) 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()) 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 { } else if let file = component.media.first as? TelegramMediaFile {
dimensions = videoDimensions.cgSize.aspectFilled(size) 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)) 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 { if component.media.count > 1 {
let secondImageNode: TransformImageNode let secondImageNode: TransformImageNode
let imageFrameNode: UIView let imageFrameNode: UIView
var secondDimensions = size
var isFirstTime = false
if let current = self.secondImageNode, let currentFrame = self.imageFrameNode { if let current = self.secondImageNode, let currentFrame = self.imageFrameNode {
secondImageNode = current secondImageNode = current
imageFrameNode = currentFrame imageFrameNode = currentFrame
} else { } else {
isFirstTime = true
secondImageNode = TransformImageNode() secondImageNode = TransformImageNode()
secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
self.insertSubview(secondImageNode.view, belowSubview: imageNode.view) self.insertSubview(secondImageNode.view, belowSubview: imageNode.view)
@ -143,21 +152,27 @@ public final class StarsAvatarComponent: Component {
imageFrameNode.layer.cornerRadius = 8.0 imageFrameNode.layer.cornerRadius = 8.0
self.insertSubview(imageFrameNode, belowSubview: imageNode.view) self.insertSubview(imageFrameNode, belowSubview: imageNode.view)
self.imageFrameNode = imageFrameNode self.imageFrameNode = imageFrameNode
}
if let image = component.media[1] as? TelegramMediaImage {
if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { if let image = component.media[1] as? TelegramMediaImage {
dimensions = imageDimensions.cgSize.aspectFilled(size) 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)) 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 { self.fetchDisposable.add(chatMessagePhotoInteractiveFetched(context: component.context, userLocation: .other, photoReference: .standalone(media: image), displayAtSize: nil, storeToDownloadsPeerId: nil).startStrict())
if let videoDimensions = file.dimensions { }
dimensions = videoDimensions.cgSize.aspectFilled(size) } 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)) secondImageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), useLargeThumbnail: true, autoFetchFullSizeThumbnail: true))
} }
} }
imageFrameNode.backgroundColor = component.backgroundColor 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) secondImageNode.frame = imageFrame.offsetBy(dx: 4.0, dy: -4.0)
imageFrameNode.frame = imageFrame.insetBy(dx: -1.0 - UIScreenPixel, dy: -1.0 - UIScreenPixel) imageFrameNode.frame = imageFrame.insetBy(dx: -1.0 - UIScreenPixel, dy: -1.0 - UIScreenPixel)
} }
@ -176,7 +191,7 @@ public final class StarsAvatarComponent: Component {
self.imageNode = imageNode self.imageNode = imageNode
imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo)) 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) imageNode.frame = CGRect(origin: .zero, size: size)

View File

@ -1,5 +1,6 @@
import Foundation import Foundation
import UIKit import UIKit
import AsyncDisplayKit
import Display import Display
import SwiftSignalKit import SwiftSignalKit
import Postbox import Postbox
@ -292,19 +293,22 @@ public final class StarsImageComponent: Component {
public let theme: PresentationTheme public let theme: PresentationTheme
public let diameter: CGFloat public let diameter: CGFloat
public let backgroundColor: UIColor public let backgroundColor: UIColor
public let action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)?
public init( public init(
context: AccountContext, context: AccountContext,
subject: Subject, subject: Subject,
theme: PresentationTheme, theme: PresentationTheme,
diameter: CGFloat, diameter: CGFloat,
backgroundColor: UIColor backgroundColor: UIColor,
action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? = nil
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
self.theme = theme self.theme = theme
self.diameter = diameter self.diameter = diameter
self.backgroundColor = backgroundColor self.backgroundColor = backgroundColor
self.action = action
} }
public static func ==(lhs: StarsImageComponent, rhs: StarsImageComponent) -> Bool { public static func ==(lhs: StarsImageComponent, rhs: StarsImageComponent) -> Bool {
@ -328,10 +332,12 @@ public final class StarsImageComponent: Component {
public final class View: UIView { public final class View: UIView {
private var component: StarsImageComponent? private var component: StarsImageComponent?
private var state: EmptyComponentState?
private var smallParticlesView: StarsParticlesView? private var smallParticlesView: StarsParticlesView?
private var largeParticlesView: StarsParticlesView? private var largeParticlesView: StarsParticlesView?
private var containerNode: ASDisplayNode?
private var imageNode: TransformImageNode? private var imageNode: TransformImageNode?
private var imageFrameNode: UIView? private var imageFrameNode: UIView?
private var secondImageNode: TransformImageNode? private var secondImageNode: TransformImageNode?
@ -339,10 +345,14 @@ public final class StarsImageComponent: Component {
private var iconBackgroundView: UIImageView? private var iconBackgroundView: UIImageView?
private var iconView: UIImageView? private var iconView: UIImageView?
private var dustNode: MediaDustNode? private var dustNode: MediaDustNode?
private var button: UIControl?
private var countView = ComponentView<Empty>() private var countView = ComponentView<Empty>()
private let fetchDisposable = MetaDisposable() private let fetchDisposable = MetaDisposable()
private var hiddenMediaDisposable: Disposable?
private var hiddenMedia: [Media] = []
public override init(frame: CGRect) { public override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
@ -353,10 +363,41 @@ public final class StarsImageComponent: Component {
} }
deinit { deinit {
self.fetchDisposable.dispose() 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.component = component
self.state = state
let smallParticlesView: StarsParticlesView let smallParticlesView: StarsParticlesView
if let current = self.smallParticlesView { if let current = self.smallParticlesView {
@ -382,14 +423,26 @@ public final class StarsImageComponent: Component {
largeParticlesView.update(size: availableSize) largeParticlesView.update(size: availableSize)
largeParticlesView.frame = CGRect(origin: .zero, 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) 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 { if case let .media(media) = component.subject, media.count > 1 {
imageSize = CGSize(width: component.diameter - 6.0, height: component.diameter - 6.0) imageSize = CGSize(width: component.diameter - 6.0, height: component.diameter - 6.0)
} else if case let .extendedMedia(media) = component.subject, media.count > 1 { } else if case let .extendedMedia(media) = component.subject, media.count > 1 {
imageSize = CGSize(width: component.diameter - 6.0, height: component.diameter - 6.0) imageSize = CGSize(width: component.diameter - 6.0, height: component.diameter - 6.0)
} }
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((containerFrame.width - imageSize.width) / 2.0), y: floorToScreenPixels((containerFrame.height - imageSize.height) / 2.0)), size: imageSize)
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - imageSize.height) / 2.0)), size: imageSize)
switch component.subject { switch component.subject {
case .none: case .none:
@ -401,7 +454,7 @@ public final class StarsImageComponent: Component {
} else { } else {
imageNode = TransformImageNode() imageNode = TransformImageNode()
imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
self.addSubview(imageNode.view) containerNode.view.addSubview(imageNode.view)
self.imageNode = imageNode self.imageNode = imageNode
imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo)) imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo))
@ -413,60 +466,77 @@ public final class StarsImageComponent: Component {
case let .media(media): case let .media(media):
let imageNode: TransformImageNode let imageNode: TransformImageNode
var dimensions = imageSize var dimensions = imageSize
var isFirstTime = false
if let current = self.imageNode { if let current = self.imageNode {
imageNode = current imageNode = current
} else { } else {
isFirstTime = true
imageNode = TransformImageNode() imageNode = TransformImageNode()
imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
self.addSubview(imageNode.view) containerNode.view.addSubview(imageNode.view)
self.imageNode = imageNode self.imageNode = imageNode
}
if let image = media.first as? TelegramMediaImage { if let image = media.first as? TelegramMediaImage {
if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions {
dimensions = imageDimensions.cgSize.aspectFilled(imageSize) dimensions = imageDimensions.cgSize.aspectFilled(imageSize)
} }
if isFirstTime {
imageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false)) 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 { } else if let file = media.first as? TelegramMediaFile {
dimensions = videoDimensions.cgSize.aspectFilled(imageSize) 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.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), useLargeThumbnail: true, autoFetchFullSizeThumbnail: true))
} }
} }
imageNode.frame = imageFrame imageNode.frame = imageFrame
imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: dimensions, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() 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 { if media.count > 1 {
let secondImageNode: TransformImageNode let secondImageNode: TransformImageNode
let imageFrameNode: UIView let imageFrameNode: UIView
var secondDimensions = imageSize
if let current = self.secondImageNode, let currentFrame = self.imageFrameNode { if let current = self.secondImageNode, let currentFrame = self.imageFrameNode {
secondImageNode = current secondImageNode = current
imageFrameNode = currentFrame imageFrameNode = currentFrame
} else { } else {
secondImageNode = TransformImageNode() secondImageNode = TransformImageNode()
secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
self.insertSubview(secondImageNode.view, belowSubview: imageNode.view) containerNode.view.insertSubview(secondImageNode.view, belowSubview: imageNode.view)
self.secondImageNode = secondImageNode self.secondImageNode = secondImageNode
imageFrameNode = UIView() imageFrameNode = UIView()
imageFrameNode.layer.cornerRadius = 17.0 imageFrameNode.layer.cornerRadius = 17.0
self.insertSubview(imageFrameNode, belowSubview: imageNode.view) containerNode.view.insertSubview(imageFrameNode, belowSubview: imageNode.view)
self.imageFrameNode = imageFrameNode self.imageFrameNode = imageFrameNode
}
if let image = media[1] as? TelegramMediaImage {
if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { if let image = media[1] as? TelegramMediaImage {
dimensions = imageDimensions.cgSize.aspectFilled(imageSize) 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)) 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 { } else if let file = media[1] as? TelegramMediaFile {
dimensions = videoDimensions.cgSize.aspectFilled(imageSize) 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)) secondImageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), useLargeThumbnail: true, autoFetchFullSizeThumbnail: true))
} }
} }
imageFrameNode.backgroundColor = component.backgroundColor 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) secondImageNode.frame = imageFrame.offsetBy(dx: 6.0, dy: -6.0)
imageFrameNode.frame = imageFrame.insetBy(dx: -2.0, dy: -2.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) 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 let countView = self.countView.view {
if countView.superview == nil { if countView.superview == nil {
self.addSubview(countView) containerNode.view.addSubview(countView)
} }
countView.frame = countFrame countView.frame = countFrame
} }
@ -489,72 +559,88 @@ public final class StarsImageComponent: Component {
case let .extendedMedia(extendedMedia): case let .extendedMedia(extendedMedia):
let imageNode: TransformImageNode let imageNode: TransformImageNode
let dustNode: MediaDustNode let dustNode: MediaDustNode
var dimensions = imageSize
var isFirstTime = false
if let current = self.imageNode, let currentDust = self.dustNode { if let current = self.imageNode, let currentDust = self.dustNode {
imageNode = current imageNode = current
dustNode = currentDust dustNode = currentDust
} else { } else {
isFirstTime = true
imageNode = TransformImageNode() imageNode = TransformImageNode()
imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
self.addSubview(imageNode.view) containerNode.view.addSubview(imageNode.view)
self.imageNode = imageNode 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 = MediaDustNode(enableAnimations: true)
dustNode.isUserInteractionEnabled = false dustNode.isUserInteractionEnabled = false
self.addSubview(dustNode.view) containerNode.view.addSubview(dustNode.view)
self.dustNode = dustNode 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 { if extendedMedia.count > 1 {
let secondImageNode: TransformImageNode let secondImageNode: TransformImageNode
let imageFrameNode: UIView let imageFrameNode: UIView
var secondDimensions = imageSize
var isFirstTime = false
if let current = self.secondImageNode, let currentFrame = self.imageFrameNode { if let current = self.secondImageNode, let currentFrame = self.imageFrameNode {
secondImageNode = current secondImageNode = current
imageFrameNode = currentFrame imageFrameNode = currentFrame
} else { } else {
isFirstTime = true
secondImageNode = TransformImageNode() secondImageNode = TransformImageNode()
secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
self.insertSubview(secondImageNode.view, belowSubview: imageNode.view) containerNode.view.insertSubview(secondImageNode.view, belowSubview: imageNode.view)
self.secondImageNode = secondImageNode self.secondImageNode = secondImageNode
imageFrameNode = UIView() imageFrameNode = UIView()
imageFrameNode.layer.cornerRadius = 17.0 imageFrameNode.layer.cornerRadius = 17.0
self.insertSubview(imageFrameNode, belowSubview: imageNode.view) containerNode.view.insertSubview(imageFrameNode, belowSubview: imageNode.view)
self.imageFrameNode = imageFrameNode self.imageFrameNode = imageFrameNode
}
let media: TelegramMediaImage
switch extendedMedia[1] { let media: TelegramMediaImage
case let .preview(_, immediateThumbnailData, _): switch extendedMedia[1] {
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) case let .preview(imageDimensions, immediateThumbnailData, _):
media = thumbnailMedia let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
default: media = thumbnailMedia
fatalError() 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)) secondImageNode.setSignal(chatSecretPhoto(account: component.context.account, userLocation: .other, photoReference: .standalone(media: media), ignoreFullSize: true, synchronousLoad: true))
} }
imageFrameNode.backgroundColor = component.backgroundColor 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) secondImageNode.frame = imageFrame.offsetBy(dx: 6.0, dy: -6.0)
imageFrameNode.frame = imageFrame.insetBy(dx: -2.0, dy: -2.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 { if extendedMedia.count > 1 {
let countSize = self.countView.update( let countSize = self.countView.update(
transition: .immediate, 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) 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 let countView = self.countView.view {
if countView.superview == nil { if countView.superview == nil {
self.addSubview(countView) containerNode.view.addSubview(countView)
} }
countView.frame = countFrame countView.frame = countFrame
} }
@ -580,7 +666,7 @@ public final class StarsImageComponent: Component {
} else { } else {
avatarNode = ImageNode() avatarNode = ImageNode()
avatarNode.displaysAsynchronously = false avatarNode.displaysAsynchronously = false
self.addSubview(avatarNode.view) containerNode.view.addSubview(avatarNode.view)
self.avatarNode = avatarNode self.avatarNode = avatarNode
avatarNode.setSignal(peerAvatarCompleteImage(account: component.context.account, peer: peer, size: imageSize, font: avatarPlaceholderFont(size: 43.0), fullSize: true)) 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() iconBackgroundView = UIImageView()
iconView = UIImageView() iconView = UIImageView()
self.addSubview(iconBackgroundView) containerNode.view.addSubview(iconBackgroundView)
self.addSubview(iconView) containerNode.view.addSubview(iconView)
self.iconBackgroundView = iconBackgroundView self.iconBackgroundView = iconBackgroundView
self.iconView = iconView 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) 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 return availableSize
} }
} }
@ -679,6 +799,6 @@ public final class StarsImageComponent: Component {
} }
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize { public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition) return view.update(component: self, state: state, availableSize: availableSize, transition: transition)
} }
} }

View File

@ -32,6 +32,7 @@ swift_library(
"//submodules/Components/SolidRoundedButtonComponent", "//submodules/Components/SolidRoundedButtonComponent",
"//submodules/AvatarNode", "//submodules/AvatarNode",
"//submodules/TelegramUI/Components/Stars/StarsImageComponent", "//submodules/TelegramUI/Components/Stars/StarsImageComponent",
"//submodules/GalleryUI",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -21,6 +21,7 @@ import TextFormat
import TelegramStringFormatting import TelegramStringFormatting
import UndoUI import UndoUI
import StarsImageComponent import StarsImageComponent
import GalleryUI
private final class StarsTransactionSheetContent: CombinedComponent { private final class StarsTransactionSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -31,6 +32,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let cancel: (Bool) -> Void let cancel: (Bool) -> Void
let openPeer: (EnginePeer) -> Void let openPeer: (EnginePeer) -> Void
let openMessage: (EngineMessage.Id) -> Void let openMessage: (EngineMessage.Id) -> Void
let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void
let copyTransactionId: () -> Void let copyTransactionId: () -> Void
init( init(
@ -40,6 +42,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
cancel: @escaping (Bool) -> Void, cancel: @escaping (Bool) -> Void,
openPeer: @escaping (EnginePeer) -> Void, openPeer: @escaping (EnginePeer) -> Void,
openMessage: @escaping (EngineMessage.Id) -> Void, openMessage: @escaping (EngineMessage.Id) -> Void,
openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void,
copyTransactionId: @escaping () -> Void copyTransactionId: @escaping () -> Void
) { ) {
self.context = context self.context = context
@ -48,6 +51,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
self.cancel = cancel self.cancel = cancel
self.openPeer = openPeer self.openPeer = openPeer
self.openMessage = openMessage self.openMessage = openMessage
self.openMedia = openMedia
self.copyTransactionId = copyTransactionId self.copyTransactionId = copyTransactionId
} }
@ -337,7 +341,10 @@ private final class StarsTransactionSheetContent: CombinedComponent {
subject: imageSubject, subject: imageSubject,
theme: theme, theme: theme,
diameter: 90.0, 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), availableSize: CGSize(width: context.availableSize.width, height: 200.0),
transition: .immediate transition: .immediate
@ -642,6 +649,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
let action: () -> Void let action: () -> Void
let openPeer: (EnginePeer) -> Void let openPeer: (EnginePeer) -> Void
let openMessage: (EngineMessage.Id) -> Void let openMessage: (EngineMessage.Id) -> Void
let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void
let copyTransactionId: () -> Void let copyTransactionId: () -> Void
init( init(
@ -650,6 +658,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
action: @escaping () -> Void, action: @escaping () -> Void,
openPeer: @escaping (EnginePeer) -> Void, openPeer: @escaping (EnginePeer) -> Void,
openMessage: @escaping (EngineMessage.Id) -> Void, openMessage: @escaping (EngineMessage.Id) -> Void,
openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void,
copyTransactionId: @escaping () -> Void copyTransactionId: @escaping () -> Void
) { ) {
self.context = context self.context = context
@ -657,6 +666,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
self.action = action self.action = action
self.openPeer = openPeer self.openPeer = openPeer
self.openMessage = openMessage self.openMessage = openMessage
self.openMedia = openMedia
self.copyTransactionId = copyTransactionId self.copyTransactionId = copyTransactionId
} }
@ -700,6 +710,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
}, },
openPeer: context.component.openPeer, openPeer: context.component.openPeer,
openMessage: context.component.openMessage, openMessage: context.component.openMessage,
openMedia: context.component.openMedia,
copyTransactionId: context.component.copyTransactionId copyTransactionId: context.component.copyTransactionId
)), )),
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
@ -787,6 +798,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
var openPeerImpl: ((EnginePeer) -> Void)? var openPeerImpl: ((EnginePeer) -> Void)?
var openMessageImpl: ((EngineMessage.Id) -> Void)? var openMessageImpl: ((EngineMessage.Id) -> Void)?
var openMediaImpl: (([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)?
var copyTransactionIdImpl: (() -> Void)? var copyTransactionIdImpl: (() -> Void)?
super.init( super.init(
context: context, context: context,
@ -800,6 +812,9 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
openMessage: { messageId in openMessage: { messageId in
openMessageImpl?(messageId) openMessageImpl?(messageId)
}, },
openMedia: { media, transitionNode, addToTransitionSurface in
openMediaImpl?(media, transitionNode, addToTransitionSurface)
},
copyTransactionId: { copyTransactionId: {
copyTransactionIdImpl?() 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 copyTransactionIdImpl = { [weak self] in
guard let self else { guard let self else {
return return