From 8ddcc4cbcf40242efa7a3846454581afc3a9ebb1 Mon Sep 17 00:00:00 2001 From: bjoern Date: Wed, 18 Dec 2024 11:24:33 +0100 Subject: [PATCH] add option to copy links instead opening (#2445) * factor our gesture detection * get link text to copy * make linter happy * Update deltachat-ios/Chat/Views/MessageLabel.swift Co-authored-by: zeitschlag * Update deltachat-ios/Chat/Views/MessageLabel.swift Co-authored-by: zeitschlag * Update deltachat-ios/Chat/Views/MessageLabel.swift Co-authored-by: zeitschlag * Update deltachat-ios/Chat/Views/MessageLabel.swift Co-authored-by: zeitschlag * Update deltachat-ios/Chat/Views/MessageLabel.swift Co-authored-by: zeitschlag * Update deltachat-ios/Chat/Views/MessageLabel.swift Co-authored-by: zeitschlag * Update deltachat-ios/Chat/Views/MessageLabel.swift Co-authored-by: zeitschlag * add CHANGELOG entry --------- Co-authored-by: zeitschlag --- CHANGELOG.md | 2 + deltachat-ios/Chat/ChatViewController.swift | 17 +++++++- deltachat-ios/Chat/Views/MessageLabel.swift | 46 ++++++++++++++------- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 496269f84..97f17e967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased - Don't show message-input when forwarding (#2435) +- Long-tap links for copying to clipboard (#2445) + ## v1.50.3 diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index bd412610e..dc160c170 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -2021,6 +2021,15 @@ extension ChatViewController { menuElements.append(action) } + private func isLinkTapped(indexPath: IndexPath, point: CGPoint) -> String? { + if let cell = tableView.cellForRow(at: indexPath) as? BaseMessageCell { + let label = cell.messageLabel.label + let localTouchLocation = tableView.convert(point, to: label) + return label.getCopyableLinkText(localTouchLocation) + } + return nil + } + // context menu for iOS 13+ override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { let messageId = messageIds[indexPath.row] @@ -2067,7 +2076,13 @@ extension ChatViewController { UIAction.menuAction(localizationKey: "forward", image: image, indexPath: indexPath, action: { self.forward(at: $0 ) }) ) - if let text = message.text, !text.isEmpty { + if let link = isLinkTapped(indexPath: indexPath, point: point) { + children.append( + UIAction.menuAction(localizationKey: "menu_copy_link_to_clipboard", systemImageName: "link", indexPath: indexPath, action: { _ in + UIPasteboard.general.string = link + }) + ) + } else if let text = message.text, !text.isEmpty { let copyTitle = message.file == nil ? "global_menu_edit_copy_desktop" : "menu_copy_text_to_clipboard" children.append( UIAction.menuAction(localizationKey: copyTitle, systemImageName: "doc.on.doc", indexPath: indexPath, action: { self.copyToClipboard(at: $0 ) }) diff --git a/deltachat-ios/Chat/Views/MessageLabel.swift b/deltachat-ios/Chat/Views/MessageLabel.swift index f23956e40..181ab1826 100644 --- a/deltachat-ios/Chat/Views/MessageLabel.swift +++ b/deltachat-ios/Chat/Views/MessageLabel.swift @@ -452,49 +452,65 @@ open class MessageLabel: UILabel { } - open func handleGesture(_ touchLocation: CGPoint) -> Bool { - - guard let index = stringIndex(at: touchLocation) else { return false } + internal func detectLink(_ touchLocation: CGPoint) -> (DetectorType, NewMessageTextCheckingType)? { + guard let index = stringIndex(at: touchLocation) else { return nil } for (detectorType, ranges) in rangesForDetectors { for (range, value) in ranges { if range.contains(index) { - handleGesture(for: detectorType, value: value) - return true + return (detectorType, value) } } } - return false + + return nil } - /// swiftlint:disable cyclomatic_complexity - private func handleGesture(for detectorType: DetectorType, value: NewMessageTextCheckingType) { + internal func getCopyableLinkText(_ touchLocation: CGPoint) -> String? { + guard let (_, value) = detectLink(touchLocation) else { return nil } + + switch value { + case let .link(url): + guard let url else { return nil } + return url.absoluteString + case let .phoneNumber(phoneNumber): + return phoneNumber + case let .custom(_, match): + return match + case .addressComponents, .date, .transitInfoComponents: + return nil + } + } + + internal func handleGesture(_ touchLocation: CGPoint) -> Bool { + guard let (detectorType, value) = detectLink(touchLocation) else { return false } + switch value { case let .addressComponents(addressComponents): var transformedAddressComponents = [String: String]() - guard let addressComponents = addressComponents else { return } + guard let addressComponents else { return false } addressComponents.forEach { (key, value) in transformedAddressComponents[key.rawValue] = value } handleAddress(transformedAddressComponents) case let .phoneNumber(phoneNumber): - guard let phoneNumber = phoneNumber else { return } + guard let phoneNumber else { return false } handlePhoneNumber(phoneNumber) case let .date(date): - guard let date = date else { return } + guard let date else { return false } handleDate(date) case let .link(url): - guard let url = url else { return } + guard let url else { return false } handleURL(url) case let .transitInfoComponents(transitInformation): var transformedTransitInformation = [String: String]() - guard let transitInformation = transitInformation else { return } + guard let transitInformation else { return false } transitInformation.forEach { (key, value) in transformedTransitInformation[key.rawValue] = value } handleTransitInformation(transformedTransitInformation) case let .custom(pattern, match): - guard let match = match else { return } + guard let match else { return false } switch detectorType { case .hashtag: handleHashtag(match) @@ -506,6 +522,8 @@ open class MessageLabel: UILabel { handleCustom(pattern, match: match) } } + + return true } private func handleAddress(_ addressComponents: [String: String]) {