import Foundation import UIKit import Display import AsyncDisplayKit import SwiftSignalKit import Postbox import TelegramCore import SyncCore import TelegramPresentationData import StickerResources import AccountContext import AnimatedStickerNode import TelegramAnimatedStickerNode import ShimmerEffect private let titleFont = Font.bold(16.0) private let statusFont = Font.regular(15.0) private let buttonFont = Font.medium(13.0) final class TrendingTopItemNode: ASDisplayNode { private let imageNode: TransformImageNode private var animationNode: AnimatedStickerNode? private var placeholderNode: StickerShimmerEffectNode? public private(set) var file: TelegramMediaFile? = nil public private(set) var theme: PresentationTheme? private var listAppearance = false private var itemSize: CGSize? private let loadDisposable = MetaDisposable() var currentIsPreviewing = false var visibility: Bool = false { didSet { if oldValue != self.visibility { self.animationNode?.visibility = self.visibility } } } override init() { self.imageNode = TransformImageNode() self.imageNode.contentAnimations = [.subsequentUpdates] self.placeholderNode = StickerShimmerEffectNode() self.placeholderNode?.isUserInteractionEnabled = false super.init() self.addSubnode(self.imageNode) if let placeholderNode = self.placeholderNode { self.addSubnode(placeholderNode) } var firstTime = true self.imageNode.imageUpdated = { [weak self] image in guard let strongSelf = self else { return } if image != nil { strongSelf.removePlaceholder(animated: !firstTime) } firstTime = false } } deinit { self.loadDisposable.dispose() } private func removePlaceholder(animated: Bool) { if let placeholderNode = self.placeholderNode { self.placeholderNode = nil if !animated { placeholderNode.removeFromSupernode() } else { placeholderNode.allowsGroupOpacity = true placeholderNode.alpha = 0.0 placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in placeholderNode?.removeFromSupernode() placeholderNode?.allowsGroupOpacity = false }) } } } private var absoluteLocation: (CGRect, CGSize)? func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteLocation = (rect, containerSize) if let placeholderNode = placeholderNode { placeholderNode.updateAbsoluteRect(rect, within: containerSize) } } func update(theme: PresentationTheme, listAppearance: Bool) { self.theme = theme self.listAppearance = listAppearance let backgroundColor: UIColor let foregroundColor: UIColor let shimmeringColor: UIColor if listAppearance { backgroundColor = theme.list.plainBackgroundColor foregroundColor = theme.list.itemPlainSeparatorColor.blitOver(backgroundColor, alpha: 0.3) shimmeringColor = theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4) } else { backgroundColor = theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0) foregroundColor = theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(backgroundColor, alpha: 0.15) shimmeringColor = theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3) } if let placeholderNode = self.placeholderNode, let file = self.file { placeholderNode.update(backgroundColor: backgroundColor, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: file.immediateThumbnailData, size: placeholderNode.frame.size) } } func setup(account: Account, item: StickerPackItem, itemSize: CGSize, synchronousLoads: Bool) { self.file = item.file self.itemSize = itemSize if item.file.isAnimatedSticker { let animationNode: AnimatedStickerNode if let currentAnimationNode = self.animationNode { animationNode = currentAnimationNode } else { animationNode = AnimatedStickerNode() animationNode.transform = self.imageNode.transform animationNode.visibility = self.visibility self.animationNode = animationNode if let placeholderNode = self.placeholderNode { self.insertSubnode(animationNode, belowSubnode: placeholderNode) } else { self.addSubnode(animationNode) } } let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512) let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0)) self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: item.file, small: false, size: fittedDimensions, synchronousLoad: synchronousLoads), attemptSynchronously: synchronousLoads) animationNode.started = { [weak self] in self?.imageNode.alpha = 0.0 } animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) self.loadDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(item.file), resource: item.file.resource).start()) } else { self.imageNode.setSignal(chatMessageSticker(account: account, file: item.file, small: true, synchronousLoad: synchronousLoads), attemptSynchronously: synchronousLoads) if let currentAnimationNode = self.animationNode { self.animationNode = nil currentAnimationNode.removeFromSupernode() } self.loadDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(item.file), resource: chatMessageStickerResource(file: item.file, small: true)).start()) } } func updatePreviewing(animated: Bool, isPreviewing: Bool) { if self.currentIsPreviewing != isPreviewing { self.currentIsPreviewing = isPreviewing if isPreviewing { if animated { self.layer.animateSpring(from: 1.0 as NSNumber, to: 0.8 as NSNumber, keyPath: "transform.scale", duration: 0.4, removeOnCompletion: false) } } else { self.layer.removeAnimation(forKey: "transform.scale") if animated { self.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) } } } } override func layout() { super.layout() if let dimensions = self.file?.dimensions, let itemSize = self.itemSize { let imageSize = dimensions.cgSize.aspectFitted(itemSize) self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() } self.imageNode.frame = self.bounds self.animationNode?.updateLayout(size: self.bounds.size) let size = self.bounds.size let boundingSize = size if let placeholderNode = self.placeholderNode { let placeholderFrame = CGRect(origin: CGPoint(x: floor((size.width - boundingSize.width) / 2.0), y: floor((size.height - boundingSize.height) / 2.0)), size: boundingSize) placeholderNode.frame = placeholderFrame if let theme = self.theme { self.update(theme: theme, listAppearance: self.listAppearance) } } } }