Files
Isaac 7b2b74e79b Postbox -> TelegramEngine wave 6: unused import Postbox batch sweep
First build-verified unused-import sweep: speculatively dropped
import Postbox from 782 consumer files (plain ^import Postbox$ lines,
excluding TelegramCore/Postbox/TelegramApi paths), iterated 18 full
project builds with --continueOnError, restored the import on every
file that failed to compile. 183 drops survived; 189 consumer modules
newly Postbox-free.

Bundled: spec + plan + C1 atomic batch drop + C2 CLAUDE.md outcome and
permanent methodology guidance under Wave-selection. The methodology
subsection captures the reusable playbook (--continueOnError is
essential, dependency graphs are deep so expect many iterations,
pattern-based preemptive restores accelerate convergence, and
CLAUDE.md's engine typealias cheat sheet arrows are migration targets
rather than typealiases in TelegramCore).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:46:13 +02:00

256 lines
9.2 KiB
Swift

import Foundation
import UIKit
import Display
import TelegramCore
import SwiftSignalKit
import AsyncDisplayKit
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)
}
}
}
}
}