diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 6db0fac958..cef20e0fe2 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1757,7 +1757,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.dustNode = dustNode strongSelf.contextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode) } - dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) + dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) dustNode.frame = textNodeFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) } else if let dustNode = strongSelf.dustNode { diff --git a/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift b/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift index 430c2a1e9e..3e4812eba5 100644 --- a/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift +++ b/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift @@ -316,7 +316,7 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe rightInset += inlineAction.icon.size.width + 8.0 } - let itemText = textAttributedStringForStateText(item.text, fontSize: 17.0, textColor: item.presentationData.theme.chat.inputPanel.primaryTextColor, accentTextColor: item.presentationData.theme.chat.inputPanel.panelControlAccentColor, writingDirection: nil) + let itemText = textAttributedStringForStateText(item.text, fontSize: 17.0, textColor: item.presentationData.theme.chat.inputPanel.primaryTextColor, accentTextColor: item.presentationData.theme.chat.inputPanel.panelControlAccentColor, writingDirection: nil, spoilersRevealed: false) let measureText = NSMutableAttributedString(attributedString: itemText) let measureRawString = measureText.string if measureRawString.hasSuffix("\n") || measureRawString.isEmpty { diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index 03be16238d..8871464fdf 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -173,7 +173,7 @@ public final class TextNodeLayout: NSObject { fileprivate let textStroke: (UIColor, CGFloat)? fileprivate let displaySpoilers: Bool public let hasRTL: Bool - public let spoilers: [CGRect] + public let spoilers: [(NSRange, CGRect)] fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, explicitAlignment: NSTextAlignment, resolvedAlignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool) { self.attributedString = attributedString @@ -198,12 +198,12 @@ public final class TextNodeLayout: NSObject { self.textStroke = textStroke self.displaySpoilers = displaySpoilers var hasRTL = false - var spoilers: [CGRect] = [] + var spoilers: [(NSRange, CGRect)] = [] for line in lines { if line.isRTL { hasRTL = true } - spoilers.append(contentsOf: line.spoilers.map { $0.frame.offsetBy(dx: line.frame.minX, dy: line.frame.minY) }) + spoilers.append(contentsOf: line.spoilers.map { ( $0.range, $0.frame.offsetBy(dx: line.frame.minX, dy: line.frame.minY)) }) } self.hasRTL = hasRTL self.spoilers = spoilers diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index c3cb3842e9..070be199d5 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -737,7 +737,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } if let dustNode = self.dustNode { - dustNode.update(size: textFrame.size, color: .white, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) + dustNode.update(size: textFrame.size, color: .white, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) } } else { diff --git a/submodules/GalleryUI/Sources/RecognizedTextSelectionNode.swift b/submodules/GalleryUI/Sources/RecognizedTextSelectionNode.swift index dc42d26acf..8f5713c25c 100644 --- a/submodules/GalleryUI/Sources/RecognizedTextSelectionNode.swift +++ b/submodules/GalleryUI/Sources/RecognizedTextSelectionNode.swift @@ -510,18 +510,18 @@ public final class RecognizedTextSelectionNode: ASDisplayNode { self?.performAction(selectedText, .lookup) let _ = self?.dismissSelection() })) -// if #available(iOS 15.0, *) { -// actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in -// self?.performAction(selectedText, .translate) -// let _ = self?.dismissSelection() -// })) -// } - if isSpeakSelectionEnabled() { - actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in - self?.performAction(selectedText, .speak) + if #available(iOS 15.0, *) { + actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in + self?.performAction(selectedText, .translate) let _ = self?.dismissSelection() })) } +// if isSpeakSelectionEnabled() { +// actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in +// self?.performAction(selectedText, .speak) +// let _ = self?.dismissSelection() +// })) +// } actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in self?.performAction(selectedText, .share) let _ = self?.dismissSelection() diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index d9dc7d04ec..54fcc55573 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -760,7 +760,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState f(.default) }))) - if #available(iOS 15.0, *) { + if #available(iOS 15.0, *), !message.text.isEmpty { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuTranslate, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in diff --git a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift index 3333e967ff..6ea076b896 100644 --- a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift @@ -13,6 +13,8 @@ import LocalizedPeerData import StickerResources import PhotoResources import TelegramStringFormatting +import TextFormat +import InvisibleInkDustNode public final class ChatMessageNotificationItem: NotificationItem { let context: AccountContext @@ -68,6 +70,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { private let titleIconNode: ASImageNode private let titleNode: TextNode private let textNode: TextNode + private var dustNode: InvisibleInkDustNode? private let imageNode: TransformImageNode private var titleAttributedText: NSAttributedString? @@ -157,6 +160,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { var imageDimensions: CGSize? var isRound = false var messageText: String + var messageEntities: [MessageTextEntity]? if item.messages.first?.id.peerId.namespace == Namespaces.Peer.SecretChat { messageText = item.strings.PUSH_ENCRYPTED_MESSAGE("").string } else if item.messages.count == 1 { @@ -180,7 +184,19 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { if message.containsSecretMedia { imageDimensions = nil } - messageText = descriptionStringForMessage(contentSettings: item.context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: item.strings, nameDisplayOrder: item.nameDisplayOrder, dateTimeFormat: item.dateTimeFormat, accountPeerId: item.context.account.peerId).0 + let (textString, _, isText) = descriptionStringForMessage(contentSettings: item.context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: item.strings, nameDisplayOrder: item.nameDisplayOrder, dateTimeFormat: item.dateTimeFormat, accountPeerId: item.context.account.peerId) + if isText { + messageText = message.text + messageEntities = message.textEntitiesAttribute?.entities.filter { entity in + if case .Spoiler = entity.type { + return true + } else { + return false + } + } + } else { + messageText = textString + } } else if item.messages.count > 1, let peer = item.messages[0].peers[item.messages[0].id.peerId] { var displayAuthor = true if let channel = peer as? TelegramChannel { @@ -286,8 +302,15 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { title = "📅 \(currentTitle)" } - messageText = messageText.replacingOccurrences(of: "\n\n", with: " ") - + let textFont = compact ? Font.regular(15.0) : Font.regular(16.0) + let textColor = presentationData.theme.inAppNotification.primaryTextColor + var attributedMessageText: NSAttributedString + if let messageEntities = messageEntities { + attributedMessageText = stringWithAppliedEntities(messageText, entities: messageEntities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false) + } else { + attributedMessageText = NSAttributedString(string: messageText.replacingOccurrences(of: "\n\n", with: " "), font: textFont, textColor: textColor) + } + self.titleAttributedText = NSAttributedString(string: title ?? "", font: compact ? Font.semibold(15.0) : Font.semibold(16.0), textColor: presentationData.theme.inAppNotification.primaryTextColor) let imageNodeLayout = self.imageNode.asyncLayout() @@ -325,7 +348,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { self.imageNode.setSignal(updateImageSignal) } - self.textAttributedText = NSAttributedString(string: messageText, font: compact ? Font.regular(15.0) : Font.regular(16.0), textColor: presentationData.theme.inAppNotification.primaryTextColor) + self.textAttributedText = attributedMessageText if let width = self.validLayout { let _ = self.updateLayout(width: width, transition: .immediate) @@ -361,7 +384,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: self.textAttributedText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) let _ = titleApply() let _ = textApply() - + let textSpacing: CGFloat = 1.0 let titleFrame = CGRect(origin: CGPoint(x: leftInset + titleInset, y: 1.0 + floor((panelHeight - textLayout.size.height - titleLayout.size.height - textSpacing) / 2.0)), size: titleLayout.size) @@ -371,10 +394,28 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { transition.updateFrame(node: self.titleIconNode, frame: CGRect(origin: CGPoint(x: leftInset + 1.0, y: titleFrame.minY + 3.0), size: image.size)) } - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + textSpacing), size: textLayout.size)) + let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + textSpacing), size: textLayout.size) + transition.updateFrame(node: self.textNode, frame: textFrame) transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: width - 10.0 - imageSize.width, y: (panelHeight - imageSize.height) / 2.0), size: imageSize)) + if !textLayout.spoilers.isEmpty, let presentationData = self.item?.context.sharedContext.currentPresentationData.with({ $0 }) { + let dustNode: InvisibleInkDustNode + if let current = self.dustNode { + dustNode = current + } else { + dustNode = InvisibleInkDustNode(textNode: nil) + dustNode.isUserInteractionEnabled = false + self.dustNode = dustNode + self.insertSubnode(dustNode, aboveSubnode: self.textNode) + } + dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) + dustNode.update(size: dustNode.frame.size, color: presentationData.theme.inAppNotification.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + } else if let dustNode = self.dustNode { + dustNode.removeFromSupernode() + self.dustNode = nil + } + return panelHeight } } diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift index a300505b13..e4b5d79f78 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift @@ -73,6 +73,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { let titleColor: UIColor let lineImage: UIImage? let textColor: UIColor + let dustColor: UIColor switch type { case let .bubble(incoming): @@ -83,6 +84,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { } else { textColor = incoming ? presentationData.theme.theme.chat.message.incoming.primaryTextColor : presentationData.theme.theme.chat.message.outgoing.primaryTextColor } + dustColor = incoming ? presentationData.theme.theme.chat.message.incoming.secondaryTextColor : presentationData.theme.theme.chat.message.outgoing.secondaryTextColor case .standalone: let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) titleColor = serviceColor.primaryText @@ -90,6 +92,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { let graphics = PresentationResourcesChat.additionalGraphics(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners) lineImage = graphics.chatServiceVerticalLineImage textColor = titleColor + dustColor = titleColor } @@ -251,7 +254,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { node.contentNode.insertSubnode(dustNode, aboveSubnode: textNode) } dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) - dustNode.update(size: dustNode.frame.size, color: titleColor, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + dustNode.update(size: dustNode.frame.size, color: dustColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) } else if let dustNode = node.dustNode { dustNode.removeFromSupernode() node.dustNode = nil diff --git a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift index 4d4231b572..2c7f91b79d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift @@ -405,7 +405,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.insertSubnode(dustNode, aboveSubnode: spoilerTextNode) } 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, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + dustNode.update(size: dustNode.frame.size, color: messageTheme.secondaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) } else if let spoilerTextNode = strongSelf.spoilerTextNode { strongSelf.spoilerTextNode = nil spoilerTextNode.removeFromSupernode() @@ -667,20 +667,36 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } item.controllerInteraction.performTextSelectionAction(item.message.stableId, text, action) }) + textSelectionNode.updateRange = { [weak self] selectionRange in + if let strongSelf = self, let dustNode = strongSelf.dustNode, !dustNode.isRevealed, let textLayout = strongSelf.textNode.cachedLayout, !textLayout.spoilers.isEmpty, let selectionRange = selectionRange { + for (spoilerRange, _) in textLayout.spoilers { + if let intersection = selectionRange.intersection(spoilerRange), intersection.length > 0 { + dustNode.update(revealed: true) + return + } + } + } + } 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() - }) + } 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() + }) + } + + if let dustNode = self.dustNode, dustNode.isRevealed { + dustNode.update(revealed: false) + } } } diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index ba2f818023..1ef0d2ffde 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -17,6 +17,7 @@ import Speak import ObjCRuntimeUtils import AvatarNode import ContextUI +import InvisibleInkDustNode private let accessoryButtonFont = Font.medium(14.0) private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) @@ -240,6 +241,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { let textInputContainerBackgroundNode: ASImageNode let textInputContainer: ASDisplayNode var textInputNode: EditableTextNode? + var dustNode: InvisibleInkDustNode? let textInputBackgroundNode: ASImageNode private var transparentTextInputBackgroundImage: UIImage? @@ -402,12 +404,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) } - textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil) + textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed) textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count) self.updatingInputState = false self.keepSendButtonEnabled = keepSendButtonEnabled self.extendedSearchLayout = extendedSearchLayout self.updateTextNodeText(animated: animated) + self.updateSpoiler() } } @@ -440,6 +443,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { private let accessoryButtonSpacing: CGFloat = 0.0 private let accessoryButtonInset: CGFloat = 2.0 + private var spoilersRevealed = false + init(presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) { self.presentationInterfaceState = presentationInterfaceState @@ -896,6 +901,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(baseFontSize), NSAttributedString.Key.foregroundColor.rawValue: textColor] textInputNode.tintColor = tintColor + + self.updateSpoiler() } } @@ -1770,9 +1777,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { @objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState { let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) - refreshChatTextInputAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) + refreshChatTextInputAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed) refreshChatTextInputTypingAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) + self.updateSpoiler() + let inputTextState = self.inputTextState self.interfaceInteraction?.updateTextInputStateAndMode({ _, inputMode in return (inputTextState, inputMode) }) @@ -1783,6 +1792,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } + private func updateSpoiler() { + guard let textInputNode = self.textInputNode else { + return + } + print(textInputNode.attributedText?.description ?? "") + } + private func updateCounterTextNode(transition: ContainedViewLayoutTransition) { if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState, let editMessage = presentationInterfaceState.interfaceState.editMessage, let inputTextMaxLength = editMessage.inputTextMaxLength { let textCount = Int32(textInputNode.textView.text.count) @@ -2203,7 +2219,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) } - let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil) + let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed) string.replaceCharacters(in: range, with: cleanReplacementString) self.textInputNode?.attributedText = string self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0) diff --git a/submodules/TelegramUI/Sources/PeerSelectionTextInputPanelNode.swift b/submodules/TelegramUI/Sources/PeerSelectionTextInputPanelNode.swift index d1f2131210..f1a3b97b39 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionTextInputPanelNode.swift @@ -207,7 +207,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) } - textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil) + textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: false) textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count) self.updatingInputState = false self.updateTextNodeText(animated: animated) @@ -697,7 +697,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A @objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState { let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) - refreshChatTextInputAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) + refreshChatTextInputAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: false) refreshChatTextInputTypingAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) let inputTextState = self.inputTextState @@ -999,7 +999,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) } - let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil) + let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: false) string.replaceCharacters(in: range, with: cleanReplacementString) self.textInputNode?.attributedText = string self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0) diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index 14fca54731..597539aaf2 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -47,7 +47,7 @@ public struct ChatTextFontAttributes: OptionSet { public static let blockQuote = ChatTextFontAttributes(rawValue: 1 << 3) } -public func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize: CGFloat, textColor: UIColor, accentTextColor: UIColor, writingDirection: NSWritingDirection?) -> NSAttributedString { +public func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize: CGFloat, textColor: UIColor, accentTextColor: UIColor, writingDirection: NSWritingDirection?, spoilersRevealed: Bool) -> NSAttributedString { let result = NSMutableAttributedString(string: stateText.string) let fullRange = NSRange(location: 0, length: result.length) @@ -408,7 +408,7 @@ private func refreshTextUrls(text: NSString, initialAttributedText: NSAttributed } } -public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme: PresentationTheme, baseFontSize: CGFloat) { +public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme: PresentationTheme, baseFontSize: CGFloat, spoilersRevealed: Bool) { guard let initialAttributedText = textNode.attributedText, initialAttributedText.length != 0 else { return } @@ -423,14 +423,14 @@ public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme var attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(initialAttributedText)) refreshTextMentions(text: text, initialAttributedText: initialAttributedText, attributedText: attributedText, fullRange: fullRange) - var resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection) + var resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed) text = resultAttributedText.string as NSString fullRange = NSRange(location: 0, length: initialAttributedText.length) attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(resultAttributedText)) refreshTextUrls(text: text, initialAttributedText: resultAttributedText, attributedText: attributedText, fullRange: fullRange) - resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection) + resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed) if !resultAttributedText.isEqual(to: initialAttributedText) { textNode.textView.textStorage.removeAttribute(NSAttributedString.Key.font, range: fullRange) @@ -502,7 +502,7 @@ public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme } } -public func refreshGenericTextInputAttributes(_ textNode: ASEditableTextNode, theme: PresentationTheme, baseFontSize: CGFloat) { +public func refreshGenericTextInputAttributes(_ textNode: ASEditableTextNode, theme: PresentationTheme, baseFontSize: CGFloat, spoilersRevealed: Bool = false) { guard let initialAttributedText = textNode.attributedText, initialAttributedText.length != 0 else { return } @@ -515,14 +515,14 @@ public func refreshGenericTextInputAttributes(_ textNode: ASEditableTextNode, th var text: NSString = initialAttributedText.string as NSString var fullRange = NSRange(location: 0, length: initialAttributedText.length) var attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(initialAttributedText)) - var resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection) + var resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed) text = resultAttributedText.string as NSString fullRange = NSRange(location: 0, length: initialAttributedText.length) attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(resultAttributedText)) refreshTextUrls(text: text, initialAttributedText: resultAttributedText, attributedText: attributedText, fullRange: fullRange) - resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection) + resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor, writingDirection: writingDirection, spoilersRevealed: spoilersRevealed) if !resultAttributedText.isEqual(to: initialAttributedText) { textNode.textView.textStorage.removeAttribute(NSAttributedString.Key.font, range: fullRange) diff --git a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift index 9ffa923f97..930eaaf914 100644 --- a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift +++ b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift @@ -196,6 +196,7 @@ public final class TextSelectionNode: ASDisplayNode { private let strings: PresentationStrings private let textNode: TextNode private let updateIsActive: (Bool) -> Void + public var updateRange: ((NSRange?) -> Void)? private let present: (ViewController, Any?) -> Void private weak var rootNode: ASDisplayNode? private let performAction: (NSAttributedString, TextSelectionAction) -> Void @@ -396,6 +397,8 @@ public final class TextSelectionNode: ASDisplayNode { } private func updateSelection(range: NSRange?, animateIn: Bool) { + self.updateRange?(range) + var rects: (rects: [CGRect], start: TextRangeRectEdge, end: TextRangeRectEdge)? if let range = range { @@ -502,18 +505,18 @@ public final class TextSelectionNode: ASDisplayNode { self?.performAction(attributedText, .lookup) self?.dismissSelection() })) -// if #available(iOS 15.0, *) { -// actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in -// self?.performAction(attributedText, .translate) -// self?.dismissSelection() -// })) -// } - if isSpeakSelectionEnabled() { - actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in - self?.performAction(attributedText, .speak) + if #available(iOS 15.0, *) { + actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in + self?.performAction(attributedText, .translate) self?.dismissSelection() })) } +// if isSpeakSelectionEnabled() { +// actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in +// self?.performAction(attributedText, .speak) +// self?.dismissSelection() +// })) +// } actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in self?.performAction(attributedText, .share) self?.dismissSelection()