import Foundation import UIKit import Display import AsyncDisplayKit import Postbox 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() } }