[WIP] Quotes

This commit is contained in:
Ali 2023-10-07 01:17:55 +04:00
parent eae866c77e
commit f8626f2c2c
12 changed files with 143 additions and 52 deletions

View File

@ -298,6 +298,7 @@ public enum PresentationResourceKey: Int32 {
case navigationPostStoryIcon
case chatReplyBackgroundTemplateImage
case chatReplyServiceBackgroundTemplateImage
}
public enum ChatExpiredStoryIndicatorType: Hashable {

View File

@ -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)
})
}
}

View File

@ -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": [],

View File

@ -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)
]

View File

@ -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",

View File

@ -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() {
}
}

View File

@ -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 {
additionalTitleWidth += 10.0
if case .bubble = arguments.type {
maxTextNumberOfLines = 5
if imageTextInset != 0.0 {
adjustedConstrainedTextSize.width += imageTextInset
textCutout = TextNodeCutout(topLeft: CGSize(width: imageTextInset, height: 10.0))
textCutoutWidth = imageTextInset + 4.0
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 {
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

View File

@ -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)

View File

@ -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)
}
}

View File

@ -17,6 +17,7 @@ import ChatMessageForwardInfoNode
import ChatMessageDateAndStatusNode
import ChatMessageItemCommon
import ChatMessageBubbleContentNode
import ChatMessageReplyInfoNode
private let nameFont = Font.medium(14.0)

View File

@ -23,6 +23,7 @@ import ChatMessageForwardInfoNode
import ChatMessageDateAndStatusNode
import ChatMessageItemCommon
import ChatMessageBubbleContentNode
import ChatMessageReplyInfoNode
struct ChatMessageInstantVideoItemLayoutResult {
let contentSize: CGSize

View File

@ -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)