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

155 lines
7.1 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramCore
import SwiftSignalKit
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import TelegramStringFormatting
import TextFormat
import TextNodeWithEntities
final class ChatFeePanelNode: ASDisplayNode {
private let context: AccountContext
private let removeFee: () -> Void
private let contextContainer: ContextControllerSourceNode
private let clippingContainer: ASDisplayNode
private let contentContainer: ASDisplayNode
private let textContainer: ASDisplayNode
private let textNode: ImmediateTextNodeWithEntities
private let removeButtonNode: HighlightTrackingButtonNode
private let removeTextNode: ImmediateTextNode
private var currentLayout: (CGFloat, CGFloat, CGFloat)?
init(context: AccountContext, removeFee: @escaping () -> Void) {
self.context = context
self.removeFee = removeFee
self.contextContainer = ContextControllerSourceNode()
self.clippingContainer = ASDisplayNode()
self.clippingContainer.clipsToBounds = true
self.contentContainer = ASDisplayNode()
self.contextContainer.isGestureEnabled = false
self.textContainer = ASDisplayNode()
self.textNode = ImmediateTextNodeWithEntities()
self.textNode.anchorPoint = CGPoint()
self.textNode.displaysAsynchronously = false
self.textNode.isUserInteractionEnabled = false
self.textNode.maximumNumberOfLines = 2
self.textNode.textAlignment = .center
self.removeButtonNode = HighlightTrackingButtonNode()
self.removeTextNode = ImmediateTextNode()
self.removeTextNode.anchorPoint = CGPoint()
self.removeTextNode.displaysAsynchronously = false
self.removeTextNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.contextContainer)
self.contextContainer.addSubnode(self.clippingContainer)
self.clippingContainer.addSubnode(self.contentContainer)
self.contextContainer.addSubnode(self.textContainer)
self.textContainer.addSubnode(self.textNode)
self.contextContainer.addSubnode(self.removeTextNode)
self.contextContainer.addSubnode(self.removeButtonNode)
self.removeButtonNode.addTarget(self, action: #selector(self.removePressed), forControlEvents: [.touchUpInside])
self.removeButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.removeTextNode.layer.removeAnimation(forKey: "opacity")
strongSelf.removeTextNode.alpha = 0.4
} else {
strongSelf.removeTextNode.alpha = 1.0
strongSelf.removeTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
}
private var theme: PresentationTheme?
func updateLayout(width: CGFloat, theme: PresentationTheme, strings: PresentationStrings, info: MessageFeeHeaderPanelComponent.Info, transition: ContainedViewLayoutTransition) -> CGFloat {
let leftInset: CGFloat = 0.0
let rightInset: CGFloat = 0.0
if self.theme !== theme {
self.theme = theme
self.removeTextNode.attributedText = NSAttributedString(string: strings.Chat_PaidMessageFee_RemoveFee, font: Font.regular(17.0), textColor: theme.chat.inputPanel.panelControlColor)
}
let attributedText = NSMutableAttributedString(string: strings.Chat_PaidMessageFee_Text(info.peer.compactDisplayTitle, "⭐️\(info.value)").string, font: Font.regular(12.0), textColor: theme.rootController.navigationBar.secondaryTextColor)
let range = (attributedText.string as NSString).range(of: "⭐️")
if range.location != NSNotFound {
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: range)
attributedText.addAttribute(.baselineOffset, value: 0.0, range: range)
}
self.textNode.attributedText = attributedText
self.textNode.visibility = true
self.textNode.arguments = TextNodeWithEntities.Arguments(
context: self.context,
cache: self.context.animationCache,
renderer: self.context.animationRenderer,
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
attemptSynchronous: false
)
let sideInset = 12.0
let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - sideInset * 2.0, height: .greatestFiniteMagnitude))
let textFrame = CGRect(origin: CGPoint(x: leftInset + floorToScreenPixels((width - leftInset - rightInset - textSize.width) / 2.0), y: 9.0), size: textSize)
transition.updateFrame(node: self.textContainer, frame: textFrame)
if self.textNode.bounds.size.width != 0.0, transition.isAnimated {
if let snapshotLayer = self.textNode.layer.snapshotContentTree() {
self.textContainer.layer.addSublayer(snapshotLayer)
snapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
snapshotLayer?.removeFromSuperlayer()
})
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
}
transition.updatePosition(node: self.textNode, position: CGPoint())
self.textNode.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
let panelHeight: CGFloat = 48.0 + textSize.height
let removeSize = self.removeTextNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
let removeFrame = CGRect(origin: CGPoint(x: leftInset + floorToScreenPixels((width - leftInset - rightInset - removeSize.width) / 2.0), y: panelHeight - removeSize.height - 9.0), size: removeSize)
transition.updatePosition(node: self.removeTextNode, position: removeFrame.origin)
self.removeTextNode.bounds = CGRect(origin: CGPoint(), size: removeFrame.size)
transition.updateFrame(node: self.removeButtonNode, frame: removeFrame.insetBy(dx: -8.0, dy: -4.0))
self.contextContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
self.clippingContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
self.contentContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
self.currentLayout = (width, leftInset, rightInset)
return panelHeight
}
@objc func removePressed() {
self.removeFee()
}
}