mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-05 14:02:48 +00:00
[WIP] Quotes
This commit is contained in:
parent
eae866c77e
commit
f8626f2c2c
@ -298,6 +298,7 @@ public enum PresentationResourceKey: Int32 {
|
||||
case navigationPostStoryIcon
|
||||
|
||||
case chatReplyBackgroundTemplateImage
|
||||
case chatReplyServiceBackgroundTemplateImage
|
||||
}
|
||||
|
||||
public enum ChatExpiredStoryIndicatorType: Hashable {
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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": [],
|
||||
|
||||
@ -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)
|
||||
]
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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() {
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ import ChatMessageForwardInfoNode
|
||||
import ChatMessageDateAndStatusNode
|
||||
import ChatMessageItemCommon
|
||||
import ChatMessageBubbleContentNode
|
||||
import ChatMessageReplyInfoNode
|
||||
|
||||
private let nameFont = Font.medium(14.0)
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ import ChatMessageForwardInfoNode
|
||||
import ChatMessageDateAndStatusNode
|
||||
import ChatMessageItemCommon
|
||||
import ChatMessageBubbleContentNode
|
||||
import ChatMessageReplyInfoNode
|
||||
|
||||
struct ChatMessageInstantVideoItemLayoutResult {
|
||||
let contentSize: CGSize
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user