import Foundation import UIKit import AsyncDisplayKit import Display import TelegramPresentationData import ChatControllerInteraction import AccountContext import TelegramCore import Postbox import WallpaperBackgroundNode import ChatMessageItemCommon public class ChatMessageShareButton: ASDisplayNode { private var backgroundContent: WallpaperBubbleBackgroundNode? private var backgroundBlurView: PortalView? private let topButton: HighlightTrackingButtonNode private let topIconNode: ASImageNode private var topIconOffset = CGPoint() private var bottomButton: HighlightTrackingButtonNode? private var bottomIconNode: ASImageNode? private var separatorNode: ASDisplayNode? private var theme: PresentationTheme? private var isReplies: Bool = false private var textNode: ImmediateTextNode? private var absolutePosition: (CGRect, CGSize)? public var pressed: (() -> Void)? public var morePressed: (() -> Void)? override public init() { self.topButton = HighlightTrackingButtonNode() self.topIconNode = ASImageNode() self.topIconNode.displaysAsynchronously = false super.init() self.allowsGroupOpacity = true self.addSubnode(self.topIconNode) self.addSubnode(self.topButton) self.topButton.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.topButton.highligthedChanged = { [weak self] highlighted in guard let self else { return } if highlighted { self.topIconNode.layer.removeAnimation(forKey: "opacity") self.topIconNode.alpha = 0.4 self.textNode?.layer.removeAnimation(forKey: "opacity") self.textNode?.alpha = 0.4 } else { self.topIconNode.alpha = 1.0 self.topIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) self.textNode?.alpha = 1.0 self.textNode?.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) } } } required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func buttonPressed() { self.pressed?() } @objc private func moreButtonPressed() { self.morePressed?() } public func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false) -> CGSize { var isReplies = false var replyCount = 0 if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { for attribute in message.attributes { if let attribute = attribute as? ReplyThreadMessageAttribute { replyCount = Int(attribute.count) isReplies = true break } } } if case let .replyThread(replyThreadMessage) = chatLocation, replyThreadMessage.effectiveTopId == message.id { replyCount = 0 isReplies = false } if disableComments { replyCount = 0 isReplies = false } if self.theme !== presentationData.theme.theme || self.isReplies != isReplies { self.theme = presentationData.theme.theme self.isReplies = isReplies var updatedIconImage: UIImage? var updatedBottomIconImage: UIImage? var updatedIconOffset = CGPoint() if message.adAttribute != nil { updatedIconImage = PresentationResourcesChat.chatFreeCloseButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) updatedIconOffset = CGPoint(x: UIScreenPixel, y: UIScreenPixel) updatedBottomIconImage = PresentationResourcesChat.chatFreeMoreButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) } else if case .pinnedMessages = subject { updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) } else if isReplies { updatedIconImage = PresentationResourcesChat.chatFreeCommentButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) } else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: account.peerId) { updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) } else { updatedIconImage = PresentationResourcesChat.chatFreeShareButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) } self.topIconNode.image = updatedIconImage self.topIconOffset = updatedIconOffset if let updatedBottomIconImage { let bottomButton: HighlightTrackingButtonNode let bottomIconNode: ASImageNode let separatorNode: ASDisplayNode if let currentButton = self.bottomButton, let currentIcon = self.bottomIconNode, let currentSeparator = self.separatorNode { bottomButton = currentButton bottomIconNode = currentIcon separatorNode = currentSeparator } else { bottomButton = HighlightTrackingButtonNode() bottomButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside) self.bottomButton = bottomButton bottomIconNode = ASImageNode() bottomIconNode.displaysAsynchronously = false self.bottomIconNode = bottomIconNode bottomButton.highligthedChanged = { [weak self] highlighted in guard let self, let bottomIconNode = self.bottomIconNode else { return } if highlighted { bottomIconNode.layer.removeAnimation(forKey: "opacity") bottomIconNode.alpha = 0.4 } else { bottomIconNode.alpha = 1.0 bottomIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) } } separatorNode = ASDisplayNode() self.separatorNode = separatorNode self.addSubnode(separatorNode) self.addSubnode(bottomIconNode) self.addSubnode(bottomButton) } separatorNode.backgroundColor = bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: presentationData.theme.wallpaper).withAlphaComponent(0.15) bottomIconNode.image = updatedBottomIconImage } else { self.bottomButton?.removeFromSupernode() self.bottomButton = nil self.bottomIconNode?.removeFromSupernode() self.bottomIconNode = nil self.separatorNode?.removeFromSupernode() self.separatorNode = nil } } var size = CGSize(width: 30.0, height: 30.0) if message.adAttribute != nil { size.height += 30.0 } var offsetIcon = false if isReplies, replyCount > 0 { offsetIcon = true let textNode: ImmediateTextNode if let current = self.textNode { textNode = current } else { textNode = ImmediateTextNode() self.textNode = textNode self.addSubnode(textNode) } let textColor = bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: presentationData.theme.wallpaper) let countString: String if replyCount >= 1000 * 1000 { countString = "\(replyCount / 1000_000)M" } else if replyCount >= 1000 { countString = "\(replyCount / 1000)K" } else { countString = "\(replyCount)" } textNode.attributedText = NSAttributedString(string: countString, font: Font.regular(11.0), textColor: textColor) let textSize = textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) size.height += textSize.height - 1.0 textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - textSize.height - 4.0), size: textSize) } else if let textNode = self.textNode { self.textNode = nil textNode.removeFromSupernode() } if self.backgroundBlurView == nil { if let backgroundBlurView = controllerInteraction.presentationContext.backgroundNode?.makeFreeBackground() { self.backgroundBlurView = backgroundBlurView self.view.insertSubview(backgroundBlurView.view, at: 0) backgroundBlurView.view.clipsToBounds = true } } if let backgroundBlurView = self.backgroundBlurView { backgroundBlurView.view.frame = CGRect(origin: CGPoint(), size: size) backgroundBlurView.view.layer.cornerRadius = min(size.width, size.height) / 2.0 } if let image = self.topIconNode.image { self.topIconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0) + self.topIconOffset.x, y: floor((size.width - image.size.width) / 2.0) - (offsetIcon ? 1.0 : 0.0) + self.topIconOffset.y), size: image.size) } self.topButton.frame = CGRect(origin: .zero, size: CGSize(width: size.width, height: size.width)) if let bottomIconNode = self.bottomIconNode, let bottomButton = self.bottomButton, let bottomImage = bottomIconNode.image { bottomIconNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - bottomImage.size.width) / 2.0), y: size.height - size.width + floorToScreenPixels((size.width - bottomImage.size.height) / 2.0)), size: bottomImage.size) bottomButton.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - size.width), size: CGSize(width: size.width, height: size.width)) } self.separatorNode?.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height / 2.0), size: CGSize(width: size.width, height: 1.0 - UIScreenPixel)) if controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { if self.backgroundContent == nil, let backgroundContent = controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { backgroundContent.clipsToBounds = true self.backgroundContent = backgroundContent self.insertSubnode(backgroundContent, at: 0) } } else { self.backgroundContent?.removeFromSupernode() self.backgroundContent = nil } if let backgroundContent = self.backgroundContent { //self.backgroundNode.isHidden = true self.backgroundBlurView?.view.isHidden = true backgroundContent.cornerRadius = min(size.width, size.height) / 2.0 backgroundContent.frame = CGRect(origin: CGPoint(), size: size) if let (rect, containerSize) = self.absolutePosition { var backgroundFrame = backgroundContent.frame backgroundFrame.origin.x += rect.minX backgroundFrame.origin.y += rect.minY backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) } } else { //self.backgroundNode.isHidden = false self.backgroundBlurView?.view.isHidden = false } return size } public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absolutePosition = (rect, containerSize) if let backgroundContent = self.backgroundContent { var backgroundFrame = backgroundContent.frame backgroundFrame.origin.x += rect.minX backgroundFrame.origin.y += rect.minY backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) } } }