From 202a7278161b1baf6f964971e6243e65d6e8d570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Mon, 27 Jan 2025 17:17:34 +0100 Subject: [PATCH] feat(copy): Allow copying link and selection of a message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- NextcloudTalk.xcodeproj/project.pbxproj | 8 ++ NextcloudTalk/BaseChatViewController.swift | 12 ++- NextcloudTalk/ChatViewController.swift | 75 ++++++++++++------- NextcloudTalk/MessageTextViewController.swift | 41 ++++++++++ NextcloudTalk/MessageTextViewController.xib | 55 ++++++++++++++ NextcloudTalk/en.lproj/Localizable.strings | 15 ++++ 6 files changed, 179 insertions(+), 27 deletions(-) create mode 100644 NextcloudTalk/MessageTextViewController.swift create mode 100644 NextcloudTalk/MessageTextViewController.xib diff --git a/NextcloudTalk.xcodeproj/project.pbxproj b/NextcloudTalk.xcodeproj/project.pbxproj index 284544830..7a10c60f2 100644 --- a/NextcloudTalk.xcodeproj/project.pbxproj +++ b/NextcloudTalk.xcodeproj/project.pbxproj @@ -124,6 +124,8 @@ 1F5499202D35B07700E9AA9E /* ButtonContainerSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F54991F2D35B07700E9AA9E /* ButtonContainerSwiftUI.swift */; }; 1F549B662D3995C600E9AA9E /* UserSelectionSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F549B652D3995C600E9AA9E /* UserSelectionSwiftUIView.swift */; }; 1F549B692D3A9AA500E9AA9E /* DebouncedOnChange in Frameworks */ = {isa = PBXBuildFile; productRef = 1F549B682D3A9AA500E9AA9E /* DebouncedOnChange */; }; + 1F549E2B2D45695C00E9AA9E /* MessageTextViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1F549E2A2D45695C00E9AA9E /* MessageTextViewController.xib */; }; + 1F549E2D2D45695D00E9AA9E /* MessageTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F549E2C2D45695D00E9AA9E /* MessageTextViewController.swift */; }; 1F5683CF2BA7980C0023E151 /* FilePreviewImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5683CE2BA7980C0023E151 /* FilePreviewImageView.swift */; }; 1F5813F828EB23EF00318FC3 /* NCSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5813F628EB23EF00318FC3 /* NCSplitViewController.swift */; }; 1F5813F928EB23EF00318FC3 /* NCSplitViewPlaceholderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5813F728EB23EF00318FC3 /* NCSplitViewPlaceholderViewController.swift */; }; @@ -743,6 +745,8 @@ 1F54991D2D346F9700E9AA9E /* UserStatusAbsenceSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserStatusAbsenceSwiftUIView.swift; sourceTree = ""; }; 1F54991F2D35B07700E9AA9E /* ButtonContainerSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonContainerSwiftUI.swift; sourceTree = ""; }; 1F549B652D3995C600E9AA9E /* UserSelectionSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSelectionSwiftUIView.swift; sourceTree = ""; }; + 1F549E2A2D45695C00E9AA9E /* MessageTextViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MessageTextViewController.xib; sourceTree = ""; }; + 1F549E2C2D45695D00E9AA9E /* MessageTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTextViewController.swift; sourceTree = ""; }; 1F5683CE2BA7980C0023E151 /* FilePreviewImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewImageView.swift; sourceTree = ""; }; 1F5813F628EB23EF00318FC3 /* NCSplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCSplitViewController.swift; sourceTree = ""; }; 1F5813F728EB23EF00318FC3 /* NCSplitViewPlaceholderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCSplitViewPlaceholderViewController.swift; sourceTree = ""; }; @@ -1985,6 +1989,8 @@ C65D252C2C7581A200157A89 /* ExpandedVoiceMessageRecordingView.swift */, 1F205C562CEFA01900AAA673 /* OutOfOfficeView.swift */, 1F205C542CEFA01200AAA673 /* OutOfOfficeView.xib */, + 1F549E2C2D45695D00E9AA9E /* MessageTextViewController.swift */, + 1F549E2A2D45695C00E9AA9E /* MessageTextViewController.xib */, ); name = "Chat views"; sourceTree = ""; @@ -2566,6 +2572,7 @@ 2C440D1220EA4A770005F9BB /* RoomInfoTableViewController.xib in Resources */, 2C1D13A3253760EE00EC0533 /* LaunchScreen.xib in Resources */, 1FB7B99C2BF0DF360093CE98 /* BannedActorCell.xib in Resources */, + 1F549E2B2D45695C00E9AA9E /* MessageTextViewController.xib in Resources */, 2C4747E22CB58FD2002828F2 /* PollMessageView.xib in Resources */, 2CA1CCAC1F067F35002FE6A2 /* Images.xcassets in Resources */, 2CA1CCD71F1E664C002FE6A2 /* ContactsTableViewCell.xib in Resources */, @@ -2921,6 +2928,7 @@ 2C5BFBEF288A947900E75118 /* PollVotingView.swift in Sources */, 1FAB2EF02AD1EAA3001214EB /* RLMSupport.swift in Sources */, 1F1B50342B8E069800B0F2F4 /* BaseChatTableViewCell.swift in Sources */, + 1F549E2D2D45695D00E9AA9E /* MessageTextViewController.swift in Sources */, 2C1EF36B25505DCE007C9768 /* NCNavigationController.m in Sources */, 1FA38C9029A4B3C6008871B8 /* NCNotificationAction.swift in Sources */, 2C44B4D127FF05A000AD1C86 /* ReactionsSummaryView.swift in Sources */, diff --git a/NextcloudTalk/BaseChatViewController.swift b/NextcloudTalk/BaseChatViewController.swift index f61d44d3c..632895fdf 100644 --- a/NextcloudTalk/BaseChatViewController.swift +++ b/NextcloudTalk/BaseChatViewController.swift @@ -1055,6 +1055,11 @@ import SwiftUI NotificationPresenter.shared().present(text: NSLocalizedString("Message link copied", comment: ""), dismissAfterDelay: 5.0, includedStyle: .dark) } + func didPressCopySelection(for message: NCChatMessage) { + let vc = MessageTextViewController(messageText: message.parsedMessage().string) + self.presentWithNavigation(vc, animated: true) + } + func didPressTranslate(for message: NCChatMessage) { let translateMessageVC = MessageTranslationViewController(message: message.parsedMessage().string, availableTranslations: NCDatabaseManager.sharedInstance().availableTranslations(forAccountId: self.room.accountId)) self.presentWithNavigation(translateMessageVC, animated: true) @@ -3070,10 +3075,15 @@ import SwiftUI var actions: [UIMenuElement] = [] // Copy option - actions.append(UIAction(title: NSLocalizedString("Copy", comment: ""), image: .init(systemName: "square.on.square")) { _ in + actions.append(UIAction(title: NSLocalizedString("Copy", comment: ""), image: .init(systemName: "doc.on.doc")) { _ in self.didPressCopy(for: message) }) + // Copy Selection + actions.append(UIAction(title: NSLocalizedString("Copy message selection", comment: ""), image: .init(systemName: "text.viewfinder")) { _ in + self.didPressCopySelection(for: message) + }) + // Copy Link actions.append(UIAction(title: NSLocalizedString("Copy message link", comment: ""), image: .init(systemName: "link")) { _ in self.didPressCopyLink(for: message) diff --git a/NextcloudTalk/ChatViewController.swift b/NextcloudTalk/ChatViewController.swift index 6cfd4089d..484f7c81a 100644 --- a/NextcloudTalk/ChatViewController.swift +++ b/NextcloudTalk/ChatViewController.swift @@ -1779,13 +1779,6 @@ import SwiftyAttributes }) } - // Reply-privately option (only to other users and not in one-to-one) - if self.isMessageReplyable(message: message), self.room.type != .oneToOne, message.actorType == "users", message.actorId != self.account.userId { - actions.append(UIAction(title: NSLocalizedString("Reply privately", comment: ""), image: .init(systemName: "person")) { _ in - self.didPressReplyPrivately(for: message) - }) - } - // Forward option (only normal messages for now) if message.file() == nil, message.poll == nil, !message.isDeletedMessage { actions.append(UIAction(title: NSLocalizedString("Forward", comment: ""), image: .init(systemName: "arrowshape.turn.up.right")) { _ in @@ -1793,13 +1786,24 @@ import SwiftyAttributes }) } - // Note to self - if message.file() == nil, message.poll == nil, !message.isDeletedMessage, room.type != .noteToSelf, - NCDatabaseManager.sharedInstance().roomHasTalkCapability(kCapabilityNoteToSelf, for: room) { - actions.append(UIAction(title: NSLocalizedString("Note to self", comment: ""), image: .init(systemName: "square.and.pencil")) { _ in - self.didPressNoteToSelf(for: message) - }) - } + var copyMenuActions: [UIMenuElement] = [] + + // Copy option + copyMenuActions.append(UIAction(title: NSLocalizedString("Message", comment: "Copy 'message'"), image: .init(systemName: "doc.text")) { _ in + self.didPressCopy(for: message) + }) + + // Copy part option + copyMenuActions.append(UIAction(title: NSLocalizedString("Selection", comment: "Copy a 'selection' of a message"), image: .init(systemName: "text.viewfinder")) { _ in + self.didPressCopySelection(for: message) + }) + + // Copy link option + copyMenuActions.append(UIAction(title: NSLocalizedString("Message link", comment: "Copy 'link' to a message"), image: .init(systemName: "link")) { _ in + self.didPressCopyLink(for: message) + }) + + actions.append(UIMenu(title: NSLocalizedString("Copy", comment: ""), image: .init(systemName: "doc.on.doc"), children: copyMenuActions)) // Remind me later if !message.sendingFailed, !message.isOfflineMessage, NCDatabaseManager.sharedInstance().roomHasTalkCapability(kCapabilityRemindMeLater, for: room) { @@ -1849,18 +1853,6 @@ import SwiftyAttributes }) } - // Copy option - actions.append(UIAction(title: NSLocalizedString("Copy", comment: ""), image: .init(systemName: "square.on.square")) { _ in - self.didPressCopy(for: message) - }) - - // Translate - if !self.offlineMode, NCDatabaseManager.sharedInstance().hasAvailableTranslations(forAccountId: self.account.accountId) { - actions.append(UIAction(title: NSLocalizedString("Translate", comment: ""), image: .init(systemName: "character.book.closed")) { _ in - self.didPressTranslate(for: message) - }) - } - // Open in nextcloud option if !self.offlineMode, message.file() != nil { let openInNextcloudTitle = String(format: NSLocalizedString("Open in %@", comment: ""), filesAppName) @@ -1877,6 +1869,37 @@ import SwiftyAttributes }) } + var moreMenuActions: [UIMenuElement] = [] + + // Reply-privately option (only to other users and not in one-to-one) + if self.isMessageReplyable(message: message), self.room.type != .oneToOne, message.actorType == "users", message.actorId != self.account.userId { + moreMenuActions.append(UIAction(title: NSLocalizedString("Reply privately", comment: ""), image: .init(systemName: "person")) { _ in + self.didPressReplyPrivately(for: message) + }) + } + + // Translate + if !self.offlineMode, NCDatabaseManager.sharedInstance().hasAvailableTranslations(forAccountId: self.account.accountId) { + moreMenuActions.append(UIAction(title: NSLocalizedString("Translate", comment: ""), image: .init(systemName: "character.book.closed")) { _ in + self.didPressTranslate(for: message) + }) + } + + // Note to self + if message.file() == nil, message.poll == nil, !message.isDeletedMessage, room.type != .noteToSelf, + NCDatabaseManager.sharedInstance().roomHasTalkCapability(kCapabilityNoteToSelf, for: room) { + moreMenuActions.append(UIAction(title: NSLocalizedString("Note to self", comment: ""), image: .init(systemName: "square.and.pencil")) { _ in + self.didPressNoteToSelf(for: message) + }) + } + + if moreMenuActions.count == 1, let firstElement = moreMenuActions.first { + // When there's only one element, no need to create a "More" menu + actions.append(firstElement) + } else if !moreMenuActions.isEmpty { + actions.append(UIMenu(title: NSLocalizedString("More", comment: "More menu elements"), children: moreMenuActions)) + } + var destructiveMenuActions: [UIMenuElement] = [] // Edit option diff --git a/NextcloudTalk/MessageTextViewController.swift b/NextcloudTalk/MessageTextViewController.swift new file mode 100644 index 000000000..869b5be25 --- /dev/null +++ b/NextcloudTalk/MessageTextViewController.swift @@ -0,0 +1,41 @@ +// +// SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: GPL-3.0-or-later +// + +import UIKit +import Foundation +import SwiftyAttributes + +@objcMembers class MessageTextViewController: UIViewController { + + @IBOutlet public weak var messageTextView: UITextView! + + private var messageText = "" + + init(messageText: String) { + super.init(nibName: "MessageTextViewController", bundle: nil) + + self.messageText = messageText + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func viewDidLoad() { + super.viewDidLoad() + + NCAppBranding.styleViewController(self) + + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("Close", comment: ""), primaryAction: UIAction { [unowned self] _ in + self.dismiss(animated: true) + }) + + self.messageTextView.layer.cornerRadius = 8 + self.messageTextView.layer.masksToBounds = true + self.messageTextView.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + self.messageTextView.text = messageText + } + +} diff --git a/NextcloudTalk/MessageTextViewController.xib b/NextcloudTalk/MessageTextViewController.xib new file mode 100644 index 000000000..53917b64b --- /dev/null +++ b/NextcloudTalk/MessageTextViewController.xib @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NextcloudTalk/en.lproj/Localizable.strings b/NextcloudTalk/en.lproj/Localizable.strings index 6654d8e4d..e787768e2 100644 --- a/NextcloudTalk/en.lproj/Localizable.strings +++ b/NextcloudTalk/en.lproj/Localizable.strings @@ -541,6 +541,9 @@ /* No comment provided by engineer. */ "Copy message link" = "Copy message link"; +/* No comment provided by engineer. */ +"Copy message selection" = "Copy message selection"; + /* No comment provided by engineer. */ "Could not access camera" = "Could not access camera"; @@ -1174,6 +1177,9 @@ /* 'Mentioned' meaning 'Mentioned conversations' */ "Mentioned" = "Mentioned"; +/* Copy 'message' */ +"Message" = "Message"; + /* No comment provided by engineer. */ "Message copied" = "Message copied"; @@ -1192,6 +1198,9 @@ /* No comment provided by engineer. */ "Message expiration time" = "Message expiration time"; +/* Copy 'link' to a message */ +"Message link" = "Message link"; + /* No comment provided by engineer. */ "Message link copied" = "Message link copied"; @@ -1231,6 +1240,9 @@ /* No comment provided by engineer. */ "Modification date" = "Modification date"; +/* More menu elements */ +"More" = "More"; + /* No comment provided by engineer. */ "More actions" = "More actions"; @@ -1621,6 +1633,9 @@ /* No comment provided by engineer. */ "Select language" = "Select language"; +/* Copy a 'selection' of a message */ +"Selection" = "Selection"; + /* No comment provided by engineer. */ "Send" = "Send";