Swiftgram/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift
2023-07-12 20:17:01 +02:00

257 lines
9.3 KiB
Swift

import Foundation
import UIKit
import Display
import TelegramCore
import SwiftSignalKit
import AsyncDisplayKit
import Postbox
import TelegramPresentationData
import StickerResources
import AccountContext
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ChatPresentationInterfaceState
import EmojiTextAttachmentView
import TextFormat
final class StickerPaneSearchStickerSection: GridSection {
let code: String
let theme: PresentationTheme
let height: CGFloat = 26.0
var hashValue: Int {
return self.code.hashValue
}
init(code: String, theme: PresentationTheme) {
self.code = code
self.theme = theme
}
func isEqual(to: GridSection) -> Bool {
if let to = to as? StickerPaneSearchStickerSection {
return self.code == to.code && self.theme === to.theme
} else {
return false
}
}
func node() -> ASDisplayNode {
return StickerPaneSearchStickerSectionNode(code: self.code, theme: self.theme)
}
}
private let sectionTitleFont = Font.medium(12.0)
final class StickerPaneSearchStickerSectionNode: ASDisplayNode {
let titleNode: ASTextNode
init(code: String, theme: PresentationTheme) {
self.titleNode = ASTextNode()
self.titleNode.isUserInteractionEnabled = false
super.init()
self.titleNode.attributedText = NSAttributedString(string: code, font: sectionTitleFont, textColor: theme.chat.inputMediaPanel.stickersSectionTextColor)
self.titleNode.maximumNumberOfLines = 1
self.titleNode.truncationMode = .byTruncatingTail
self.addSubnode(self.titleNode)
}
override func layout() {
super.layout()
let bounds = self.bounds
let titleSize = self.titleNode.measure(CGSize(width: bounds.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude))
self.titleNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 9.0), size: titleSize)
}
}
public final class StickerPaneSearchStickerItem: GridItem {
public let context: AccountContext
public let theme: PresentationTheme
public let code: String?
public let stickerItem: FoundStickerItem
public let selected: (ASDisplayNode, CALayer, CGRect) -> Void
public let inputNodeInteraction: ChatMediaInputNodeInteraction
public let section: GridSection?
public init(context: AccountContext, theme: PresentationTheme, code: String?, stickerItem: FoundStickerItem, inputNodeInteraction: ChatMediaInputNodeInteraction, selected: @escaping (ASDisplayNode, CALayer, CGRect) -> Void) {
self.context = context
self.theme = theme
self.stickerItem = stickerItem
self.inputNodeInteraction = inputNodeInteraction
self.selected = selected
self.code = code
self.section = nil
}
public func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
let node = StickerPaneSearchStickerItemNode()
node.inputNodeInteraction = self.inputNodeInteraction
node.setup(context: self.context, theme: self.theme, stickerItem: self.stickerItem, code: self.code)
node.selected = self.selected
return node
}
public func update(node: GridItemNode) {
guard let node = node as? StickerPaneSearchStickerItemNode else {
assertionFailure()
return
}
node.inputNodeInteraction = self.inputNodeInteraction
node.setup(context: self.context, theme: self.theme, stickerItem: self.stickerItem, code: self.code)
node.selected = self.selected
}
}
private let textFont = Font.regular(20.0)
public final class StickerPaneSearchStickerItemNode: GridItemNode {
private var currentState: (AccountContext, FoundStickerItem, CGSize)?
var itemLayer: InlineStickerItemLayer?
private let textNode: ASTextNode
private let stickerFetchedDisposable = MetaDisposable()
public var currentIsPreviewing = false
public override var isVisibleInGrid: Bool {
didSet {
self.updateVisibility()
}
}
private var isPlaying = false
public var inputNodeInteraction: ChatMediaInputNodeInteraction?
public var selected: ((ASDisplayNode, CALayer, CGRect) -> Void)?
public var stickerItem: FoundStickerItem? {
return self.currentState?.1
}
public override init() {
self.textNode = ASTextNode()
self.textNode.isUserInteractionEnabled = false
super.init()
self.textNode.maximumNumberOfLines = 1
self.addSubnode(self.textNode)
}
deinit {
self.stickerFetchedDisposable.dispose()
}
public override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
}
func setup(context: AccountContext, theme: PresentationTheme, stickerItem: FoundStickerItem, code: String?) {
if self.currentState == nil || self.currentState!.0 !== context || self.currentState!.1 != stickerItem {
self.textNode.attributedText = NSAttributedString(string: code ?? "", font: textFont, textColor: .black)
let file = stickerItem.file
let itemDimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
let playbackItemSize = CGSize(width: 96.0, height: 96.0)
let itemPlaybackSize = itemDimensions.aspectFitted(playbackItemSize)
let itemLayer: InlineStickerItemLayer
if let current = self.itemLayer {
itemLayer = current
itemLayer.dynamicColor = .white
} else {
itemLayer = InlineStickerItemLayer(
context: context,
userLocation: .other,
attemptSynchronousLoad: false,
emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file),
file: file,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1),
pointSize: itemPlaybackSize,
dynamicColor: .white
)
self.itemLayer = itemLayer
self.layer.insertSublayer(itemLayer, at: 0)
}
self.currentState = (context, stickerItem, itemDimensions)
self.setNeedsLayout()
self.updateVisibility()
}
}
public override func layout() {
super.layout()
let bounds = self.bounds
let boundingSize = bounds.insetBy(dx: 6.0, dy: 6.0).size
if let (_, _, itemDimensions) = self.currentState {
let itemSize = itemDimensions.aspectFitted(boundingSize)
let itemFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - itemSize.width) / 2.0), y: (bounds.size.height - itemSize.height) / 2.0), size: itemSize)
if let itemLayer = self.itemLayer {
itemLayer.frame = itemFrame
}
let textSize = self.textNode.measure(CGSize(width: bounds.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude))
self.textNode.frame = CGRect(origin: CGPoint(x: bounds.size.width - textSize.width, y: bounds.size.height - textSize.height), size: textSize)
}
}
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
guard let itemLayer = self.itemLayer else {
return
}
self.selected?(self, itemLayer, self.bounds)
}
public func transitionNode() -> ASDisplayNode? {
return self
}
public func updateVisibility() {
guard let context = self.currentState?.0 else {
return
}
let isPlaying = self.isVisibleInGrid && context.sharedContext.energyUsageSettings.loopStickers
if self.isPlaying != isPlaying, let itemLayer = self.itemLayer {
self.isPlaying = isPlaying
itemLayer.isVisibleForAnimations = isPlaying
}
}
public func updatePreviewing(animated: Bool) {
var isPreviewing = false
if let (_, item, _) = self.currentState, let interaction = self.inputNodeInteraction {
isPreviewing = interaction.previewedStickerPackItemFile?.id == item.file.id
}
if self.currentIsPreviewing != isPreviewing {
self.currentIsPreviewing = isPreviewing
if isPreviewing {
self.layer.sublayerTransform = CATransform3DMakeScale(0.8, 0.8, 1.0)
if animated {
self.layer.animateSpring(from: 1.0 as NSNumber, to: 0.8 as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.4)
}
} else {
self.layer.sublayerTransform = CATransform3DIdentity
if animated {
self.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.5)
}
}
}
}
}