From 604f5a272f4b5dd319f564611b0478091479f1fe Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 15 Sep 2022 00:28:16 +0300 Subject: [PATCH] Fix text format menu on iOS 16 --- .../Telegram-iOS/en.lproj/Localizable.strings | 1 + .../Source/ASEditableTextNode.mm | 8 ++ .../AsyncDisplayKit/ASEditableTextNode.h | 3 + .../AttachmentTextInputPanelNode.swift | 69 +++++++++++++++-- .../Sources/CreatePollTextInputItem.swift | 77 +++++++++++++++++-- .../Sources/ChatTextInputPanelNode.swift | 69 +++++++++++++++-- 6 files changed, 209 insertions(+), 18 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 93ac3ed360..ff971f1ef1 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8079,3 +8079,4 @@ Sorry for the inconvenience."; "PeerInfo.LabelAllReactions" = "All Reactions"; +"TextFormat.Format" = "Format"; diff --git a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm b/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm index b9a977c80e..9dbdff9cee 100644 --- a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm +++ b/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm @@ -1203,4 +1203,12 @@ return 0; } +- (UIMenu *)textView:(UITextView *)textView editMenuForTextInRange:(NSRange)range suggestedActions:(NSArray *)suggestedActions API_AVAILABLE(ios(16.0)) { + if ([_delegate respondsToSelector:@selector(editableTextNodeMenu:forTextRange:suggestedActions:)]) { + return [_delegate editableTextNodeMenu:self forTextRange:range suggestedActions:suggestedActions]; + } else { + return [UIMenu menuWithChildren:suggestedActions]; + } +} + @end diff --git a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASEditableTextNode.h b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASEditableTextNode.h index b30969e3f1..3dd542d7af 100644 --- a/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASEditableTextNode.h +++ b/submodules/AsyncDisplayKit/Source/PublicHeaders/AsyncDisplayKit/ASEditableTextNode.h @@ -221,6 +221,9 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)editableTextNodeShouldReturn:(ASEditableTextNode *)editableTextNode; - (void)editableTextNodeBackspaceWhileEmpty:(ASEditableTextNode *)editableTextNode; + +- (UIMenu *)editableTextNodeMenu:(ASEditableTextNode *)editableTextNode forTextRange:(NSRange)textRange suggestedActions:(NSArray *)suggestedActions API_AVAILABLE(ios(16.0)); + @end NS_ASSUME_NONNULL_END diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index 23beb07c7a..22e51eaca6 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -1422,13 +1422,17 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS else if action == makeSelectorFromString("_accessibilitySpeakLanguageSelection:") || action == makeSelectorFromString("_accessibilityPauseSpeaking:") || action == makeSelectorFromString("_accessibilitySpeakSentence:") { return ASEditableTextNodeTargetForAction(target: nil) } else if action == makeSelectorFromString("_showTextStyleOptions:") { - if case .general = self.inputMenu.state { - if let textInputNode = self.textInputNode, textInputNode.attributedText == nil || textInputNode.attributedText!.length == 0 || textInputNode.selectedRange.length == 0 { + if #available(iOS 16.0, *) { + return ASEditableTextNodeTargetForAction(target: nil) + } else { + if case .general = self.inputMenu.state { + if let textInputNode = self.textInputNode, textInputNode.attributedText == nil || textInputNode.attributedText!.length == 0 || textInputNode.selectedRange.length == 0 { + return ASEditableTextNodeTargetForAction(target: nil) + } + return ASEditableTextNodeTargetForAction(target: self) + } else { return ASEditableTextNodeTargetForAction(target: nil) } - return ASEditableTextNodeTargetForAction(target: self) - } else { - return ASEditableTextNodeTargetForAction(target: nil) } } else if action == #selector(self.formatAttributesBold(_:)) || action == #selector(self.formatAttributesItalic(_:)) || action == #selector(self.formatAttributesMonospace(_:)) || action == #selector(self.formatAttributesLink(_:)) || action == #selector(self.formatAttributesStrikethrough(_:)) || action == #selector(self.formatAttributesUnderline(_:)) || action == #selector(self.formatAttributesSpoiler(_:)) { if case .format = self.inputMenu.state { @@ -1443,6 +1447,61 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS return nil } + @available(iOS 16.0, *) + public func editableTextNodeMenu(_ editableTextNode: ASEditableTextNode, forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu { + var actions = suggestedActions + + var children: [UIAction] = [ + UIAction(title: self.strings?.TextFormat_Bold ?? "Bold", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesBold(strongSelf) + } + }, + UIAction(title: self.strings?.TextFormat_Italic ?? "Italic", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesItalic(strongSelf) + } + }, + UIAction(title: self.strings?.TextFormat_Monospace ?? "Monospace", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesMonospace(strongSelf) + } + }, + UIAction(title: self.strings?.TextFormat_Link ?? "Link", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesLink(strongSelf) + } + }, + UIAction(title: self.strings?.TextFormat_Strikethrough ?? "Strikethrough", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesStrikethrough(strongSelf) + } + }, + UIAction(title: self.strings?.TextFormat_Underline ?? "Underline", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesUnderline(strongSelf) + } + } + ] + + var hasSpoilers = true + if self.presentationInterfaceState?.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat { + hasSpoilers = false + } + + if hasSpoilers { + children.append(UIAction(title: self.strings?.TextFormat_Spoiler ?? "Spoiler", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesSpoiler(strongSelf) + } + }) + } + + let formatMenu = UIMenu(title: self.strings?.TextFormat_Format ?? "Format", image: nil, children: children) + actions.insert(formatMenu, at: 3) + return UIMenu(children: actions) + } + @objc func _accessibilitySpeak(_ sender: Any) { var text = "" self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in diff --git a/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift b/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift index 20481f84aa..0f6dc11345 100644 --- a/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift +++ b/submodules/ComposePollUI/Sources/CreatePollTextInputItem.swift @@ -426,14 +426,18 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe public func editableTextNodeTarget(forAction action: Selector) -> ASEditableTextNodeTargetForAction? { if action == makeSelectorFromString("_showTextStyleOptions:") { - if case .general = self.inputMenu.state { - if self.textNode.attributedText == nil || self.textNode.attributedText!.length == 0 || self.textNode.selectedRange.length == 0 { - return ASEditableTextNodeTargetForAction(target: nil) - } - return ASEditableTextNodeTargetForAction(target: self) - } else { - return ASEditableTextNodeTargetForAction(target: nil) - } + if #available(iOS 16.0, *) { + return ASEditableTextNodeTargetForAction(target: nil) + } else { + if case .general = self.inputMenu.state { + if self.textNode.attributedText == nil || self.textNode.attributedText!.length == 0 || self.textNode.selectedRange.length == 0 { + return ASEditableTextNodeTargetForAction(target: nil) + } + return ASEditableTextNodeTargetForAction(target: self) + } else { + return ASEditableTextNodeTargetForAction(target: nil) + } + } } else if action == #selector(self.formatAttributesBold(_:)) || action == #selector(self.formatAttributesItalic(_:)) || action == #selector(self.formatAttributesMonospace(_:)) || action == #selector(self.formatAttributesLink(_:)) || action == #selector(self.formatAttributesStrikethrough(_:)) || action == #selector(self.formatAttributesUnderline(_:)) { if case .format = self.inputMenu.state { return ASEditableTextNodeTargetForAction(target: self) @@ -451,6 +455,56 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe self.inputMenu.format(view: self.textNode.view, rect: self.textNode.selectionRect.offsetBy(dx: 0.0, dy: -self.textNode.textView.contentOffset.y).insetBy(dx: 0.0, dy: -1.0)) } + @available(iOS 16.0, *) + public func editableTextNodeMenu(_ editableTextNode: ASEditableTextNode, forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu { + var actions = suggestedActions + + if let strings = self.item?.presentationData.strings { + let children: [UIAction] = [ + UIAction(title: strings.TextFormat_Bold, image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesBold(strongSelf) + } + }, + UIAction(title: strings.TextFormat_Italic, image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesItalic(strongSelf) + } + }, + UIAction(title: strings.TextFormat_Monospace, image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesMonospace(strongSelf) + } + }, + UIAction(title: strings.TextFormat_Link, image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesLink(strongSelf) + } + }, + UIAction(title: strings.TextFormat_Strikethrough, image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesStrikethrough(strongSelf) + } + }, + UIAction(title: strings.TextFormat_Underline, image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesUnderline(strongSelf) + } + }, + UIAction(title: strings.TextFormat_Spoiler, image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesSpoiler(strongSelf) + } + } + ] + + let formatMenu = UIMenu(title: strings.TextFormat_Format, image: nil, children: children) + actions.insert(formatMenu, at: 3) + } + + return UIMenu(children: actions) + } + @objc func formatAttributesBold(_ sender: Any) { self.inputMenu.back() if let item = self.item { @@ -491,6 +545,13 @@ public class CreatePollTextInputItemNode: ListViewItemNode, ASEditableTextNodeDe } } + @objc func formatAttributesSpoiler(_ sender: Any) { + self.inputMenu.back() + if let item = self.item { + chatTextInputAddFormattingAttribute(item: item, textNode: self.textNode, theme: item.presentationData.theme, attribute: ChatTextInputAttributes.spoiler) + } + } + public func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { if let item = self.item { if text.count > 1, let processPaste = item.processPaste { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 5e9e72e469..7d88bcb22c 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -2997,13 +2997,17 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { else if action == makeSelectorFromString("_accessibilitySpeakLanguageSelection:") || action == makeSelectorFromString("_accessibilityPauseSpeaking:") || action == makeSelectorFromString("_accessibilitySpeakSentence:") { return ASEditableTextNodeTargetForAction(target: nil) } else if action == makeSelectorFromString("_showTextStyleOptions:") { - if case .general = self.inputMenu.state { - if let textInputNode = self.textInputNode, textInputNode.attributedText == nil || textInputNode.attributedText!.length == 0 || textInputNode.selectedRange.length == 0 { + if #available(iOS 16.0, *) { + return ASEditableTextNodeTargetForAction(target: nil) + } else { + if case .general = self.inputMenu.state { + if let textInputNode = self.textInputNode, textInputNode.attributedText == nil || textInputNode.attributedText!.length == 0 || textInputNode.selectedRange.length == 0 { + return ASEditableTextNodeTargetForAction(target: nil) + } + return ASEditableTextNodeTargetForAction(target: self) + } else { return ASEditableTextNodeTargetForAction(target: nil) } - return ASEditableTextNodeTargetForAction(target: self) - } else { - return ASEditableTextNodeTargetForAction(target: nil) } } else if action == #selector(self.formatAttributesBold(_:)) || action == #selector(self.formatAttributesItalic(_:)) || action == #selector(self.formatAttributesMonospace(_:)) || action == #selector(self.formatAttributesLink(_:)) || action == #selector(self.formatAttributesStrikethrough(_:)) || action == #selector(self.formatAttributesUnderline(_:)) || action == #selector(self.formatAttributesSpoiler(_:)) { if case .format = self.inputMenu.state { @@ -3044,6 +3048,61 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { return nil } + @available(iOS 16.0, *) + func editableTextNodeMenu(_ editableTextNode: ASEditableTextNode, forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu { + var actions = suggestedActions + + var children: [UIAction] = [ + UIAction(title: self.strings?.TextFormat_Bold ?? "Bold", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesBold(strongSelf) + } + }, + UIAction(title: self.strings?.TextFormat_Italic ?? "Italic", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesItalic(strongSelf) + } + }, + UIAction(title: self.strings?.TextFormat_Monospace ?? "Monospace", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesMonospace(strongSelf) + } + }, + UIAction(title: self.strings?.TextFormat_Link ?? "Link", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesLink(strongSelf) + } + }, + UIAction(title: self.strings?.TextFormat_Strikethrough ?? "Strikethrough", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesStrikethrough(strongSelf) + } + }, + UIAction(title: self.strings?.TextFormat_Underline ?? "Underline", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesUnderline(strongSelf) + } + } + ] + + var hasSpoilers = true + if self.presentationInterfaceState?.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat { + hasSpoilers = false + } + + if hasSpoilers { + children.append(UIAction(title: self.strings?.TextFormat_Spoiler ?? "Spoiler", image: nil) { [weak self] (action) in + if let strongSelf = self { + strongSelf.formatAttributesSpoiler(strongSelf) + } + }) + } + + let formatMenu = UIMenu(title: self.strings?.TextFormat_Format ?? "Format", image: nil, children: children) + actions.insert(formatMenu, at: 3) + return UIMenu(children: actions) + } + @objc func _accessibilitySpeak(_ sender: Any) { var text = "" self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in