import Foundation import UIKit import AsyncDisplayKit import ContextUI import TelegramPresentationData import Display enum PeerInfoHeaderNavigationButtonKey { case back case edit case done case cancel case select case selectionDone case search case editPhoto case editVideo case more case qrCode case moreToSearch case postStory } final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { let containerNode: ContextControllerSourceNode let contextSourceNode: ContextReferenceContentNode private let textNode: ImmediateTextNode private let iconNode: ASImageNode private let backIconLayer: SimpleShapeLayer private let backgroundNode: NavigationBackgroundNode private var key: PeerInfoHeaderNavigationButtonKey? private var contentsColor: UIColor = .white private var canBeExpanded: Bool = false var action: ((ASDisplayNode, ContextGesture?) -> Void)? init() { self.contextSourceNode = ContextReferenceContentNode() self.containerNode = ContextControllerSourceNode() self.containerNode.animateScale = false self.textNode = ImmediateTextNode() self.iconNode = ASImageNode() self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true self.backIconLayer = SimpleShapeLayer() self.backIconLayer.lineWidth = 3.0 self.backIconLayer.lineCap = .round self.backIconLayer.lineJoin = .round self.backIconLayer.strokeColor = UIColor.white.cgColor self.backIconLayer.fillColor = nil self.backIconLayer.isHidden = true self.backIconLayer.path = try? convertSvgPath("M10.5,2 L1.5,11 L10.5,20 ") self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true) super.init(pointerStyle: .insetRectangle(-8.0, 2.0)) self.isAccessibilityElement = true self.accessibilityTraits = .button self.containerNode.addSubnode(self.contextSourceNode) self.contextSourceNode.addSubnode(self.backgroundNode) self.contextSourceNode.addSubnode(self.textNode) self.contextSourceNode.addSubnode(self.iconNode) self.contextSourceNode.layer.addSublayer(self.backIconLayer) self.addSubnode(self.containerNode) self.containerNode.activated = { [weak self] gesture, _ in guard let strongSelf = self else { return } strongSelf.action?(strongSelf.contextSourceNode, gesture) } self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) } @objc private func pressed() { self.action?(self.contextSourceNode, nil) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { var boundingRect = self.bounds if self.textNode.alpha != 0.0 { boundingRect = boundingRect.union(self.textNode.frame) } boundingRect = boundingRect.insetBy(dx: -8.0, dy: -4.0) if boundingRect.contains(point) { return super.hitTest(self.bounds.center, with: event) } else { return nil } } func updateContentsColor(backgroundColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) { self.contentsColor = contentsColor self.canBeExpanded = canBeExpanded self.backgroundNode.updateColor(color: backgroundColor, transition: transition) transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) transition.updateTintColor(layer: self.iconNode.layer, color: self.contentsColor) transition.updateStrokeColor(layer: self.backIconLayer, strokeColor: self.contentsColor) switch self.key { case .back: transition.updateAlpha(layer: self.textNode.layer, alpha: canBeExpanded ? 1.0 : 0.0) transition.updateTransformScale(node: self.textNode, scale: canBeExpanded ? 1.0 : 0.001) var iconTransform = CATransform3DIdentity iconTransform = CATransform3DScale(iconTransform, canBeExpanded ? 1.0 : 0.8, canBeExpanded ? 1.0 : 0.8, 1.0) iconTransform = CATransform3DTranslate(iconTransform, canBeExpanded ? -7.0 : 0.0, 0.0, 0.0) transition.updateTransform(node: self.iconNode, transform: CATransform3DGetAffineTransform(iconTransform)) transition.updateTransform(layer: self.backIconLayer, transform: CATransform3DGetAffineTransform(iconTransform)) transition.updateLineWidth(layer: self.backIconLayer, lineWidth: canBeExpanded ? 3.0 : 2.075) default: break } } func update(key: PeerInfoHeaderNavigationButtonKey, presentationData: PresentationData, height: CGFloat) -> CGSize { let transition: ContainedViewLayoutTransition = .immediate var iconOffset = CGPoint() switch key { case .back: iconOffset = CGPoint(x: -1.0, y: 0.0) default: break } let textSize: CGSize if self.key != key { self.key = key let text: String var accessibilityText: String var icon: UIImage? var isBold = false var isGestureEnabled = false switch key { case .back: text = presentationData.strings.Common_Back accessibilityText = presentationData.strings.Common_Back icon = NavigationBar.backArrowImage(color: .white) case .edit: text = presentationData.strings.Common_Edit accessibilityText = text case .cancel: text = presentationData.strings.Common_Cancel accessibilityText = text isBold = false case .done, .selectionDone: text = presentationData.strings.Common_Done accessibilityText = text isBold = true case .select: text = presentationData.strings.Common_Select accessibilityText = text case .search: text = "" accessibilityText = presentationData.strings.Common_Search icon = nil// PresentationResourcesRootController.navigationCompactSearchIcon(presentationData.theme) case .editPhoto: text = presentationData.strings.Settings_EditPhoto accessibilityText = text case .editVideo: text = presentationData.strings.Settings_EditVideo accessibilityText = text case .more: text = "" accessibilityText = presentationData.strings.Common_More icon = nil// PresentationResourcesRootController.navigationMoreCircledIcon(presentationData.theme) isGestureEnabled = true case .qrCode: text = "" accessibilityText = presentationData.strings.PeerInfo_QRCode_Title icon = PresentationResourcesRootController.navigationQrCodeIcon(presentationData.theme) case .moreToSearch: text = "" accessibilityText = "" case .postStory: text = "" accessibilityText = presentationData.strings.Story_Privacy_PostStory icon = PresentationResourcesRootController.navigationPostStoryIcon(presentationData.theme) } self.accessibilityLabel = accessibilityText self.containerNode.isGestureEnabled = isGestureEnabled let font: UIFont = isBold ? Font.semibold(17.0) : Font.regular(17.0) self.textNode.attributedText = NSAttributedString(string: text, font: font, textColor: .white) transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) self.iconNode.image = icon transition.updateTintColor(layer: self.iconNode.layer, color: self.contentsColor) self.iconNode.isHidden = false textSize = self.textNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) } else { textSize = self.textNode.bounds.size } let inset: CGFloat = 0.0 var textInset: CGFloat = 0.0 switch key { case .back: textInset += 11.0 default: break } let resultSize: CGSize let textFrame = CGRect(origin: CGPoint(x: inset + textInset, y: floor((height - textSize.height) / 2.0)), size: textSize) self.textNode.position = textFrame.center self.textNode.bounds = CGRect(origin: CGPoint(), size: textFrame.size) if let image = self.iconNode.image { let iconFrame = CGRect(origin: CGPoint(x: inset, y: floor((height - image.size.height) / 2.0)), size: image.size).offsetBy(dx: iconOffset.x, dy: iconOffset.y) self.iconNode.position = iconFrame.center self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) if case .back = key { self.backIconLayer.position = iconFrame.center self.backIconLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) self.iconNode.isHidden = true self.backIconLayer.isHidden = false } else { self.iconNode.isHidden = false self.backIconLayer.isHidden = true } let size = CGSize(width: image.size.width + inset * 2.0, height: height) self.containerNode.frame = CGRect(origin: CGPoint(), size: size) self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size) resultSize = size } else { let size = CGSize(width: textSize.width + inset * 2.0, height: height) self.containerNode.frame = CGRect(origin: CGPoint(), size: size) self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size) resultSize = size } let diameter: CGFloat = 32.0 let backgroundWidth: CGFloat if self.iconNode.image != nil { backgroundWidth = diameter } else { backgroundWidth = max(diameter, resultSize.width + 12.0 * 2.0) } let backgroundFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - backgroundWidth) * 0.5), y: floor((resultSize.height - diameter) * 0.5)), size: CGSize(width: backgroundWidth, height: diameter)) transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) self.backgroundNode.update(size: backgroundFrame.size, cornerRadius: diameter * 0.5, transition: transition) self.hitTestSlop = UIEdgeInsets(top: -2.0, left: -12.0, bottom: -2.0, right: -12.0) return resultSize } }