From f8e994c074e29852a576889c56a646f92fd9eecb Mon Sep 17 00:00:00 2001 From: "B. Petersen" <r10s@b44t.com> Date: Tue, 17 Dec 2024 23:53:38 +0100 Subject: [PATCH] get link text to copy --- deltachat-ios/Chat/ChatViewController.swift | 26 +++++---- deltachat-ios/Chat/Views/MessageLabel.swift | 64 ++++++++++++--------- 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index ecf2d6706..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] @@ -2028,15 +2037,6 @@ extension ChatViewController { return nil } - // Check if the long tap is on a link (or other message text element with custom long tap behavior) - if let msgcell = tableView.cellForRow(at: indexPath) as? BaseMessageCell { - let label = msgcell.messageLabel.label - let localTouchLocation = tableView.convert(point, to: label) - if let (detectorType, value) = label.detectGesture(localTouchLocation) { - print("url: \(detectorType) -- \(value) -- ") - } - } - return UIContextMenuConfiguration( identifier: NSString(string: "\(messageId)"), previewProvider: nil, @@ -2076,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 2f3617354..6ef0f9168 100644 --- a/deltachat-ios/Chat/Views/MessageLabel.swift +++ b/deltachat-ios/Chat/Views/MessageLabel.swift @@ -452,55 +452,65 @@ open class MessageLabel: UILabel { } - internal func detectGesture(_ 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) { - return (detectorType, value) - } - } - } - return nil - } - - open func handleGesture(_ touchLocation: CGPoint) -> Bool { - if let (detectorType, value) = detectGesture(touchLocation) { - handleGesture(for: detectorType, value: value) - return true + 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) { + return (detectorType, value) + } + } + } + + return nil + } + + internal func getCopyableLinkText(_ touchLocation: CGPoint) -> String? { + guard let (detectorType, value) = detectLink(touchLocation) else { return nil } + + switch value { + case let .link(url): + guard let url = url else { return nil } + return url.absoluteString + case let .phoneNumber(phoneNumber): + return phoneNumber + case let .custom(pattern, match): + return match + case .addressComponents, .date, .transitInfoComponents: + return nil } - return false } - /// swiftlint:disable cyclomatic_complexity - private func handleGesture(for detectorType: DetectorType, value: NewMessageTextCheckingType) { + 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 = 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 = phoneNumber else { return false } handlePhoneNumber(phoneNumber) case let .date(date): - guard let date = date else { return } + guard let date = date else { return false } handleDate(date) case let .link(url): - guard let url = url else { return } + guard let url = url else { return false } handleURL(url) case let .transitInfoComponents(transitInformation): var transformedTransitInformation = [String: String]() - guard let transitInformation = transitInformation else { return } + guard let transitInformation = 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 = match else { return false } switch detectorType { case .hashtag: handleHashtag(match) @@ -512,6 +522,8 @@ open class MessageLabel: UILabel { handleCustom(pattern, match: match) } } + + return true } private func handleAddress(_ addressComponents: [String: String]) {