mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Text-webpage position swap animation
This commit is contained in:
parent
db38904254
commit
56bf7fac1e
@ -177,7 +177,9 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
|||||||
|
|
||||||
private let messageClipNode: ASDisplayNode
|
private let messageClipNode: ASDisplayNode
|
||||||
private let messageBackgroundNode: ASImageNode
|
private let messageBackgroundNode: ASImageNode
|
||||||
|
private let fromMessageTextScrollView: UIScrollView
|
||||||
private let fromMessageTextNode: ChatInputTextNode
|
private let fromMessageTextNode: ChatInputTextNode
|
||||||
|
private let toMessageTextScrollView: UIScrollView
|
||||||
private let toMessageTextNode: ChatInputTextNode
|
private let toMessageTextNode: ChatInputTextNode
|
||||||
private let scrollNode: ASScrollNode
|
private let scrollNode: ASScrollNode
|
||||||
|
|
||||||
@ -225,10 +227,16 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
|||||||
self.messageBackgroundNode = ASImageNode()
|
self.messageBackgroundNode = ASImageNode()
|
||||||
self.messageBackgroundNode.isUserInteractionEnabled = true
|
self.messageBackgroundNode.isUserInteractionEnabled = true
|
||||||
self.fromMessageTextNode = ChatInputTextNode()
|
self.fromMessageTextNode = ChatInputTextNode()
|
||||||
|
self.fromMessageTextNode.textView.isScrollEnabled = false
|
||||||
self.fromMessageTextNode.isUserInteractionEnabled = false
|
self.fromMessageTextNode.isUserInteractionEnabled = false
|
||||||
|
self.fromMessageTextScrollView = UIScrollView()
|
||||||
|
self.fromMessageTextScrollView.isUserInteractionEnabled = false
|
||||||
self.toMessageTextNode = ChatInputTextNode()
|
self.toMessageTextNode = ChatInputTextNode()
|
||||||
self.toMessageTextNode.alpha = 0.0
|
self.toMessageTextNode.textView.isScrollEnabled = false
|
||||||
self.toMessageTextNode.isUserInteractionEnabled = false
|
self.toMessageTextNode.isUserInteractionEnabled = false
|
||||||
|
self.toMessageTextScrollView = UIScrollView()
|
||||||
|
self.toMessageTextScrollView.alpha = 0.0
|
||||||
|
self.toMessageTextScrollView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.scrollNode = ASScrollNode()
|
self.scrollNode = ASScrollNode()
|
||||||
self.scrollNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
|
self.scrollNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
|
||||||
@ -304,8 +312,10 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
|||||||
self.addSubnode(self.sendButtonNode)
|
self.addSubnode(self.sendButtonNode)
|
||||||
self.scrollNode.addSubnode(self.messageClipNode)
|
self.scrollNode.addSubnode(self.messageClipNode)
|
||||||
self.messageClipNode.addSubnode(self.messageBackgroundNode)
|
self.messageClipNode.addSubnode(self.messageBackgroundNode)
|
||||||
self.messageClipNode.addSubnode(self.fromMessageTextNode)
|
self.messageClipNode.view.addSubview(self.fromMessageTextScrollView)
|
||||||
self.messageClipNode.addSubnode(self.toMessageTextNode)
|
self.fromMessageTextScrollView.addSubview(self.fromMessageTextNode.view)
|
||||||
|
self.messageClipNode.view.addSubview(self.toMessageTextScrollView)
|
||||||
|
self.toMessageTextScrollView.addSubview(self.toMessageTextNode.view)
|
||||||
|
|
||||||
self.contentNodes.forEach(self.contentContainerNode.addSubnode)
|
self.contentNodes.forEach(self.contentContainerNode.addSubnode)
|
||||||
|
|
||||||
@ -469,12 +479,12 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
|||||||
|
|
||||||
self.sourceSendButton.isHidden = true
|
self.sourceSendButton.isHidden = true
|
||||||
if self.animateInputField {
|
if self.animateInputField {
|
||||||
self.fromMessageTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
self.fromMessageTextScrollView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
self.toMessageTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: false)
|
self.toMessageTextScrollView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: false)
|
||||||
} else {
|
} else {
|
||||||
self.messageBackgroundNode.isHidden = true
|
self.messageBackgroundNode.isHidden = true
|
||||||
self.fromMessageTextNode.isHidden = true
|
self.fromMessageTextScrollView.isHidden = true
|
||||||
self.toMessageTextNode.isHidden = true
|
self.toMessageTextScrollView.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let duration = 0.4
|
let duration = 0.4
|
||||||
@ -514,8 +524,8 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
|||||||
if self.textInputView.numberOfLines == 1 && self.textInputView.isRTL {
|
if self.textInputView.numberOfLines == 1 && self.textInputView.isRTL {
|
||||||
textXOffset = initialWidth - self.messageClipNode.bounds.width
|
textXOffset = initialWidth - self.messageClipNode.bounds.width
|
||||||
}
|
}
|
||||||
self.fromMessageTextNode.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
self.fromMessageTextScrollView.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
self.toMessageTextNode.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
self.toMessageTextScrollView.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
|
|
||||||
let contentOffset = CGPoint(x: self.sendButtonFrame.midX - self.contentContainerNode.frame.midX, y: self.sendButtonFrame.midY - self.contentContainerNode.frame.midY)
|
let contentOffset = CGPoint(x: self.sendButtonFrame.midX - self.contentContainerNode.frame.midX, y: self.sendButtonFrame.midY - self.contentContainerNode.frame.midY)
|
||||||
|
|
||||||
@ -574,8 +584,8 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
|||||||
|
|
||||||
if self.animateInputField {
|
if self.animateInputField {
|
||||||
if cancel {
|
if cancel {
|
||||||
self.fromMessageTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, delay: 0.15, removeOnCompletion: false)
|
self.fromMessageTextScrollView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, delay: 0.15, removeOnCompletion: false)
|
||||||
self.toMessageTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.15, removeOnCompletion: false)
|
self.toMessageTextScrollView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.15, removeOnCompletion: false)
|
||||||
self.messageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.15, removeOnCompletion: false, completion: { _ in
|
self.messageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.15, removeOnCompletion: false, completion: { _ in
|
||||||
completedAlpha = true
|
completedAlpha = true
|
||||||
intermediateCompletion()
|
intermediateCompletion()
|
||||||
@ -636,8 +646,8 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
|||||||
if self.textInputView.numberOfLines == 1 && self.textInputView.isRTL {
|
if self.textInputView.numberOfLines == 1 && self.textInputView.isRTL {
|
||||||
textXOffset = initialWidth - self.messageClipNode.bounds.width
|
textXOffset = initialWidth - self.messageClipNode.bounds.width
|
||||||
}
|
}
|
||||||
self.fromMessageTextNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
self.fromMessageTextScrollView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||||
self.toMessageTextNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
self.toMessageTextScrollView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||||
} else {
|
} else {
|
||||||
completedBubble = true
|
completedBubble = true
|
||||||
}
|
}
|
||||||
@ -772,10 +782,12 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
|||||||
textFrame.origin.x -= messageOriginDelta
|
textFrame.origin.x -= messageOriginDelta
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fromMessageTextNode.frame = textFrame
|
self.fromMessageTextScrollView.frame = textFrame
|
||||||
|
self.fromMessageTextNode.frame = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||||
self.fromMessageTextNode.updateLayout(size: textFrame.size)
|
self.fromMessageTextNode.updateLayout(size: textFrame.size)
|
||||||
|
|
||||||
self.toMessageTextNode.frame = textFrame
|
self.toMessageTextScrollView.frame = textFrame
|
||||||
|
self.toMessageTextNode.frame = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||||
self.toMessageTextNode.updateLayout(size: textFrame.size)
|
self.toMessageTextNode.updateLayout(size: textFrame.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2070,7 +2070,21 @@ public final class ControlledTransition {
|
|||||||
if layer.bounds == bounds {
|
if layer.bounds == bounds {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let fromValue = layer.presentation()?.bounds ?? layer.bounds
|
let fromValue: CGRect
|
||||||
|
if let animationKeys = layer.animationKeys(), animationKeys.contains(where: { key in
|
||||||
|
guard let animation = layer.animation(forKey: key) as? CAPropertyAnimation else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if animation.keyPath == "bounds" {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
fromValue = layer.presentation()?.bounds ?? layer.bounds
|
||||||
|
} else {
|
||||||
|
fromValue = layer.bounds
|
||||||
|
}
|
||||||
layer.bounds = bounds
|
layer.bounds = bounds
|
||||||
self.add(animation: ControlledTransitionProperty(
|
self.add(animation: ControlledTransitionProperty(
|
||||||
layer: layer,
|
layer: layer,
|
||||||
|
@ -558,6 +558,10 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func setNeedsLayout() {
|
||||||
|
super.setNeedsLayout()
|
||||||
|
}
|
||||||
|
|
||||||
override public func layoutSubviews() {
|
override public func layoutSubviews() {
|
||||||
let isLayoutUpdated = self.validLayoutSize != self.bounds.size
|
let isLayoutUpdated = self.validLayoutSize != self.bounds.size
|
||||||
self.validLayoutSize = self.bounds.size
|
self.validLayoutSize = self.bounds.size
|
||||||
|
@ -65,7 +65,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
private var contentFile: ChatMessageInteractiveFileNode?
|
private var contentFile: ChatMessageInteractiveFileNode?
|
||||||
private var actionButton: ChatMessageAttachedContentButtonNode?
|
private var actionButton: ChatMessageAttachedContentButtonNode?
|
||||||
private var actionButtonSeparator: SimpleLayer?
|
private var actionButtonSeparator: SimpleLayer?
|
||||||
public let statusNode: ChatMessageDateAndStatusNode
|
public var statusNode: ChatMessageDateAndStatusNode?
|
||||||
|
|
||||||
private var inlineMediaValue: Media?
|
private var inlineMediaValue: Media?
|
||||||
|
|
||||||
@ -109,12 +109,10 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
|
|
||||||
override public init() {
|
override public init() {
|
||||||
self.transformContainer = ASDisplayNode()
|
self.transformContainer = ASDisplayNode()
|
||||||
self.statusNode = ChatMessageDateAndStatusNode()
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.transformContainer)
|
self.addSubnode(self.transformContainer)
|
||||||
self.addSubnode(self.statusNode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -148,7 +146,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
let makeContentMedia = ChatMessageInteractiveMediaNode.asyncLayout(self.contentMedia)
|
let makeContentMedia = ChatMessageInteractiveMediaNode.asyncLayout(self.contentMedia)
|
||||||
let makeContentFile = ChatMessageInteractiveFileNode.asyncLayout(self.contentFile)
|
let makeContentFile = ChatMessageInteractiveFileNode.asyncLayout(self.contentFile)
|
||||||
let makeActionButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.actionButton)
|
let makeActionButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.actionButton)
|
||||||
let makeStatusLayout = self.statusNode.asyncLayout()
|
let makeStatusLayout = ChatMessageDateAndStatusNode.asyncLayout(self.statusNode)
|
||||||
|
|
||||||
return { [weak self] presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, chatLocation, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize, animationCache, animationRenderer in
|
return { [weak self] presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, chatLocation, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize, animationCache, animationRenderer in
|
||||||
let isPreview = presentationData.isPreview
|
let isPreview = presentationData.isPreview
|
||||||
@ -604,35 +602,44 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusLayoutAndContinue = makeStatusLayout(ChatMessageDateAndStatusNode.Arguments(
|
var statusLayoutAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode))?
|
||||||
context: context,
|
if case let .linear(_, bottom) = position {
|
||||||
presentationData: presentationData,
|
switch bottom {
|
||||||
edited: edited,
|
case .None, .Neighbour(_, .footer, _):
|
||||||
impressionCount: viewCount,
|
let statusLayoutAndContinueValue = makeStatusLayout(ChatMessageDateAndStatusNode.Arguments(
|
||||||
dateText: dateText,
|
context: context,
|
||||||
type: statusType,
|
presentationData: presentationData,
|
||||||
layoutInput: .trailingContent(
|
edited: edited,
|
||||||
contentWidth: trailingContentWidth,
|
impressionCount: viewCount,
|
||||||
reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions), preferAdditionalInset: false)
|
dateText: dateText,
|
||||||
),
|
type: statusType,
|
||||||
constrainedSize: CGSize(width: maxStatusContentWidth, height: CGFloat.greatestFiniteMagnitude),
|
layoutInput: .trailingContent(
|
||||||
availableReactions: associatedData.availableReactions,
|
contentWidth: trailingContentWidth,
|
||||||
reactions: dateReactionsAndPeers.reactions,
|
reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions), preferAdditionalInset: false)
|
||||||
reactionPeers: dateReactionsAndPeers.peers,
|
),
|
||||||
displayAllReactionPeers: message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
constrainedSize: CGSize(width: maxStatusContentWidth, height: CGFloat.greatestFiniteMagnitude),
|
||||||
replyCount: dateReplies,
|
availableReactions: associatedData.availableReactions,
|
||||||
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
|
reactions: dateReactionsAndPeers.reactions,
|
||||||
hasAutoremove: message.isSelfExpiring,
|
reactionPeers: dateReactionsAndPeers.peers,
|
||||||
canViewReactionList: canViewMessageReactionList(message: message),
|
displayAllReactionPeers: message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||||
animationCache: controllerInteraction.presentationContext.animationCache,
|
replyCount: dateReplies,
|
||||||
animationRenderer: controllerInteraction.presentationContext.animationRenderer
|
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
))
|
hasAutoremove: message.isSelfExpiring,
|
||||||
actualWidth = max(actualWidth, statusLayoutAndContinue.0)
|
canViewReactionList: canViewMessageReactionList(message: message),
|
||||||
|
animationCache: controllerInteraction.presentationContext.animationCache,
|
||||||
|
animationRenderer: controllerInteraction.presentationContext.animationRenderer
|
||||||
|
))
|
||||||
|
statusLayoutAndContinue = statusLayoutAndContinueValue
|
||||||
|
actualWidth = max(actualWidth, statusLayoutAndContinueValue.0)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
actualWidth += insets.left + insets.right
|
actualWidth += insets.left + insets.right
|
||||||
|
|
||||||
return (actualWidth, { resultingWidth in
|
return (actualWidth, { resultingWidth in
|
||||||
let statusSizeAndApply = statusLayoutAndContinue.1(resultingWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right - 6.0)
|
let statusSizeAndApply = statusLayoutAndContinue?.1(resultingWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right - 6.0)
|
||||||
|
|
||||||
let contentMediaSizeAndApply: (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode)?
|
let contentMediaSizeAndApply: (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode)?
|
||||||
if let contentMediaFinalizeLayout {
|
if let contentMediaFinalizeLayout {
|
||||||
@ -816,7 +823,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .linear(_, bottom) = position {
|
if case let .linear(_, bottom) = position, let statusSizeAndApply {
|
||||||
switch bottom {
|
switch bottom {
|
||||||
case .None, .Neighbour(_, .footer, _):
|
case .None, .Neighbour(_, .footer, _):
|
||||||
let bottomStatusContentHeight = statusBackgroundSpacing + statusSizeAndApply.0.height
|
let bottomStatusContentHeight = statusBackgroundSpacing + statusSizeAndApply.0.height
|
||||||
@ -1099,7 +1106,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
|
|
||||||
if let item = contentDisplayOrder.first(where: { $0.item == .actionButton }), let (actionButtonSize, actionButtonApply) = actionButtonSizeAndApply {
|
if let item = contentDisplayOrder.first(where: { $0.item == .actionButton }), let (actionButtonSize, actionButtonApply) = actionButtonSizeAndApply {
|
||||||
var actionButtonFrame = CGRect(origin: CGPoint(x: insets.left, y: item.offsetY), size: actionButtonSize)
|
var actionButtonFrame = CGRect(origin: CGPoint(x: insets.left, y: item.offsetY), size: actionButtonSize)
|
||||||
if let _ = message.adAttribute {
|
if let _ = message.adAttribute, let statusSizeAndApply {
|
||||||
actionButtonFrame.origin.y += statusSizeAndApply.0.height
|
actionButtonFrame.origin.y += statusSizeAndApply.0.height
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1151,38 +1158,40 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
actionButtonSeparator.removeFromSuperlayer()
|
actionButtonSeparator.removeFromSuperlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
if let statusSizeAndApply {
|
||||||
statusSizeAndApply.1(animation)
|
var statusFrame = CGRect(origin: CGPoint(x: actualSize.width - backgroundInsets.right - statusSizeAndApply.0.width, y: actualSize.height - layoutConstants.text.bubbleInsets.bottom - statusSizeAndApply.0.height), size: statusSizeAndApply.0)
|
||||||
|
|
||||||
var statusFrame = CGRect(origin: CGPoint(x: actualSize.width - insets.right - statusSizeAndApply.0.width, y: actualSize.height - layoutConstants.text.bubbleInsets.bottom - statusSizeAndApply.0.height), size: statusSizeAndApply.0)
|
|
||||||
if let _ = message.adAttribute, let (actionButtonSize, _) = actionButtonSizeAndApply {
|
if let _ = message.adAttribute, let (actionButtonSize, _) = actionButtonSizeAndApply {
|
||||||
statusFrame.origin.y -= actionButtonSize.height + statusBackgroundSpacing
|
statusFrame.origin.y -= actionButtonSize.height + statusBackgroundSpacing
|
||||||
}
|
}
|
||||||
animation.animator.updateFrame(layer: self.statusNode.layer, frame: statusFrame, completion: nil)
|
|
||||||
|
|
||||||
self.statusNode.reactionSelected = { [weak self] value in
|
let statusNode = statusSizeAndApply.1(self.statusNode == nil ? .None : animation)
|
||||||
guard let self, let message = self.message else {
|
if self.statusNode !== statusNode {
|
||||||
return
|
self.statusNode?.removeFromSupernode()
|
||||||
|
self.statusNode = statusNode
|
||||||
|
self.addSubnode(statusNode)
|
||||||
|
|
||||||
|
statusNode.reactionSelected = { [weak self] value in
|
||||||
|
guard let self, let message = self.message else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controllerInteraction.updateMessageReaction(message, .reaction(value))
|
||||||
}
|
}
|
||||||
controllerInteraction.updateMessageReaction(message, .reaction(value))
|
|
||||||
}
|
statusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in
|
||||||
|
guard let self, let message = self.message else {
|
||||||
self.statusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in
|
gesture?.cancel()
|
||||||
guard let self, let message = self.message else {
|
return
|
||||||
gesture?.cancel()
|
}
|
||||||
return
|
controllerInteraction.openMessageReactionContextMenu(message, sourceNode, gesture, value)
|
||||||
}
|
|
||||||
controllerInteraction.openMessageReactionContextMenu(message, sourceNode, gesture, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if case let .linear(_, bottom) = position {
|
|
||||||
switch bottom {
|
|
||||||
case .None, .Neighbour(_, .footer, _):
|
|
||||||
animation.animator.updateAlpha(layer: self.statusNode.layer, alpha: 1.0, completion: nil)
|
|
||||||
default:
|
|
||||||
animation.animator.updateAlpha(layer: self.statusNode.layer, alpha: 0.0, completion: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statusNode.frame = statusFrame
|
||||||
|
} else {
|
||||||
|
animation.animator.updateFrame(layer: statusNode.layer, frame: statusFrame, completion: nil)
|
||||||
}
|
}
|
||||||
|
} else if let statusNode = self.statusNode {
|
||||||
|
self.statusNode = nil
|
||||||
|
statusNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -2239,8 +2248,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
|
public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
|
||||||
if !self.statusNode.isHidden {
|
if let statusNode = self.statusNode, !statusNode.isHidden {
|
||||||
if let result = self.statusNode.reactionView(value: value) {
|
if let result = statusNode.reactionView(value: value) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2832,7 +2832,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
if case let .System(duration, _) = animation {
|
if case let .System(duration, _) = animation {
|
||||||
legacyTransition = .animated(duration: duration, curve: .spring)
|
legacyTransition = .animated(duration: duration, curve: .spring)
|
||||||
|
|
||||||
if let subject = item.associatedData.subject, case .messageOptions = subject {
|
if let subject = item.associatedData.subject, case .messageOptions = subject, !"".isEmpty {
|
||||||
useDisplayLinkAnimations = true
|
useDisplayLinkAnimations = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3343,6 +3343,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
strongSelf.contentContainersWrapperNode.view.mask = nil
|
strongSelf.contentContainersWrapperNode.view.mask = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var animateTextAndWebpagePositionSwap: Bool?
|
||||||
|
var bottomStatusNodeAnimationSourcePosition: CGPoint?
|
||||||
|
|
||||||
if removedContentNodeIndices?.count ?? 0 != 0 || addedContentNodes?.count ?? 0 != 0 || updatedContentNodeOrder {
|
if removedContentNodeIndices?.count ?? 0 != 0 || addedContentNodes?.count ?? 0 != 0 || updatedContentNodeOrder {
|
||||||
var updatedContentNodes = strongSelf.contentNodes
|
var updatedContentNodes = strongSelf.contentNodes
|
||||||
|
|
||||||
@ -3415,6 +3418,26 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
|
|
||||||
assert(sortedContentNodes.count == updatedContentNodes.count)
|
assert(sortedContentNodes.count == updatedContentNodes.count)
|
||||||
|
|
||||||
|
if animation.isAnimated, let fromTextIndex = strongSelf.contentNodes.firstIndex(where: { $0 is ChatMessageTextBubbleContentNode }), let fromWebpageIndex = strongSelf.contentNodes.firstIndex(where: { $0 is ChatMessageWebpageBubbleContentNode }) {
|
||||||
|
if let toTextIndex = sortedContentNodes.firstIndex(where: { $0 is ChatMessageTextBubbleContentNode }), let toWebpageIndex = sortedContentNodes.firstIndex(where: { $0 is ChatMessageWebpageBubbleContentNode }) {
|
||||||
|
if fromTextIndex == toWebpageIndex && fromWebpageIndex == toTextIndex {
|
||||||
|
animateTextAndWebpagePositionSwap = fromTextIndex < toTextIndex
|
||||||
|
|
||||||
|
if let textNode = strongSelf.contentNodes[fromTextIndex] as? ChatMessageTextBubbleContentNode, let webpageNode = strongSelf.contentNodes[fromWebpageIndex] as? ChatMessageWebpageBubbleContentNode {
|
||||||
|
if fromTextIndex > toTextIndex {
|
||||||
|
if let statusNode = textNode.statusNode, let contentSuperview = textNode.view.superview, statusNode.view.isDescendant(of: contentSuperview) {
|
||||||
|
bottomStatusNodeAnimationSourcePosition = statusNode.view.convert(CGPoint(x: statusNode.bounds.width, y: statusNode.bounds.height), to: contentSuperview)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let statusNode = webpageNode.contentNode.statusNode, let contentSuperview = webpageNode.view.superview, statusNode.view.isDescendant(of: contentSuperview) {
|
||||||
|
bottomStatusNodeAnimationSourcePosition = statusNode.view.convert(CGPoint(x: statusNode.bounds.width, y: statusNode.bounds.height), to: contentSuperview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.contentNodes = sortedContentNodes
|
strongSelf.contentNodes = sortedContentNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3463,7 +3486,47 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
})
|
})
|
||||||
strongSelf.setAnimationForKey("contentNode\(contentNodeIndex)Frame", animation: animation)
|
strongSelf.setAnimationForKey("contentNode\(contentNodeIndex)Frame", animation: animation)
|
||||||
} else {
|
} else {
|
||||||
animation.animator.updateFrame(layer: contentNode.layer, frame: contentNodeFrame, completion: nil)
|
if let animateTextAndWebpagePositionSwap, let contentNode = contentNode as? ChatMessageTextBubbleContentNode, let snapshotLayer = contentNode.layer.snapshotContentTree() {
|
||||||
|
let clippingLayer = SimpleLayer()
|
||||||
|
clippingLayer.masksToBounds = true
|
||||||
|
clippingLayer.frame = contentNode.frame
|
||||||
|
|
||||||
|
clippingLayer.addSublayer(snapshotLayer)
|
||||||
|
snapshotLayer.frame = CGRect(origin: CGPoint(), size: contentNode.bounds.size)
|
||||||
|
|
||||||
|
contentNode.layer.superlayer?.insertSublayer(clippingLayer, below: contentNode.layer)
|
||||||
|
|
||||||
|
animation.animator.updateAlpha(layer: clippingLayer, alpha: 0.0, completion: { [weak clippingLayer] _ in
|
||||||
|
clippingLayer?.removeFromSuperlayer()
|
||||||
|
})
|
||||||
|
|
||||||
|
let positionOffset: CGFloat = animateTextAndWebpagePositionSwap ? -1.0 : 1.0
|
||||||
|
|
||||||
|
animation.animator.updatePosition(layer: snapshotLayer, position: CGPoint(x: snapshotLayer.position.x, y: snapshotLayer.position.y + positionOffset * contentNode.frame.height), completion: nil)
|
||||||
|
|
||||||
|
contentNode.frame = contentNodeFrame
|
||||||
|
|
||||||
|
if let statusNode = contentNode.statusNode, let contentSuperview = contentNode.view.superview, statusNode.view.isDescendant(of: contentSuperview), let bottomStatusNodeAnimationSourcePosition {
|
||||||
|
let localSourcePosition = statusNode.view.convert(bottomStatusNodeAnimationSourcePosition, from: contentSuperview)
|
||||||
|
let offset = CGPoint(x: statusNode.bounds.width - localSourcePosition.x, y: statusNode.bounds.height - localSourcePosition.y)
|
||||||
|
animation.animator.animatePosition(layer: statusNode.layer, from: statusNode.layer.position.offsetBy(dx: -offset.x, dy: -offset.y), to: statusNode.layer.position, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentNode.animateClippingTransition(offset: positionOffset * contentNodeFrame.height, animation: animation)
|
||||||
|
|
||||||
|
contentNode.alpha = 0.0
|
||||||
|
animation.animator.updateAlpha(layer: contentNode.layer, alpha: 1.0, completion: nil)
|
||||||
|
} else if animateTextAndWebpagePositionSwap != nil, let contentNode = contentNode as? ChatMessageWebpageBubbleContentNode {
|
||||||
|
if let statusNode = contentNode.contentNode.statusNode, let contentSuperview = contentNode.view.superview, statusNode.view.isDescendant(of: contentSuperview), let bottomStatusNodeAnimationSourcePosition {
|
||||||
|
let localSourcePosition = statusNode.view.convert(bottomStatusNodeAnimationSourcePosition, from: contentSuperview)
|
||||||
|
let offset = CGPoint(x: statusNode.bounds.width - localSourcePosition.x, y: statusNode.bounds.height - localSourcePosition.y)
|
||||||
|
animation.animator.animatePosition(layer: statusNode.layer, from: statusNode.layer.position.offsetBy(dx: -offset.x, dy: -offset.y), to: statusNode.layer.position, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.animator.updateFrame(layer: contentNode.layer, frame: contentNodeFrame, completion: nil)
|
||||||
|
} else {
|
||||||
|
animation.animator.updateFrame(layer: contentNode.layer, frame: contentNodeFrame, completion: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if animateAlpha {
|
} else if animateAlpha {
|
||||||
contentNode.frame = contentNodeFrame
|
contentNode.frame = contentNodeFrame
|
||||||
|
@ -144,8 +144,8 @@ public final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNod
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
|
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
|
||||||
if !self.contentNode.statusNode.isHidden {
|
if let statusNode = self.contentNode.statusNode, !statusNode.isHidden {
|
||||||
return self.contentNode.statusNode.reactionView(value: value)
|
return statusNode.reactionView(value: value)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -140,8 +140,8 @@ public final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContent
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
|
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
|
||||||
if !self.contentNode.statusNode.isHidden {
|
if let statusNode = self.contentNode.statusNode, !statusNode.isHidden {
|
||||||
return self.contentNode.statusNode.reactionView(value: value)
|
return statusNode.reactionView(value: value)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -54,12 +54,13 @@ private final class CachedChatMessageText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
|
private let containerNode: ASDisplayNode
|
||||||
private let textNode: TextNodeWithEntities
|
private let textNode: TextNodeWithEntities
|
||||||
private var spoilerTextNode: TextNodeWithEntities?
|
private var spoilerTextNode: TextNodeWithEntities?
|
||||||
private var dustNode: InvisibleInkDustNode?
|
private var dustNode: InvisibleInkDustNode?
|
||||||
|
|
||||||
private let textAccessibilityOverlayNode: TextAccessibilityOverlayNode
|
private let textAccessibilityOverlayNode: TextAccessibilityOverlayNode
|
||||||
private let statusNode: ChatMessageDateAndStatusNode
|
public var statusNode: ChatMessageDateAndStatusNode?
|
||||||
private var linkHighlightingNode: LinkHighlightingNode?
|
private var linkHighlightingNode: LinkHighlightingNode?
|
||||||
private var shimmeringNode: ShimmeringLinkNode?
|
private var shimmeringNode: ShimmeringLinkNode?
|
||||||
private var textSelectionNode: TextSelectionNode?
|
private var textSelectionNode: TextSelectionNode?
|
||||||
@ -99,40 +100,26 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
required public init() {
|
required public init() {
|
||||||
self.textNode = TextNodeWithEntities()
|
self.containerNode = ASDisplayNode()
|
||||||
|
|
||||||
self.statusNode = ChatMessageDateAndStatusNode()
|
self.textNode = TextNodeWithEntities()
|
||||||
|
|
||||||
self.textAccessibilityOverlayNode = TextAccessibilityOverlayNode()
|
self.textAccessibilityOverlayNode = TextAccessibilityOverlayNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.containerNode)
|
||||||
|
|
||||||
self.textNode.textNode.isUserInteractionEnabled = false
|
self.textNode.textNode.isUserInteractionEnabled = false
|
||||||
self.textNode.textNode.contentMode = .topLeft
|
self.textNode.textNode.contentMode = .topLeft
|
||||||
self.textNode.textNode.contentsScale = UIScreenScale
|
self.textNode.textNode.contentsScale = UIScreenScale
|
||||||
self.textNode.textNode.displaysAsynchronously = true
|
self.textNode.textNode.displaysAsynchronously = true
|
||||||
self.addSubnode(self.textNode.textNode)
|
self.containerNode.addSubnode(self.textNode.textNode)
|
||||||
self.addSubnode(self.textAccessibilityOverlayNode)
|
self.containerNode.addSubnode(self.textAccessibilityOverlayNode)
|
||||||
|
|
||||||
self.textAccessibilityOverlayNode.openUrl = { [weak self] url in
|
self.textAccessibilityOverlayNode.openUrl = { [weak self] url in
|
||||||
self?.item?.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: false, external: false))
|
self?.item?.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: false, external: false))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.statusNode.reactionSelected = { [weak self] value in
|
|
||||||
guard let strongSelf = self, let item = strongSelf.item else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
self.statusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in
|
|
||||||
guard let strongSelf = self, let item = strongSelf.item else {
|
|
||||||
gesture?.cancel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init?(coder aDecoder: NSCoder) {
|
required public init?(coder aDecoder: NSCoder) {
|
||||||
@ -147,7 +134,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
|
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
|
||||||
let textLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
let textLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||||
let spoilerTextLayout = TextNodeWithEntities.asyncLayout(self.spoilerTextNode)
|
let spoilerTextLayout = TextNodeWithEntities.asyncLayout(self.spoilerTextNode)
|
||||||
let statusLayout = self.statusNode.asyncLayout()
|
let statusLayout = ChatMessageDateAndStatusNode.asyncLayout(self.statusNode)
|
||||||
|
|
||||||
let currentCachedChatMessageText = self.cachedChatMessageText
|
let currentCachedChatMessageText = self.cachedChatMessageText
|
||||||
|
|
||||||
@ -454,7 +441,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
spoilerTextLayoutAndApply = nil
|
spoilerTextLayoutAndApply = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
|
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode))?
|
||||||
if let statusType = statusType {
|
if let statusType = statusType {
|
||||||
var isReplyThread = false
|
var isReplyThread = false
|
||||||
if case .replyThread = item.chatLocation {
|
if case .replyThread = item.chatLocation {
|
||||||
@ -527,6 +514,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.cachedChatMessageText = updatedCachedChatMessageText
|
strongSelf.cachedChatMessageText = updatedCachedChatMessageText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: boundingSize)
|
||||||
|
|
||||||
let cachedLayout = strongSelf.textNode.textNode.cachedLayout
|
let cachedLayout = strongSelf.textNode.textNode.cachedLayout
|
||||||
|
|
||||||
if case .System = animation {
|
if case .System = animation {
|
||||||
@ -538,7 +527,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
fadeNode.contents = textContents
|
fadeNode.contents = textContents
|
||||||
fadeNode.frame = strongSelf.textNode.textNode.frame
|
fadeNode.frame = strongSelf.textNode.textNode.frame
|
||||||
fadeNode.isLayerBacked = true
|
fadeNode.isLayerBacked = true
|
||||||
strongSelf.addSubnode(fadeNode)
|
strongSelf.containerNode.addSubnode(fadeNode)
|
||||||
fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in
|
fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in
|
||||||
fadeNode?.removeFromSupernode()
|
fadeNode?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
@ -559,7 +548,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
spoilerTextNode.textNode.contentMode = .topLeft
|
spoilerTextNode.textNode.contentMode = .topLeft
|
||||||
spoilerTextNode.textNode.contentsScale = UIScreenScale
|
spoilerTextNode.textNode.contentsScale = UIScreenScale
|
||||||
spoilerTextNode.textNode.displaysAsynchronously = false
|
spoilerTextNode.textNode.displaysAsynchronously = false
|
||||||
strongSelf.insertSubnode(spoilerTextNode.textNode, aboveSubnode: strongSelf.textAccessibilityOverlayNode)
|
strongSelf.containerNode.insertSubnode(spoilerTextNode.textNode, aboveSubnode: strongSelf.textAccessibilityOverlayNode)
|
||||||
|
|
||||||
strongSelf.spoilerTextNode = spoilerTextNode
|
strongSelf.spoilerTextNode = spoilerTextNode
|
||||||
}
|
}
|
||||||
@ -572,7 +561,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
} else {
|
} else {
|
||||||
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: item.context.sharedContext.energyUsageSettings.fullTranslucency)
|
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: item.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||||
strongSelf.dustNode = dustNode
|
strongSelf.dustNode = dustNode
|
||||||
strongSelf.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode)
|
strongSelf.containerNode.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode)
|
||||||
}
|
}
|
||||||
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
||||||
dustNode.update(size: dustNode.frame.size, color: messageTheme.secondaryTextColor, textColor: messageTheme.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
dustNode.update(size: dustNode.frame.size, color: messageTheme.secondaryTextColor, textColor: messageTheme.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
||||||
@ -611,27 +600,48 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
strongSelf.updateIsTranslating(isTranslating)
|
strongSelf.updateIsTranslating(isTranslating)
|
||||||
|
|
||||||
if let statusSizeAndApply = statusSizeAndApply {
|
if let statusSizeAndApply {
|
||||||
animation.animator.updateFrame(layer: strongSelf.statusNode.layer, frame: CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0), completion: nil)
|
let statusNode = statusSizeAndApply.1(strongSelf.statusNode == nil ? .None : animation)
|
||||||
if strongSelf.statusNode.supernode == nil {
|
let statusFrame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
|
||||||
strongSelf.addSubnode(strongSelf.statusNode)
|
|
||||||
statusSizeAndApply.1(.None)
|
if strongSelf.statusNode !== statusNode {
|
||||||
|
strongSelf.statusNode?.removeFromSupernode()
|
||||||
|
strongSelf.statusNode = statusNode
|
||||||
|
|
||||||
|
strongSelf.addSubnode(statusNode)
|
||||||
|
|
||||||
|
statusNode.reactionSelected = { [weak strongSelf] value in
|
||||||
|
guard let strongSelf, let item = strongSelf.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value))
|
||||||
|
}
|
||||||
|
statusNode.openReactionPreview = { [weak strongSelf] gesture, sourceNode, value in
|
||||||
|
guard let strongSelf, let item = strongSelf.item else {
|
||||||
|
gesture?.cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value)
|
||||||
|
}
|
||||||
|
statusNode.frame = statusFrame
|
||||||
} else {
|
} else {
|
||||||
statusSizeAndApply.1(animation)
|
animation.animator.updateFrame(layer: statusNode.layer, frame: statusFrame, completion: nil)
|
||||||
}
|
}
|
||||||
} else if strongSelf.statusNode.supernode != nil {
|
} else if let statusNode = strongSelf.statusNode {
|
||||||
strongSelf.statusNode.removeFromSupernode()
|
strongSelf.statusNode = nil
|
||||||
|
statusNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported), let statusNode = strongSelf.statusNode {
|
||||||
strongSelf.statusNode.pressed = {
|
statusNode.pressed = {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self, let statusNode = strongSelf.statusNode else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.statusNode)
|
item.controllerInteraction.displayImportedMessageTooltip(statusNode)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
strongSelf.statusNode.pressed = nil
|
strongSelf.statusNode?.pressed = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject {
|
if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject {
|
||||||
@ -688,17 +698,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
override public func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
override public func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
||||||
self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.statusNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||||
self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.statusNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
self.textNode.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
self.textNode.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
self.statusNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||||
@ -785,7 +795,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let _ = self.statusNode.hitTest(self.view.convert(point, to: self.statusNode.view), with: nil) {
|
if let statusNode = self.statusNode, let _ = statusNode.hitTest(self.view.convert(point, to: statusNode.view), with: nil) {
|
||||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||||
}
|
}
|
||||||
return ChatMessageBubbleContentTapAction(content: .none)
|
return ChatMessageBubbleContentTapAction(content: .none)
|
||||||
@ -793,7 +803,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
if self.statusNode.supernode != nil, let result = self.statusNode.hitTest(self.view.convert(point, to: self.statusNode.view), with: event) {
|
if let statusNode = self.statusNode, statusNode.supernode != nil, let result = statusNode.hitTest(self.view.convert(point, to: statusNode.view), with: event) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
return super.hitTest(point, with: event)
|
return super.hitTest(point, with: event)
|
||||||
@ -815,7 +825,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
shimmeringNode.updateLayout(self.textNode.textNode.frame.size)
|
shimmeringNode.updateLayout(self.textNode.textNode.frame.size)
|
||||||
shimmeringNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
shimmeringNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
self.shimmeringNode = shimmeringNode
|
self.shimmeringNode = shimmeringNode
|
||||||
self.insertSubnode(shimmeringNode, belowSubnode: self.textNode.textNode)
|
self.containerNode.insertSubnode(shimmeringNode, belowSubnode: self.textNode.textNode)
|
||||||
}
|
}
|
||||||
} else if let shimmeringNode = self.shimmeringNode {
|
} else if let shimmeringNode = self.shimmeringNode {
|
||||||
self.shimmeringNode = nil
|
self.shimmeringNode = nil
|
||||||
@ -862,7 +872,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
} else {
|
} else {
|
||||||
linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor)
|
linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor)
|
||||||
self.linkHighlightingNode = linkHighlightingNode
|
self.linkHighlightingNode = linkHighlightingNode
|
||||||
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode.textNode)
|
self.containerNode.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode.textNode)
|
||||||
}
|
}
|
||||||
linkHighlightingNode.frame = self.textNode.textNode.frame
|
linkHighlightingNode.frame = self.textNode.textNode.frame
|
||||||
linkHighlightingNode.updateRects(rects)
|
linkHighlightingNode.updateRects(rects)
|
||||||
@ -893,7 +903,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
} else {
|
} else {
|
||||||
textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.textHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.textHighlightColor)
|
textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.textHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.textHighlightColor)
|
||||||
self.textHighlightingNodes.append(textHighlightNode)
|
self.textHighlightingNodes.append(textHighlightNode)
|
||||||
self.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
|
self.containerNode.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
|
||||||
}
|
}
|
||||||
textHighlightNode.frame = self.textNode.textNode.frame
|
textHighlightNode.frame = self.textNode.textNode.frame
|
||||||
textHighlightNode.updateRects(rects)
|
textHighlightNode.updateRects(rects)
|
||||||
@ -927,7 +937,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
} else {
|
} else {
|
||||||
textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor.withMultipliedAlpha(0.5) : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor.withMultipliedAlpha(0.5))
|
textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor.withMultipliedAlpha(0.5) : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor.withMultipliedAlpha(0.5))
|
||||||
self.linkPreviewHighlightingNodes.append(textHighlightNode)
|
self.linkPreviewHighlightingNodes.append(textHighlightNode)
|
||||||
self.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
|
self.containerNode.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
|
||||||
}
|
}
|
||||||
textHighlightNode.frame = self.textNode.textNode.frame
|
textHighlightNode.frame = self.textNode.textNode.frame
|
||||||
textHighlightNode.updateRects(rects)
|
textHighlightNode.updateRects(rects)
|
||||||
@ -951,7 +961,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
} else {
|
} else {
|
||||||
linkProgressView = TextLoadingEffectView(frame: CGRect())
|
linkProgressView = TextLoadingEffectView(frame: CGRect())
|
||||||
self.linkProgressView = linkProgressView
|
self.linkProgressView = linkProgressView
|
||||||
self.view.addSubview(linkProgressView)
|
self.containerNode.view.addSubview(linkProgressView)
|
||||||
}
|
}
|
||||||
linkProgressView.frame = self.textNode.textNode.frame
|
linkProgressView.frame = self.textNode.textNode.frame
|
||||||
|
|
||||||
@ -1050,7 +1060,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
} else {
|
} else {
|
||||||
textHighlightNode = LinkHighlightingNode(color: color)
|
textHighlightNode = LinkHighlightingNode(color: color)
|
||||||
self.quoteHighlightingNode = textHighlightNode
|
self.quoteHighlightingNode = textHighlightNode
|
||||||
self.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
|
self.containerNode.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
|
||||||
}
|
}
|
||||||
textHighlightNode.frame = self.textNode.textNode.frame
|
textHighlightNode.frame = self.textNode.textNode.frame
|
||||||
textHighlightNode.updateRects(rects)
|
textHighlightNode.updateRects(rects)
|
||||||
@ -1157,8 +1167,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
textSelectionNode.enableShare = enableOtherActions
|
textSelectionNode.enableShare = enableOtherActions
|
||||||
textSelectionNode.menuSkipCoordnateConversion = !enableOtherActions
|
textSelectionNode.menuSkipCoordnateConversion = !enableOtherActions
|
||||||
self.textSelectionNode = textSelectionNode
|
self.textSelectionNode = textSelectionNode
|
||||||
self.addSubnode(textSelectionNode)
|
self.containerNode.addSubnode(textSelectionNode)
|
||||||
self.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode.textNode)
|
self.containerNode.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode.textNode)
|
||||||
textSelectionNode.frame = self.textNode.textNode.frame
|
textSelectionNode.frame = self.textNode.textNode.frame
|
||||||
textSelectionNode.highlightAreaNode.frame = self.textNode.textNode.frame
|
textSelectionNode.highlightAreaNode.frame = self.textNode.textNode.frame
|
||||||
}
|
}
|
||||||
@ -1180,8 +1190,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
|
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
|
||||||
if !self.statusNode.isHidden {
|
if let statusNode = self.statusNode, !statusNode.isHidden {
|
||||||
return self.statusNode.reactionView(value: value)
|
return statusNode.reactionView(value: value)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1191,7 +1201,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func animateFrom(sourceView: UIView, scrollOffset: CGFloat, widthDifference: CGFloat, transition: CombinedTransition) {
|
public func animateFrom(sourceView: UIView, scrollOffset: CGFloat, widthDifference: CGFloat, transition: CombinedTransition) {
|
||||||
self.view.addSubview(sourceView)
|
self.containerNode.view.addSubview(sourceView)
|
||||||
|
|
||||||
sourceView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak sourceView] _ in
|
sourceView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak sourceView] _ in
|
||||||
sourceView?.removeFromSuperview()
|
sourceView?.removeFromSuperview()
|
||||||
@ -1206,8 +1216,10 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
transition.vertical.animatePositionAdditive(node: self.textNode.textNode, offset: offset)
|
transition.vertical.animatePositionAdditive(node: self.textNode.textNode, offset: offset)
|
||||||
transition.updatePosition(layer: sourceView.layer, position: CGPoint(x: sourceView.layer.position.x - offset.x, y: sourceView.layer.position.y - offset.y))
|
transition.updatePosition(layer: sourceView.layer, position: CGPoint(x: sourceView.layer.position.x - offset.x, y: sourceView.layer.position.y - offset.y))
|
||||||
|
|
||||||
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
if let statusNode = self.statusNode {
|
||||||
transition.horizontal.animatePositionAdditive(node: self.statusNode, offset: CGPoint(x: -widthDifference, y: 0.0))
|
statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
transition.horizontal.animatePositionAdditive(node: statusNode, offset: CGPoint(x: -widthDifference, y: 0.0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func beginTextSelection(range: NSRange?, displayMenu: Bool = true) {
|
public func beginTextSelection(range: NSRange?, displayMenu: Bool = true) {
|
||||||
@ -1260,4 +1272,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
return (substring, entities)
|
return (substring, entities)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func animateClippingTransition(offset: CGFloat, animation: ListViewItemUpdateAnimation) {
|
||||||
|
self.containerNode.clipsToBounds = true
|
||||||
|
self.containerNode.bounds = CGRect(origin: CGPoint(x: 0.0, y: offset), size: self.containerNode.bounds.size)
|
||||||
|
self.containerNode.alpha = 0.0
|
||||||
|
animation.animator.updateAlpha(layer: self.containerNode.layer, alpha: 1.0, completion: nil)
|
||||||
|
animation.animator.updateBounds(layer: self.containerNode.layer, bounds: CGRect(origin: CGPoint(), size: self.containerNode.bounds.size), completion: { [weak self] completed in
|
||||||
|
guard let self, completed else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.containerNode.clipsToBounds = false
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ private let titleFont: UIFont = Font.semibold(15.0)
|
|||||||
public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
private var webPage: TelegramMediaWebpage?
|
private var webPage: TelegramMediaWebpage?
|
||||||
|
|
||||||
private var contentNode: ChatMessageAttachedContentNode
|
public private(set) var contentNode: ChatMessageAttachedContentNode
|
||||||
|
|
||||||
override public var visibility: ListViewItemNodeVisibility {
|
override public var visibility: ListViewItemNodeVisibility {
|
||||||
didSet {
|
didSet {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user