diff --git a/submodules/DrawingUI/Sources/DrawingTextEntity.swift b/submodules/DrawingUI/Sources/DrawingTextEntity.swift index 20cde13ec5..0388e89aeb 100644 --- a/submodules/DrawingUI/Sources/DrawingTextEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingTextEntity.swift @@ -147,11 +147,12 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate return true } - private var emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = [] + private var emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute, CGFloat)] = [] func updateEntities() { self.textView.drawingLayoutManager.ensureLayout(for: self.textView.textContainer) - var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = [] + var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute, CGFloat)] = [] + let fontSize = self.displayFontSize * 0.78 var shouldRepeat = false if let attributedText = self.textView.attributedText { @@ -160,7 +161,11 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { if let start = self.textView.position(from: beginning, offset: range.location), let end = self.textView.position(from: start, offset: range.length), let textRange = self.textView.textRange(from: start, to: end) { let rect = self.textView.firstRect(for: textRange) - customEmojiRects.append((rect, value)) + var emojiFontSize = fontSize + if let font = attributes[.font] as? UIFont { + emojiFontSize = font.pointSize + } + customEmojiRects.append((rect, value, emojiFontSize)) if rect.origin.x.isInfinite { shouldRepeat = true } @@ -202,7 +207,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate self.customEmojiContainerView = customEmojiContainerView } - customEmojiContainerView.update(fontSize: self.displayFontSize * 0.78, textColor: textColor, emojiRects: customEmojiRects) + customEmojiContainerView.update(fontSize: fontSize, textColor: textColor, emojiRects: customEmojiRects) } else if let customEmojiContainerView = self.customEmojiContainerView { customEmojiContainerView.removeFromSuperview() self.customEmojiContainerView = nil @@ -739,13 +744,12 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate let scale = self.textEntity.scale let rotation = self.textEntity.rotation - let itemSize: CGFloat = floor(24.0 * self.displayFontSize * 0.78 / 17.0) - var entities: [DrawingEntity] = [] - for (emojiRect, emojiAttribute) in self.emojiRects { + for (emojiRect, emojiAttribute, fontSize) in self.emojiRects { guard let file = emojiAttribute.file else { continue } + let itemSize: CGFloat = floor(24.0 * fontSize * 0.78 / 17.0) let emojiTextPosition = emojiRect.center.offsetBy(dx: -textSize.width / 2.0, dy: -textSize.height / 2.0) let entity = DrawingStickerEntity(content: .file(file, .sticker)) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index fce4dd4a6d..50e68e6946 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -1802,7 +1802,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { } item.controllerInteraction.performTextSelectionAction(item.message, true, text, action) }) - textSelectionNode.enableQuote = true + textSelectionNode.enableQuote = false self.textSelectionNode = textSelectionNode self.textClippingNode.addSubnode(textSelectionNode) self.textClippingNode.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 755577b0cf..f2e672a88b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -616,8 +616,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if case let .reply(info) = info { if strongSelf.textSelectionNode == nil { strongSelf.updateIsExtractedToContextPreview(true) - if let initialQuote = info.quote, item.message.id == initialQuote.messageId, let string = strongSelf.textNode.textNode.cachedLayout?.attributedString { - let nsString = string.string as NSString + if let initialQuote = info.quote, item.message.id == initialQuote.messageId { + let nsString = item.message.text as NSString let subRange = nsString.range(of: initialQuote.text) if subRange.location != NSNotFound { strongSelf.beginTextSelection(range: subRange, displayMenu: true) @@ -1119,6 +1119,9 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if !item.controllerInteraction.canSendMessages() && !enableCopy { enableQuote = false } + if item.message.id.peerId.namespace == Namespaces.Peer.SecretChat { + enableQuote = false + } textSelectionNode.enableQuote = enableQuote textSelectionNode.enableTranslate = enableOtherActions diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 7ff5b33872..14f33a7653 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -136,6 +136,13 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent guard let self, let item = self.item, let webPage = self.webPage, case let .Loaded(content) = webPage.content else { return ChatMessageBubbleContentTapAction(content: .none) } + + if let file = content.file { + if !file.isVideo, !file.isVideoSticker, !file.isAnimated, !file.isAnimatedSticker, !file.isSticker, !file.isMusic { + return ChatMessageBubbleContentTapAction(content: .openMessage) + } + } + var isConcealed = true if item.message.text.contains(content.url) { isConcealed = false diff --git a/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift index d6340a035b..101a027a0a 100644 --- a/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift @@ -75,7 +75,6 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode { self.iconView.tintColor = theme.chat.inputPanel.panelControlAccentColor self.titleNode = CompositeTextNode() - self.titleNode.imageTintColor = theme.chat.inputPanel.panelControlAccentColor self.textNode = ImmediateTextNodeWithEntities() self.textNode.maximumNumberOfLines = 1 @@ -243,29 +242,30 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode { let icon: UIImage? icon = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextChannelIcon")?.withRenderingMode(.alwaysTemplate) + //TODO:localize if let _ = strongSelf.quote { if let icon { let string = "Reply to Quote by " - titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: .white))] + titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor))] titleText.append(.icon(icon)) - titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(15.0), textColor: .white))) + titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor))) } } else { if let icon { let string = "Reply to " - titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: .white))] + titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor))] titleText.append(.icon(icon)) - titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(15.0), textColor: .white))) + titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor))) } } } else { if let _ = strongSelf.quote { //TODO:localize let string = "Reply to Quote by \(authorName)" - titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: .white))] + titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor))] } else { let string = strongSelf.strings.Conversation_ReplyMessagePanelTitle(authorName).string - titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: .white))] + titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor))] } if strongSelf.messageId.peerId != strongSelf.chatPeerId { @@ -276,9 +276,9 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode { } else { icon = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextGroupIcon")?.withRenderingMode(.alwaysTemplate) } - if let icon { - titleText.append(.icon(icon)) - titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(15.0), textColor: .white))) + if let iconImage = generateTintedImage(image: icon, color: strongSelf.theme.chat.inputPanel.panelControlAccentColor) { + titleText.append(.icon(iconImage)) + titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) } } } @@ -387,10 +387,25 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode { self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme) self.iconView.tintColor = theme.chat.inputPanel.panelControlAccentColor - self.titleNode.imageTintColor = theme.chat.inputPanel.panelControlAccentColor + self.titleNode.components = self.titleNode.components.map { item in + switch item { + case let .text(text): + let updatedText = NSMutableAttributedString(attributedString: text) + updatedText.addAttribute(.foregroundColor, value: theme.chat.inputPanel.panelControlAccentColor, range: NSRange(location: 0, length: updatedText.length)) + return .text(updatedText) + case let .icon(icon): + if let iconImage = generateTintedImage(image: icon, color: theme.chat.inputPanel.panelControlAccentColor) { + return .icon(iconImage) + } else { + return .icon(icon) + } + } + } - if let text = self.textNode.attributedText?.string { - self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: self.textIsOptions ? self.theme.chat.inputPanel.secondaryTextColor : self.theme.chat.inputPanel.primaryTextColor) + if let text = self.textNode.attributedText { + let updatedText = NSMutableAttributedString(attributedString: text) + updatedText.addAttribute(.foregroundColor, value: self.textIsOptions ? self.theme.chat.inputPanel.secondaryTextColor : self.theme.chat.inputPanel.primaryTextColor, range: NSRange(location: 0, length: updatedText.length)) + self.textNode.attributedText = updatedText } self.textNode.spoilerColor = self.theme.chat.inputPanel.secondaryTextColor diff --git a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift index 8c4bdd3667..018d5d4646 100644 --- a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift +++ b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift @@ -606,11 +606,11 @@ public final class CustomEmojiContainerView: UIView { preconditionFailure() } - public func update(fontSize: CGFloat, textColor: UIColor, emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)]) { + public func update(fontSize: CGFloat, textColor: UIColor, emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute, CGFloat)]) { var nextIndexById: [Int64: Int] = [:] var validKeys = Set() - for (rect, emoji) in emojiRects { + for (rect, emoji, fontSize) in emojiRects { let index: Int if let nextIndex = nextIndexById[emoji.fileId] { index = nextIndex diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index 7742b05ae2..f7824966c8 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -663,7 +663,7 @@ public final class TextFieldComponent: Component { } var spoilerRects: [CGRect] = [] - var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = [] + var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute, CGFloat)] = [] let textView = self.textView if let attributedText = textView.attributedText { @@ -707,9 +707,13 @@ public final class TextFieldComponent: Component { if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { if let start = textView.position(from: beginning, offset: range.location), let end = textView.position(from: start, offset: range.length), let textRange = textView.textRange(from: start, to: end) { + var emojiFontSize = component.fontSize + if let font = attributes[.font] as? UIFont { + emojiFontSize = font.pointSize + } let textRects = textView.selectionRects(for: textRange) for textRect in textRects { - customEmojiRects.append((textRect.rect, value)) + customEmojiRects.append((textRect.rect, value, emojiFontSize)) break } } diff --git a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift index 032c38f515..f384ad05c0 100644 --- a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift +++ b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift @@ -132,6 +132,7 @@ public final class TextNodeWithEntities { let string = NSMutableAttributedString(attributedString: sourceString) var fullRange = NSRange(location: 0, length: string.length) + var originalTextId = 0 while true { var found = false string.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: fullRange, options: [], using: { value, range, stop in @@ -141,7 +142,8 @@ public final class TextNodeWithEntities { let replacementRange = NSRange(location: 0, length: updatedSubstring.length) updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange) updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange) - updatedSubstring.addAttribute(originalTextAttributeKey, value: string.attributedSubstring(from: range).string, range: replacementRange) + updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange) + originalTextId += 1 let itemSize = (font.pointSize * 24.0 / 17.0) diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 89206fe64b..7167d73c52 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -404,6 +404,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch } if selfController.presentationInterfaceState.copyProtectionEnabled || messages.first?.isCopyProtected() == true { + } else if messages.first?.id.peerId.namespace == Namespaces.Peer.SecretChat { } else { //TODO:localize items.append(.action(ContextMenuActionItem(text: "Reply in Another Chat", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController] c, f in @@ -746,7 +747,17 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD if case let .Loaded(content) = linkOptions.webpage.content, let isMediaLargeByDefault = content.isMediaLargeByDefault, isMediaLargeByDefault { //TODO:localize - items.append(.action(ContextMenuActionItem(text: linkOptions.largeMedia ? "Shrink Photo" : "Enlarge Photo", icon: { theme in + let shrinkTitle: String + let enlargeTitle: String + if let file = content.file, file.isVideo { + shrinkTitle = "Shrink Video" + enlargeTitle = "Enlarge Video" + } else { + shrinkTitle = "Shrink Photo" + enlargeTitle = "Enlarge Photo" + } + + items.append(.action(ContextMenuActionItem(text: linkOptions.largeMedia ? shrinkTitle : enlargeTitle, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: !linkOptions.largeMedia ? "Chat/Context Menu/ImageEnlarge" : "Chat/Context Menu/ImageShrink"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController] _, f in selfController?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ac1cf29076..4846c9d2a8 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3819,7 +3819,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } - if let messageId = message?.id, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { + if let messageId = message?.id, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) ?? message { var quoteData: EngineMessageReplyQuote? let nsRange = NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound) @@ -10765,9 +10765,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let inputText = NSMutableAttributedString(attributedString: textInputState.inputText) let range = textInputState.selectionRange - inputText.replaceCharacters(in: NSMakeRange(range.lowerBound, range.count), with: text) - let selectionPosition = range.lowerBound + (text.string as NSString).length + let updatedText = NSMutableAttributedString(attributedString: text) + if range.lowerBound < inputText.length { + if let quote = inputText.attribute(ChatTextInputAttributes.quote, at: range.lowerBound, effectiveRange: nil) { + updatedText.addAttribute(ChatTextInputAttributes.quote, value: quote, range: NSRange(location: 0, length: updatedText.length)) + } + } + inputText.replaceCharacters(in: NSMakeRange(range.lowerBound, range.count), with: updatedText) + + let selectionPosition = range.lowerBound + (updatedText.string as NSString).length return (ChatTextInputState(inputText: inputText, selectionRange: selectionPosition ..< selectionPosition), inputMode) } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 82436a5d8a..5f43c18512 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -3382,7 +3382,25 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let replyMessageSubject = self.chatPresentationInterfaceState.interfaceState.replyMessageSubject, let quote = replyMessageSubject.quote { if let replyMessage = self.chatPresentationInterfaceState.replyMessage { - if !replyMessage.text.contains(quote.text) { + let nsText = replyMessage.text as NSString + var startIndex = 0 + var found = false + while true { + let range = nsText.range(of: quote.text, range: NSRange(location: startIndex, length: nsText.length - startIndex)) + if range.location != NSNotFound { + let subEntities = messageTextEntitiesInRange(entities: replyMessage.textEntitiesAttribute?.entities ?? [], range: range, onlyQuoteable: true) + if subEntities == quote.entities { + found = true + break + } + + startIndex = range.upperBound + } else { + break + } + } + + if !found { //TODO:localize let authorName: String = (replyMessage.author.flatMap(EnginePeer.init))?.compactDisplayTitle ?? "" let errorTextData = self.chatPresentationInterfaceState.strings.Chat_ErrorQuoteOutdatedText(authorName) diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index cfe936e805..c161584f93 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -2572,7 +2572,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch let textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor var rects: [CGRect] = [] - var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = [] + var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute, CGFloat)] = [] let fontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) @@ -2619,7 +2619,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch if let start = textInputNode.textView.position(from: beginning, offset: range.location), let end = textInputNode.textView.position(from: start, offset: range.length), let textRange = textInputNode.textView.textRange(from: start, to: end) { let textRects = textInputNode.textView.selectionRects(for: textRange) for textRect in textRects { - customEmojiRects.append((textRect.rect, value)) + var emojiFontSize = fontSize + if let font = attributes[.font] as? UIFont { + emojiFontSize = font.pointSize + } + customEmojiRects.append((textRect.rect, value, emojiFontSize)) break } } diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index 2e2b636988..ba9a8fbd57 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -26,6 +26,15 @@ public struct ChatTextInputAttributes { } public let originalTextAttributeKey = NSAttributedString.Key(rawValue: "Attribute__OriginalText") +public final class OriginalTextAttribute: NSObject { + public let id: Int + public let string: String + + public init(id: Int, string: String) { + self.id = id + self.string = string + } +} public func stateAttributedStringForText(_ text: NSAttributedString) -> NSAttributedString { let sourceString = NSMutableAttributedString(attributedString: text) diff --git a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift index 9247e0794c..6531efe9ef 100644 --- a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift +++ b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift @@ -446,6 +446,11 @@ public final class TextSelectionNode: ASDisplayNode { } public func setSelection(range: NSRange, displayMenu: Bool) { + guard let cachedLayout = self.textNode.cachedLayout, let attributedString = cachedLayout.attributedString else { + return + } + let range = self.convertSelectionFromOriginalText(attributedString: attributedString, range: range) + self.currentRange = (range.lowerBound, range.upperBound) self.updateSelection(range: range, animateIn: true) self.updateIsActive(true) @@ -455,12 +460,109 @@ public final class TextSelectionNode: ASDisplayNode { } } + private func convertSelectionToOriginalText(attributedString: NSAttributedString, range: NSRange) -> NSRange { + var adjustedRange = range + + do { + attributedString.enumerateAttribute(originalTextAttributeKey, in: NSRange(location: 0, length: range.lowerBound), options: [], using: { value, range, stop in + guard let value = value as? OriginalTextAttribute else { + return + } + let updatedSubstring = NSMutableAttributedString(string: value.string) + let difference = updatedSubstring.length - range.length + + adjustedRange.location += difference + }) + } + + do { + attributedString.enumerateAttribute(originalTextAttributeKey, in: range, options: [], using: { value, range, stop in + guard let value = value as? OriginalTextAttribute else { + return + } + let updatedSubstring = NSMutableAttributedString(string: value.string) + let difference = updatedSubstring.length - range.length + + adjustedRange.length += difference + }) + } + + return adjustedRange + } + + private func convertSelectionFromOriginalText(attributedString: NSAttributedString, range: NSRange) -> NSRange { + var adjustedRange = range + + final class PreviousText: NSObject { + let id: Int + let string: String + + init(id: Int, string: String) { + self.id = id + self.string = string + } + } + + var nextId = 0 + let attributedString = NSMutableAttributedString(attributedString: attributedString) + var fullRange = NSRange(location: 0, length: attributedString.length) + while true { + var found = false + attributedString.enumerateAttribute(originalTextAttributeKey, in: fullRange, options: [], using: { value, range, stop in + if let value = value as? OriginalTextAttribute { + let updatedSubstring = NSMutableAttributedString(string: value.string) + + let replacementRange = NSRange(location: 0, length: updatedSubstring.length) + updatedSubstring.addAttributes(attributedString.attributes(at: range.location, effectiveRange: nil), range: replacementRange) + updatedSubstring.addAttribute(NSAttributedString.Key(rawValue: "__previous_text"), value: PreviousText(id: nextId, string: attributedString.attributedSubstring(from: range).string), range: replacementRange) + nextId += 1 + + attributedString.replaceCharacters(in: range, with: updatedSubstring) + let updatedRange = NSRange(location: range.location, length: updatedSubstring.length) + + found = true + stop.pointee = ObjCBool(true) + fullRange = NSRange(location: updatedRange.upperBound, length: fullRange.upperBound - range.upperBound) + } + }) + if !found { + break + } + } + + do { + attributedString.enumerateAttribute(NSAttributedString.Key(rawValue: "__previous_text"), in: NSRange(location: 0, length: range.lowerBound), options: [], using: { value, range, stop in + guard let value = value as? PreviousText else { + return + } + let updatedSubstring = NSMutableAttributedString(string: value.string) + let difference = updatedSubstring.length - range.length + + adjustedRange.location += difference + }) + } + + do { + attributedString.enumerateAttribute(NSAttributedString.Key(rawValue: "__previous_text"), in: range, options: [], using: { value, range, stop in + guard let value = value as? PreviousText else { + return + } + let updatedSubstring = NSMutableAttributedString(string: value.string) + let difference = updatedSubstring.length - range.length + + adjustedRange.length += difference + }) + } + + return adjustedRange + } + public func getSelection() -> NSRange? { - guard let currentRange = self.currentRange else { + guard let currentRange = self.currentRange, let cachedLayout = self.textNode.cachedLayout, let attributedString = cachedLayout.attributedString else { return nil } let range = NSRange(location: min(currentRange.0, currentRange.1), length: max(currentRange.0, currentRange.1) - min(currentRange.0, currentRange.1)) - return range + return self.convertSelectionToOriginalText(attributedString: attributedString, range: range) } private func updateSelection(range: NSRange?, animateIn: Bool) { @@ -578,8 +680,8 @@ public final class TextSelectionNode: ASDisplayNode { while true { var found = false string.enumerateAttribute(originalTextAttributeKey, in: fullRange, options: [], using: { value, range, stop in - if let value = value as? String { - let updatedSubstring = NSMutableAttributedString(string: value) + if let value = value as? OriginalTextAttribute { + let updatedSubstring = NSMutableAttributedString(string: value.string) let replacementRange = NSRange(location: 0, length: updatedSubstring.length) updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange) @@ -597,6 +699,8 @@ public final class TextSelectionNode: ASDisplayNode { } } + let adjustedRange = self.convertSelectionToOriginalText(attributedString: attributedString, range: range) + var actions: [ContextMenuAction] = [] if self.enableCopy { actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.strings.Conversation_ContextMenuCopy), action: { [weak self] in @@ -606,7 +710,7 @@ public final class TextSelectionNode: ASDisplayNode { } if self.enableQuote { actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuQuote, accessibilityLabel: self.strings.Conversation_ContextMenuQuote), action: { [weak self] in - self?.performAction(string, .quote(range: range.lowerBound ..< range.upperBound)) + self?.performAction(string, .quote(range: adjustedRange.lowerBound ..< adjustedRange.upperBound)) self?.cancelSelection() })) } else if self.enableLookup {