import Foundation import UIKit import Display import AsyncDisplayKit import Postbox import TelegramCore import TelegramPresentationData import LocalizedPeerData import TelegramStringFormatting import TelegramNotices import AnimatedAvatarSetNode import AccountContext import ChatPresentationInterfaceState import LegacyChatHeaderPanelComponent private final class ChatInfoTitlePanelPeerNearbyInfoNode: ASDisplayNode { private var theme: PresentationTheme? private let labelNode: ImmediateTextNode private let filledBackgroundNode: LinkHighlightingNode private let openPeersNearby: () -> Void init(openPeersNearby: @escaping () -> Void) { self.openPeersNearby = openPeersNearby self.labelNode = ImmediateTextNode() self.labelNode.maximumNumberOfLines = 1 self.labelNode.textAlignment = .center self.filledBackgroundNode = LinkHighlightingNode(color: .clear) super.init() self.addSubnode(self.filledBackgroundNode) self.addSubnode(self.labelNode) } override func didLoad() { super.didLoad() let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) self.view.addGestureRecognizer(tapRecognizer) } @objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) { self.openPeersNearby() } func update(width: CGFloat, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, chatPeer: Peer, distance: Int32, transition: ContainedViewLayoutTransition) -> CGFloat { let primaryTextColor = serviceMessageColorComponents(theme: theme, wallpaper: wallpaper).primaryText if self.theme !== theme { self.theme = theme self.labelNode.linkHighlightColor = primaryTextColor.withAlphaComponent(0.3) } let topInset: CGFloat = 6.0 let bottomInset: CGFloat = 6.0 let sideInset: CGFloat = 16.0 let stringAndRanges = strings.Conversation_PeerNearbyDistance(EnginePeer(chatPeer).compactDisplayTitle, shortStringForDistance(strings: strings, distance: distance)) let attributedString = NSMutableAttributedString(string: stringAndRanges.string, font: Font.regular(13.0), textColor: primaryTextColor) let boldAttributes = [NSAttributedString.Key.font: Font.semibold(13.0), NSAttributedString.Key(rawValue: "_Link"): true as NSNumber] for range in stringAndRanges.ranges.prefix(1) { attributedString.addAttributes(boldAttributes, range: range.range) } self.labelNode.attributedText = attributedString let labelLayout = self.labelNode.updateLayoutFullInfo(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude)) var labelRects = labelLayout.linesRects() if labelRects.count > 1 { let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width }) for i in 0 ..< sortedIndices.count { let index = sortedIndices[i] for j in -1 ... 1 { if j != 0 && index + j >= 0 && index + j < sortedIndices.count { if abs(labelRects[index + j].width - labelRects[index].width) < 40.0 { labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width) labelRects[index].size.width = labelRects[index + j].size.width } } } } } for i in 0 ..< labelRects.count { labelRects[i] = labelRects[i].insetBy(dx: -6.0, dy: floor((labelRects[i].height - 20.0) / 2.0)) labelRects[i].size.height = 20.0 labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0) } let backgroundLayout = self.filledBackgroundNode.asyncLayout() let serviceColor = serviceMessageColorComponents(theme: theme, wallpaper: wallpaper) let backgroundApply = backgroundLayout(serviceColor.fill, labelRects, 10.0, 10.0, 0.0) backgroundApply() let backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0) let labelFrame = CGRect(origin: CGPoint(x: floor((width - labelLayout.size.width) / 2.0), y: topInset + floorToScreenPixels((backgroundSize.height - labelLayout.size.height) / 2.0) - 1.0), size: labelLayout.size) self.labelNode.frame = labelFrame self.filledBackgroundNode.frame = labelFrame.offsetBy(dx: 0.0, dy: -11.0) return topInset + backgroundSize.height + bottomInset } } final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { private final class Params { let width: CGFloat let leftInset: CGFloat let rightInset: CGFloat let interfaceState: ChatPresentationInterfaceState init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, interfaceState: ChatPresentationInterfaceState) { self.width = width self.leftInset = leftInset self.rightInset = rightInset self.interfaceState = interfaceState } } private let context: AccountContext private let separatorNode: ASDisplayNode private let closeButton: HighlightableButtonNode private let button: HighlightableButtonNode private let buttonTitle: ImmediateTextNode private let avatarsContext: AnimatedAvatarSetContext private var avatarsContent: AnimatedAvatarSetContext.Content? private let avatarsNode: AnimatedAvatarSetNode private let activateAreaNode: AccessibilityAreaNode private var theme: PresentationTheme? private var peerId: PeerId? private var peers: [EnginePeer] = [] private var count: Int32 = 0 private var params: Params? init(context: AccountContext) { self.context = context self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true self.closeButton = HighlightableButtonNode() self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) self.closeButton.displaysAsynchronously = false self.button = HighlightableButtonNode() self.buttonTitle = ImmediateTextNode() self.buttonTitle.anchorPoint = CGPoint() self.avatarsContext = AnimatedAvatarSetContext() self.avatarsNode = AnimatedAvatarSetNode() self.activateAreaNode = AccessibilityAreaNode() self.activateAreaNode.accessibilityTraits = .button super.init() self.addSubnode(self.separatorNode) self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside]) self.addSubnode(self.closeButton) self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.addSubnode(self.button) self.buttonTitle.isUserInteractionEnabled = false self.button.addSubnode(self.buttonTitle) self.addSubnode(self.avatarsNode) self.addSubnode(self.activateAreaNode) } func update(peerId: PeerId, peers: [EnginePeer], count: Int32) { self.peerId = peerId self.peers = peers self.count = count self.avatarsContent = self.avatarsContext.update(peers: peers, animated: false) if let params = self.params { let _ = self.updateLayout(width: params.width, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate, interfaceState: params.interfaceState) } } override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult { self.params = Params(width: width, leftInset: leftInset, rightInset: rightInset, interfaceState: interfaceState) if interfaceState.theme !== self.theme { self.theme = interfaceState.theme self.closeButton.setImage(PresentationResourcesChat.chatInputPanelEncircledCloseIconImage(interfaceState.theme), for: []) self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor } let panelHeight: CGFloat = 40.0 let contentRightInset: CGFloat = 14.0 + rightInset let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: width - contentRightInset - closeButtonSize.width, y: floorToScreenPixels((panelHeight - closeButtonSize.height) / 2.0)), size: closeButtonSize)) self.buttonTitle.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_RequestsToJoin(self.count), font: Font.regular(16.0), textColor: interfaceState.theme.rootController.navigationBar.accentTextColor) transition.updateFrame(node: self.button, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: panelHeight))) let titleSize = self.buttonTitle.updateLayout(CGSize(width: width - leftInset - 90.0 - contentRightInset, height: 100.0)) var buttonTitleFrame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - titleSize.width) * 0.5), y: floor((panelHeight - titleSize.height) * 0.5)), size: titleSize) buttonTitleFrame.origin.x = max(buttonTitleFrame.minX, leftInset + 90.0) transition.updatePosition(node: self.buttonTitle, position: buttonTitleFrame.origin) self.buttonTitle.bounds = CGRect(origin: CGPoint(), size: buttonTitleFrame.size) let initialPanelHeight = panelHeight transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) if let avatarsContent = self.avatarsContent { let avatarsSize = self.avatarsNode.update(context: self.context, content: avatarsContent, itemSize: CGSize(width: 32.0, height: 32.0), animated: true, synchronousLoad: true) transition.updateFrame(node: self.avatarsNode, frame: CGRect(origin: CGPoint(x: leftInset + 8.0, y: floor((panelHeight - avatarsSize.height) / 2.0)), size: avatarsSize)) } self.activateAreaNode.frame = CGRect(origin: .zero, size: CGSize(width: width, height: panelHeight)) self.activateAreaNode.accessibilityLabel = interfaceState.strings.Conversation_RequestsToJoin(self.count) return LayoutResult(backgroundHeight: initialPanelHeight, insetHeight: panelHeight, hitTestSlop: 0.0) } @objc func buttonPressed() { self.interfaceInteraction?.openInviteRequests() } @objc func closePressed() { guard let peerId = self.peerId else { return } let ids = peers.map { $0.id.toInt64() } let _ = ApplicationSpecificNotice.setDismissedInvitationRequests(accountManager: context.sharedContext.accountManager, peerId: peerId, values: ids).startStandalone() } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let result = self.closeButton.hitTest(CGPoint(x: point.x - self.closeButton.frame.minX, y: point.y - self.closeButton.frame.minY), with: event) { return result } return super.hitTest(point, with: event) } }