From 9a1799d616586e8f9b36a1561b647826d4038892 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 27 May 2022 19:28:59 +0400 Subject: [PATCH] Text selection --- .../ChatMessageAttachedContentNode.swift | 3 +- .../Sources/ChatMessageBubbleItemNode.swift | 4 +- .../ChatMessageFileBubbleContentNode.swift | 15 +++- .../ChatMessageInteractiveFileNode.swift | 76 ++++++++++++++++++- 4 files changed, 93 insertions(+), 5 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index 231776a8eb..ee635133e5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -584,7 +584,8 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { displayReactions: false, messageSelection: nil, layoutConstants: layoutConstants, - constrainedSize: CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height) + constrainedSize: CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height), + controllerInteraction: controllerInteraction )) refineContentFileLayout = refineLayout } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index f6afb54426..1af1065af5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -2697,6 +2697,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode contentNode.updateIsTextSelectionActive = { [weak contextSourceNode] value in contextSourceNode?.updateDistractionFreeMode?(value) + } contentNode.updateIsExtractedToContextPreview(contextSourceNode.isExtractedToContextPreview) } @@ -2829,8 +2830,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode animation.animator.updatePosition(layer: strongSelf.clippingNode.layer, position: backgroundFrame.center, completion: nil) strongSelf.clippingNode.clipsToBounds = true animation.animator.updateBounds(layer: strongSelf.clippingNode.layer, bounds: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size), completion: { [weak strongSelf] _ in - let _ = strongSelf - //strongSelf?.clippingNode.clipsToBounds = false + strongSelf?.clippingNode.clipsToBounds = false }) strongSelf.backgroundNode.updateLayout(size: backgroundFrame.size, transition: animation) diff --git a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift index c9cf6b1d94..35f74b7f40 100644 --- a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift @@ -72,6 +72,10 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value) } + + self.interactiveFileNode.updateIsTextSelectionActive = { [weak self] value in + self?.updateIsTextSelectionActive?(value) + } } override func accessibilityActivate() -> Bool { @@ -136,7 +140,8 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { displayReactions: true, messageSelection: item.message.groupingKey != nil ? selection : nil, layoutConstants: layoutConstants, - constrainedSize: CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height) + constrainedSize: CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height), + controllerInteraction: item.controllerInteraction )) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) @@ -197,6 +202,14 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { self.interactiveFileNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } + override func willUpdateIsExtractedToContextPreview(_ value: Bool) { + self.interactiveFileNode.willUpdateIsExtractedToContextPreview(value) + } + + override func updateIsExtractedToContextPreview(_ value: Bool) { + self.interactiveFileNode.updateIsExtractedToContextPreview(value) + } + override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if self.interactiveFileNode.dateAndStatusNode.supernode != nil, let _ = self.interactiveFileNode.dateAndStatusNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.dateAndStatusNode.view), with: nil) { return .ignore diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 05e542a239..dc876678fb 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -24,6 +24,7 @@ import AudioWaveformComponent import ShimmerEffect import ConvertOpusToAAC import LocalAudioTranscription +import TextSelectionNode private struct FetchControls { let fetch: (Bool) -> Void @@ -62,6 +63,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let messageSelection: Bool? let layoutConstants: ChatMessageItemLayoutConstants let constrainedSize: CGSize + let controllerInteraction: ChatControllerInteraction init( context: AccountContext, @@ -82,7 +84,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { displayReactions: Bool, messageSelection: Bool?, layoutConstants: ChatMessageItemLayoutConstants, - constrainedSize: CGSize + constrainedSize: CGSize, + controllerInteraction: ChatControllerInteraction ) { self.context = context self.presentationData = presentationData @@ -103,6 +106,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { self.messageSelection = messageSelection self.layoutConstants = layoutConstants self.constrainedSize = constrainedSize + self.controllerInteraction = controllerInteraction } } @@ -124,6 +128,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { private var audioTranscriptionButton: ComponentHostView? private let textNode: TextNode + private var textSelectionNode: TextSelectionNode? + + var updateIsTextSelectionActive: ((Bool) -> Void)? + let dateAndStatusNode: ChatMessageDateAndStatusNode private let consumableContentNode: ASImageNode @@ -179,6 +187,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { private var context: AccountContext? private var message: Message? + private var arguments: Arguments? private var presentationData: ChatPresentationData? private var file: TelegramMediaFile? private var progressFrame: CGRect? @@ -805,6 +814,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { strongSelf.context = arguments.context strongSelf.presentationData = arguments.presentationData strongSelf.message = arguments.message + strongSelf.arguments = arguments strongSelf.file = arguments.file let _ = titleApply() @@ -865,6 +875,15 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } + if let textSelectionNode = strongSelf.textSelectionNode { + let shouldUpdateLayout = textSelectionNode.frame.size != textFrame.size + textSelectionNode.frame = textFrame + textSelectionNode.highlightAreaNode.frame = textFrame + if shouldUpdateLayout { + textSelectionNode.updateLayout() + } + } + if let statusSizeAndApply = statusSizeAndApply { let statusFrame: CGRect if textString != nil { @@ -1545,6 +1564,61 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } + func willUpdateIsExtractedToContextPreview(_ value: Bool) { + if !value { + if let textSelectionNode = self.textSelectionNode { + self.textSelectionNode = nil + textSelectionNode.highlightAreaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + textSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak textSelectionNode] _ in + textSelectionNode?.highlightAreaNode.removeFromSupernode() + textSelectionNode?.removeFromSupernode() + }) + } + } + } + + func updateIsExtractedToContextPreview(_ value: Bool) { + if value { + if self.textSelectionNode == nil, let item = self.arguments, /*!item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected(),*/ let rootNode = item.controllerInteraction.chatControllerNode() { + let selectionColor: UIColor + let knobColor: UIColor + if item.message.effectivelyIncoming(item.context.account.peerId) { + selectionColor = item.presentationData.theme.theme.chat.message.incoming.textSelectionColor + knobColor = item.presentationData.theme.theme.chat.message.incoming.textSelectionKnobColor + } else { + selectionColor = item.presentationData.theme.theme.chat.message.outgoing.textSelectionColor + knobColor = item.presentationData.theme.theme.chat.message.outgoing.textSelectionKnobColor + } + + let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: selectionColor, knob: knobColor), strings: item.presentationData.strings, textNode: self.textNode, updateIsActive: { [weak self] value in + self?.updateIsTextSelectionActive?(value) + }, present: { [weak self] c, a in + self?.arguments?.controllerInteraction.presentGlobalOverlayController(c, a) + }, rootNode: rootNode, performAction: { [weak self] text, action in + guard let strongSelf = self, let item = strongSelf.arguments else { + return + } + item.controllerInteraction.performTextSelectionAction(item.message.stableId, text, action) + }) + self.textSelectionNode = textSelectionNode + self.addSubnode(textSelectionNode) + self.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode) + textSelectionNode.frame = self.textNode.frame + textSelectionNode.highlightAreaNode.frame = self.textNode.frame + } + } else { + if let textSelectionNode = self.textSelectionNode { + self.textSelectionNode = nil + self.updateIsTextSelectionActive?(false) + textSelectionNode.highlightAreaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + textSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak textSelectionNode] _ in + textSelectionNode?.highlightAreaNode.removeFromSupernode() + textSelectionNode?.removeFromSupernode() + }) + } + } + } + func transitionNode(media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let iconNode = self.iconNode, let file = self.file, file.isEqual(to: media) { return (iconNode, iconNode.bounds, { [weak iconNode] in