import Foundation import UIKit import Display import AsyncDisplayKit import SwiftSignalKit import ComponentFlow import TelegramCore import TelegramPresentationData import PresentationDataUtils import ContextUI import AvatarNode import EmojiStatusComponent import AccountContext public final class AccountPeerContextItem: ContextMenuCustomItem { let context: AccountContext let account: Account let peer: EnginePeer let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void public init(context: AccountContext, account: Account, peer: EnginePeer, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) { self.context = context self.account = account self.peer = peer self.action = action } public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode { return AccountPeerContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected) } } private final class AccountPeerContextItemNode: ASDisplayNode, ContextMenuCustomNode { private let item: AccountPeerContextItem private let presentationData: PresentationData private let getController: () -> ContextControllerProtocol? private let actionSelected: (ContextMenuActionResult) -> Void private let buttonNode: HighlightTrackingButtonNode private let textNode: ImmediateTextNode private let avatarNode: AvatarNode private let emojiStatusView: ComponentView init(presentationData: PresentationData, item: AccountPeerContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) { self.item = item self.presentationData = presentationData self.getController = getController self.actionSelected = actionSelected let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 17.0 / 17.0) self.textNode = ImmediateTextNode() self.textNode.isAccessibilityElement = false self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false let peerTitle = item.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) self.textNode.attributedText = NSAttributedString(string: peerTitle, font: textFont, textColor: presentationData.theme.contextMenu.primaryColor) self.textNode.maximumNumberOfLines = 1 self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0)) self.emojiStatusView = ComponentView() self.buttonNode = HighlightTrackingButtonNode() self.buttonNode.isAccessibilityElement = true self.buttonNode.accessibilityLabel = peerTitle super.init() self.addSubnode(self.textNode) self.addSubnode(self.avatarNode) self.addSubnode(self.buttonNode) self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) } func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) { let sideInset: CGFloat = 18.0 let iconSideInset: CGFloat = 20.0 let verticalInset: CGFloat = 11.0 let iconSize = CGSize(width: 28.0, height: 28.0) let standardIconWidth: CGFloat = 32.0 var rightTextInset: CGFloat = sideInset if !iconSize.width.isZero { rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset - 12.0 } self.avatarNode.setPeer(context: self.item.context, account: self.item.account, theme: self.presentationData.theme, peer: self.item.peer) if self.item.peer.emojiStatus != nil { rightTextInset += 32.0 } let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude)) return (CGSize(width: textSize.width + sideInset + rightTextInset, height: verticalInset * 2.0 + textSize.height), { size, transition in let verticalOrigin = floor((size.height - textSize.height) / 2.0) let textFrame = CGRect(origin: CGPoint(x: iconSideInset + 40.0, y: verticalOrigin), size: textSize) transition.updateFrameAdditive(node: self.textNode, frame: textFrame) var iconContent: EmojiStatusComponent.Content? if case let .user(user) = self.item.peer { if let emojiStatus = user.emojiStatus { iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever) } else if user.isPremium { iconContent = .premium(color: self.presentationData.theme.list.itemAccentColor) } } else if case let .channel(channel) = self.item.peer { if let emojiStatus = channel.emojiStatus { iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever) } } if let iconContent { let emojiStatusSize = self.emojiStatusView.update( transition: .immediate, component: AnyComponent(EmojiStatusComponent( context: self.item.context, animationCache: self.item.context.animationCache, animationRenderer: self.item.context.animationRenderer, content: iconContent, isVisibleForAnimations: true, action: nil )), environment: {}, containerSize: CGSize(width: 24.0, height: 24.0) ) if let view = self.emojiStatusView.view { if view.superview == nil { self.view.addSubview(view) } transition.updateFrame(view: view, frame: CGRect(origin: CGPoint(x: textFrame.maxX + 2.0, y: textFrame.minY + floor((textFrame.height - emojiStatusSize.height) / 2.0)), size: emojiStatusSize)) } } transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)) transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) }) } func updateTheme(presentationData: PresentationData) { if let attributedText = self.textNode.attributedText { let updatedAttributedText = NSMutableAttributedString(attributedString: attributedText) updatedAttributedText.addAttribute(.foregroundColor, value: presentationData.theme.contextMenu.primaryColor.cgColor, range: NSRange(location: 0, length: updatedAttributedText.length)) self.textNode.attributedText = updatedAttributedText } } @objc private func buttonPressed() { self.performAction() } func canBeHighlighted() -> Bool { return true } func setIsHighlighted(_ value: Bool) { } func updateIsHighlighted(isHighlighted: Bool) { self.setIsHighlighted(isHighlighted) } func performAction() { guard let controller = self.getController() else { return } self.item.action(controller, { [weak self] result in self?.actionSelected(result) }) } }