diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 768f885afb..a62d9c832c 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -298,6 +298,7 @@ public enum PresentationResourceKey: Int32 { case navigationPostStoryIcon case chatReplyBackgroundTemplateImage + case chatReplyServiceBackgroundTemplateImage } public enum ChatExpiredStoryIndicatorType: Hashable { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 8af17d9eb5..bceb49c555 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1303,4 +1303,17 @@ public struct PresentationResourcesChat { })?.stretchableImage(withLeftCapWidth: Int(radius), topCapHeight: Int(radius)).withRenderingMode(.alwaysTemplate) }) } + + public static func chatReplyServiceBackgroundTemplateImage(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatReplyServiceBackgroundTemplateImage.rawValue, { theme in + let radius: CGFloat = 3.0 + + return generateImage(CGSize(width: radius * 2.0 + 1.0, height: radius * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: CGSize(width: radius, height: size.height)), cornerRadius: radius).cgPath) + context.fillPath() + })?.stretchableImage(withLeftCapWidth: Int(radius), topCapHeight: Int(radius)).withRenderingMode(.alwaysTemplate) + }) + } } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 7cebb1e04f..435b80283a 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -347,6 +347,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode", "//submodules/TelegramUI/Components/Chat/EditableTokenListNode", "//submodules/TelegramUI/Components/Chat/ChatInputTextNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift index ecd3d1fc34..6c24b99597 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift @@ -190,3 +190,13 @@ public func canViewMessageReactionList(message: Message) -> Bool { return false } } + +public let chatMessagePeerIdColors: [UIColor] = [ + UIColor(rgb: 0xfc5c51), + UIColor(rgb: 0xfa790f), + UIColor(rgb: 0x895dd5), + UIColor(rgb: 0x0fb297), + UIColor(rgb: 0x00c0c2), + UIColor(rgb: 0x3ca5ec), + UIColor(rgb: 0x3d72ed) +] diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD index 34a62a8140..d4d5e85187 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD @@ -11,8 +11,21 @@ swift_library( ], deps = [ "//submodules/AsyncDisplayKit", + "//submodules/Postbox", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", - "//submodules/ChatPresentationInterfaceState", + "//submodules/AccountContext", + "//submodules/LocalizedPeerData", + "//submodules/PhotoResources", + "//submodules/TelegramStringFormatting", + "//submodules/TextFormat", + "//submodules/InvisibleInkDustNode", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TelegramUI/Components/AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/AccessoryPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/AccessoryPanelNode.swift deleted file mode 100644 index 17ff62255f..0000000000 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/AccessoryPanelNode.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import TelegramPresentationData -import ChatPresentationInterfaceState - -open class AccessoryPanelNode: ASDisplayNode { - open var originalFrameBeforeDismissed: CGRect? - open var dismiss: (() -> Void)? - open var interfaceInteraction: ChatPanelInterfaceInteraction? - - open func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - } - - open func updateState(size: CGSize, inset: CGFloat, interfaceState: ChatPresentationInterfaceState) { - } - - open func animateIn() { - } - - open func animateOut() { - } -} diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift similarity index 89% rename from submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index bbb0ef658c..c8ddf92588 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -15,13 +15,36 @@ import InvisibleInkDustNode import TextNodeWithEntities import AnimationCache import MultiAnimationRenderer +import ChatMessageItemCommon public enum ChatMessageReplyInfoType { case bubble(incoming: Bool) case standalone } +private let quoteIcon: UIImage = { + return UIImage(bundleImageName: "Chat/Message/ReplyQuoteIcon")!.precomposed().withRenderingMode(.alwaysTemplate) +}() + public class ChatMessageReplyInfoNode: ASDisplayNode { + public final class TransitionReplyPanel { + public let titleNode: ASDisplayNode + public let textNode: ASDisplayNode + public let lineNode: ASDisplayNode + public let imageNode: ASDisplayNode + public let relativeSourceRect: CGRect + public let relativeTargetRect: CGRect + + public init(titleNode: ASDisplayNode, textNode: ASDisplayNode, lineNode: ASDisplayNode, imageNode: ASDisplayNode, relativeSourceRect: CGRect, relativeTargetRect: CGRect) { + self.titleNode = titleNode + self.textNode = textNode + self.lineNode = lineNode + self.imageNode = imageNode + self.relativeSourceRect = relativeSourceRect + self.relativeTargetRect = relativeTargetRect + } + } + public class Arguments { public let presentationData: ChatPresentationData public let strings: PresentationStrings @@ -74,6 +97,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } private let backgroundView: UIImageView + private var quoteIconView: UIImageView? private let contentNode: ASDisplayNode private var titleNode: TextNode? private var textNode: TextNodeWithEntities? @@ -331,23 +355,29 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { let textInsets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0) - let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets)) - if isExpiredStory || isStory { - contrainedTextSize.width -= 26.0 - } + var additionalTitleWidth: CGFloat = 0.0 var maxTextNumberOfLines = 1 var adjustedConstrainedTextSize = contrainedTextSize var textCutout: TextNodeCutout? var textCutoutWidth: CGFloat = 0.0 if arguments.quote != nil { - maxTextNumberOfLines = 5 - if imageTextInset != 0.0 { - adjustedConstrainedTextSize.width += imageTextInset - textCutout = TextNodeCutout(topLeft: CGSize(width: imageTextInset, height: 10.0)) - textCutoutWidth = imageTextInset + 4.0 + additionalTitleWidth += 10.0 + if case .bubble = arguments.type { + maxTextNumberOfLines = 5 + if imageTextInset != 0.0 { + adjustedConstrainedTextSize.width += imageTextInset + textCutout = TextNodeCutout(topLeft: CGSize(width: imageTextInset + 6.0, height: 10.0)) + textCutoutWidth = imageTextInset + 6.0 + } } } - let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: maxTextNumberOfLines, truncationType: .end, constrainedSize: adjustedConstrainedTextSize, alignment: .natural, lineSpacing: 0.05, cutout: textCutout, insets: textInsets)) + + let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: contrainedTextSize.width - additionalTitleWidth, height: contrainedTextSize.height), alignment: .natural, cutout: nil, insets: textInsets)) + if isExpiredStory || isStory { + contrainedTextSize.width -= 26.0 + } + + let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: maxTextNumberOfLines, truncationType: .end, constrainedSize: adjustedConstrainedTextSize, alignment: .natural, lineSpacing: 0.07, cutout: textCutout, insets: textInsets)) let imageSide: CGFloat imageSide = titleLayout.size.height + titleLayout.size.height - 14.0 @@ -401,7 +431,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } } - var size = CGSize(width: max(titleLayout.size.width - textInsets.left - textInsets.right, textLayout.size.width - textInsets.left - textInsets.right - textCutoutWidth) + leftInset + 6.0, height: titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing) + var size = CGSize(width: max(titleLayout.size.width + additionalTitleWidth - textInsets.left - textInsets.right, textLayout.size.width - textInsets.left - textInsets.right - textCutoutWidth) + leftInset + 6.0, height: titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing) if isExpiredStory || isStory { size.width += 16.0 } @@ -446,7 +476,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { node.addSubnode(imageNode) node.imageNode = imageNode } - imageNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 4.0), size: CGSize(width: imageSide, height: imageSide)) + imageNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 3.0 + UIScreenPixel), size: CGSize(width: imageSide, height: imageSide)) if let updateImageSignal = updateImageSignal { imageNode.setSignal(updateImageSignal) @@ -519,14 +549,41 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } if node.backgroundView.image == nil { - node.backgroundView.image = PresentationResourcesChat.chatReplyBackgroundTemplateImage(arguments.presentationData.theme.theme) + if case .standalone = arguments.type { + node.backgroundView.image = PresentationResourcesChat.chatReplyServiceBackgroundTemplateImage(arguments.presentationData.theme.theme) + } else { + node.backgroundView.image = PresentationResourcesChat.chatReplyBackgroundTemplateImage(arguments.presentationData.theme.theme) + } if node.backgroundView.superview == nil { node.contentNode.view.insertSubview(node.backgroundView, at: 0) } } + var backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: realSize.width, height: realSize.height + 2.0)) + if case .standalone = arguments.type { + backgroundFrame.size.height -= 1.0 + } + node.backgroundView.tintColor = mainColor - node.backgroundView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: realSize.width, height: realSize.height + 2.0)) + node.backgroundView.frame = backgroundFrame + + if arguments.quote != nil { + let quoteIconView: UIImageView + if let current = node.quoteIconView { + quoteIconView = current + } else { + quoteIconView = UIImageView(image: quoteIcon) + node.quoteIconView = quoteIconView + node.contentNode.view.addSubview(quoteIconView) + } + quoteIconView.tintColor = mainColor + quoteIconView.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - 4.0 - quoteIcon.size.width, y: backgroundFrame.minY + 4.0), size: quoteIcon.size) + } else { + if let quoteIconView = node.quoteIconView { + node.quoteIconView = nil + quoteIconView.removeFromSuperview() + } + } node.contentNode.frame = CGRect(origin: CGPoint(), size: size) @@ -535,7 +592,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } } - func animateFromInputPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, unclippedTransitionNode: ASDisplayNode? = nil, localRect: CGRect, transition: CombinedTransition) -> CGPoint { + public func animateFromInputPanel(sourceReplyPanel: TransitionReplyPanel, unclippedTransitionNode: ASDisplayNode? = nil, localRect: CGRect, transition: CombinedTransition) -> CGPoint { let sourceParentNode = ASDisplayNode() let sourceParentOffset: CGPoint diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index a5181021e4..182ef1fd21 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -32,6 +32,7 @@ import ChatMessageForwardInfoNode import ChatMessageDateAndStatusNode import ChatMessageItemCommon import ChatMessageBubbleContentNode +import ChatMessageReplyInfoNode private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) @@ -2874,7 +2875,15 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if let replyInfoNode = self.replyInfoNode { let localRect = self.contextSourceNode.contentNode.view.convert(sourceReplyPanel.relativeSourceRect, to: replyInfoNode.view) - let offset = replyInfoNode.animateFromInputPanel(sourceReplyPanel: sourceReplyPanel, localRect: localRect, transition: transition) + let mappedPanel = ChatMessageReplyInfoNode.TransitionReplyPanel( + titleNode: sourceReplyPanel.titleNode, + textNode: sourceReplyPanel.textNode, + lineNode: sourceReplyPanel.lineNode, + imageNode: sourceReplyPanel.imageNode, + relativeSourceRect: sourceReplyPanel.relativeSourceRect, + relativeTargetRect: sourceReplyPanel.relativeTargetRect + ) + let offset = replyInfoNode.animateFromInputPanel(sourceReplyPanel: mappedPanel, localRect: localRect, transition: transition) if let replyBackgroundNode = self.replyBackgroundNode { transition.animatePositionAdditive(layer: replyBackgroundNode.layer, offset: offset) replyBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 58a953d368..20db4f4dd9 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -35,6 +35,7 @@ import ChatMessageBubbleContentNode import ChatHistoryEntry import ChatMessageTextBubbleContentNode import ChatMessageItemCommon +import ChatMessageReplyInfoNode enum InternalBubbleTapAction { case action(() -> Void) @@ -357,16 +358,6 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ return (result, needSeparateContainers, needReactions) } -let chatMessagePeerIdColors: [UIColor] = [ - UIColor(rgb: 0xfc5c51), - UIColor(rgb: 0xfa790f), - UIColor(rgb: 0x895dd5), - UIColor(rgb: 0x0fb297), - UIColor(rgb: 0x00c0c2), - UIColor(rgb: 0x3ca5ec), - UIColor(rgb: 0x3d72ed) -] - private enum ContentNodeOperation { case remove(index: Int) case insert(index: Int, node: ChatMessageBubbleContentNode) @@ -926,7 +917,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode func animateReplyPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, transition: CombinedTransition) { if let replyInfoNode = self.replyInfoNode { let localRect = self.mainContextSourceNode.contentNode.view.convert(sourceReplyPanel.relativeSourceRect, to: replyInfoNode.view) - let _ = replyInfoNode.animateFromInputPanel(sourceReplyPanel: sourceReplyPanel, unclippedTransitionNode: self.mainContextSourceNode.contentNode, localRect: localRect, transition: transition) + let mappedPanel = ChatMessageReplyInfoNode.TransitionReplyPanel( + titleNode: sourceReplyPanel.titleNode, + textNode: sourceReplyPanel.textNode, + lineNode: sourceReplyPanel.lineNode, + imageNode: sourceReplyPanel.imageNode, + relativeSourceRect: sourceReplyPanel.relativeSourceRect, + relativeTargetRect: sourceReplyPanel.relativeTargetRect + ) + let _ = replyInfoNode.animateFromInputPanel(sourceReplyPanel: mappedPanel, unclippedTransitionNode: self.mainContextSourceNode.contentNode, localRect: localRect, transition: transition) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 6468def148..890e52d475 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -17,6 +17,7 @@ import ChatMessageForwardInfoNode import ChatMessageDateAndStatusNode import ChatMessageItemCommon import ChatMessageBubbleContentNode +import ChatMessageReplyInfoNode private let nameFont = Font.medium(14.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 8669d67c08..e4559f28f5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -23,6 +23,7 @@ import ChatMessageForwardInfoNode import ChatMessageDateAndStatusNode import ChatMessageItemCommon import ChatMessageBubbleContentNode +import ChatMessageReplyInfoNode struct ChatMessageInstantVideoItemLayoutResult { let contentSize: CGSize diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 50f377f772..61bbc9310b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -17,6 +17,7 @@ import ChatControllerInteraction import ChatMessageForwardInfoNode import ChatMessageDateAndStatusNode import ChatMessageItemCommon +import ChatMessageReplyInfoNode private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) @@ -1847,8 +1848,16 @@ class ChatMessageStickerItemNode: ChatMessageItemView { func animateReplyPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, transition: CombinedTransition) { if let replyInfoNode = self.replyInfoNode { let localRect = self.contextSourceNode.contentNode.view.convert(sourceReplyPanel.relativeSourceRect, to: replyInfoNode.view) + let mappedPanel = ChatMessageReplyInfoNode.TransitionReplyPanel( + titleNode: sourceReplyPanel.titleNode, + textNode: sourceReplyPanel.textNode, + lineNode: sourceReplyPanel.lineNode, + imageNode: sourceReplyPanel.imageNode, + relativeSourceRect: sourceReplyPanel.relativeSourceRect, + relativeTargetRect: sourceReplyPanel.relativeTargetRect + ) - let offset = replyInfoNode.animateFromInputPanel(sourceReplyPanel: sourceReplyPanel, localRect: localRect, transition: transition) + let offset = replyInfoNode.animateFromInputPanel(sourceReplyPanel: mappedPanel, localRect: localRect, transition: transition) if let replyBackgroundNode = self.replyBackgroundNode { transition.animatePositionAdditive(layer: replyBackgroundNode.layer, offset: offset) replyBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)