From 261b088c1aa0136e7e8026fb8dec2419b0e68a63 Mon Sep 17 00:00:00 2001 From: Casper Zandbergen Date: Thu, 16 Jan 2025 12:47:38 +0100 Subject: [PATCH 1/4] clipper UIMenu --- deltachat-ios/Chat/ChatViewController.swift | 92 +++++++++++++-------- 1 file changed, 58 insertions(+), 34 deletions(-) diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index 82ec49a87..b078c2453 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -1168,6 +1168,10 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { $0.setSize(CGSize(width: 40, height: 40), animated: false) $0.accessibilityLabel = String.localized("menu_add_attachment") $0.accessibilityTraits = .button + if #available(iOS 14.0, *) { + $0.showsMenuAsPrimaryAction = true + $0.menu = clipperButtonMenu() + } }.onSelected { $0.tintColor = UIColor.themeColor(light: .lightGray, dark: .darkGray) }.onDeselected { @@ -1202,31 +1206,71 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { } } + @available(iOS 14, *) + private func clipperButtonMenu() -> UIMenu { + func action(localized: String, systemImage: String, attributes: UIMenuElement.Attributes = [], handler: @escaping () -> Void) -> UIAction { + UIAction(title: String.localized(localized), image: UIImage(systemName: systemImage), attributes: attributes, handler: { _ in handler() }) + } + var actions = [UIMenuElement]() + actions.append(action(localized: "camera", systemImage: "camera", handler: showCameraViewController)) + actions.append(action(localized: "gallery", systemImage: "photo.stack", handler: showPhotoVideoLibrary)) + actions.append(action(localized: "files", systemImage: "folder", handler: showDocumentLibrary)) + if dcContext.hasWebxdc() { + actions.append(action(localized: "webxdc_apps", systemImage: "app", handler: showWebxdcSelector)) + } + actions.append(action(localized: "voice_message", systemImage: "waveform", handler: showVoiceMessageRecorder)) + if let config = dcContext.getConfig("webrtc_instance"), !config.isEmpty { + // person.crop.square.badge.video + actions.append(action(localized: "videochat", systemImage: "video.bubble", handler: videoChatButtonPressed)) + } + if UserDefaults.standard.bool(forKey: "location_streaming") { + let isLocationStreaming = dcContext.isSendingLocationsToChat(chatId: chatId) + actions.append(action( + localized: isLocationStreaming ? "stop_sharing_location" : "location", + systemImage: isLocationStreaming ? "location.slash" : "location", + attributes: isLocationStreaming ? .destructive : [], + handler: locationStreamingButtonPressed + )) + } + actions.append(action(localized: "contact", systemImage: "person.crop.circle", handler: showContactList)) + + // Actions in this menu are reversed order vs iOS 13 because they are ordered + // by importance starting closest to the button + return UIMenu(children: actions) + } + + /// On iOS 13 we still use the action sheet but iOS 14+ has a UIMenu @objc private func clipperButtonPressed() { + guard #unavailable(iOS 14) else { return } + func action(localized: String, style: UIAlertAction.Style = .default, handler: @escaping () -> Void) -> UIAlertAction { + UIAlertAction(title: String.localized(localized), style: style, handler: { _ in handler() }) + } let alert = UIAlertController(title: nil, message: nil, preferredStyle: .safeActionSheet) - let galleryAction = PhotoPickerAlertAction(title: String.localized("gallery"), style: .default, handler: galleryButtonPressed(_:)) - let cameraAction = PhotoPickerAlertAction(title: String.localized("camera"), style: .default, handler: cameraButtonPressed(_:)) - let documentAction = UIAlertAction(title: String.localized("files"), style: .default, handler: documentActionPressed(_:)) - let voiceMessageAction = UIAlertAction(title: String.localized("voice_message"), style: .default, handler: voiceMessageButtonPressed(_:)) - let sendContactAction = UIAlertAction(title: String.localized("contact"), style: .default, handler: showContactList(_:)) + let galleryAction = action(localized: "gallery", handler: showPhotoVideoLibrary) + let cameraAction = action(localized: "camera", handler: showCameraViewController) + let documentAction = action(localized: "files", handler: showDocumentLibrary) + let voiceMessageAction = action(localized: "voice_message", handler: showVoiceMessageRecorder) + let sendContactAction = action(localized: "contact", handler: showContactList) let isLocationStreaming = dcContext.isSendingLocationsToChat(chatId: chatId) - let locationStreamingAction = UIAlertAction(title: isLocationStreaming ? String.localized("stop_sharing_location") : String.localized("location"), - style: isLocationStreaming ? .destructive : .default, - handler: locationStreamingButtonPressed(_:)) + let locationStreamingAction = action( + localized: isLocationStreaming ? "stop_sharing_location" : "location", + style: isLocationStreaming ? .destructive : .default, + handler: locationStreamingButtonPressed + ) alert.addAction(cameraAction) alert.addAction(galleryAction) alert.addAction(documentAction) if dcContext.hasWebxdc() { - let webxdcAction = UIAlertAction(title: String.localized("webxdc_apps"), style: .default, handler: webxdcButtonPressed(_:)) + let webxdcAction = action(localized: "webxdc_apps", handler: showWebxdcSelector) alert.addAction(webxdcAction) } alert.addAction(voiceMessageAction) if let config = dcContext.getConfig("webrtc_instance"), !config.isEmpty { - let videoChatInvitation = UIAlertAction(title: String.localized("videochat"), style: .default, handler: videoChatButtonPressed(_:)) + let videoChatInvitation = action(localized: "videochat", handler: videoChatButtonPressed) alert.addAction(videoChatInvitation) } @@ -1445,7 +1489,7 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { } } - private func showPhotoVideoLibrary(delegate: MediaPickerDelegate) { + private func showPhotoVideoLibrary() { mediaPicker?.showPhotoVideoLibrary() } @@ -1486,27 +1530,7 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { navigationController?.present(alert, animated: true, completion: nil) } - private func webxdcButtonPressed(_ action: UIAlertAction) { - showWebxdcSelector() - } - - private func documentActionPressed(_ action: UIAlertAction) { - showDocumentLibrary() - } - - private func voiceMessageButtonPressed(_ action: UIAlertAction) { - showVoiceMessageRecorder() - } - - private func cameraButtonPressed(_ action: UIAlertAction) { - showCameraViewController() - } - - private func galleryButtonPressed(_ action: UIAlertAction) { - showPhotoVideoLibrary(delegate: self) - } - - private func showContactList(_ action: UIAlertAction) { + private func showContactList() { let contactList = SendContactViewController(dcContext: dcContext) contactList.delegate = self @@ -1521,7 +1545,7 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { present(navigationController, animated: true) } - private func locationStreamingButtonPressed(_ action: UIAlertAction) { + private func locationStreamingButtonPressed() { let isLocationStreaming = dcContext.isSendingLocationsToChat(chatId: chatId) if isLocationStreaming { locationStreamingFor(seconds: 0) @@ -1537,7 +1561,7 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { } } - private func videoChatButtonPressed(_ action: UIAlertAction) { + private func videoChatButtonPressed() { let chat = dcContext.getChat(chatId: chatId) let alert = UIAlertController(title: String.localizedStringWithFormat(String.localized("videochat_invite_user_to_videochat"), chat.name), From b12856ce4b450fe5caec5e2bb97334ce52e5fe49 Mon Sep 17 00:00:00 2001 From: Casper Zandbergen Date: Thu, 16 Jan 2025 13:00:09 +0100 Subject: [PATCH 2/4] fallback sf symbols --- deltachat-ios/Chat/ChatViewController.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index b078c2453..0d41acb10 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -1213,15 +1213,16 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { } var actions = [UIMenuElement]() actions.append(action(localized: "camera", systemImage: "camera", handler: showCameraViewController)) - actions.append(action(localized: "gallery", systemImage: "photo.stack", handler: showPhotoVideoLibrary)) + let galleryImage = if #available(iOS 16, *) { "photo.stack" } else { "photo" } + actions.append(action(localized: "gallery", systemImage: galleryImage, handler: showPhotoVideoLibrary)) actions.append(action(localized: "files", systemImage: "folder", handler: showDocumentLibrary)) if dcContext.hasWebxdc() { actions.append(action(localized: "webxdc_apps", systemImage: "app", handler: showWebxdcSelector)) } actions.append(action(localized: "voice_message", systemImage: "waveform", handler: showVoiceMessageRecorder)) if let config = dcContext.getConfig("webrtc_instance"), !config.isEmpty { - // person.crop.square.badge.video - actions.append(action(localized: "videochat", systemImage: "video.bubble", handler: videoChatButtonPressed)) + let videoChatImage = if #available(iOS 17, *) { "video.bubble" } else { "video" } + actions.append(action(localized: "videochat", systemImage: videoChatImage, handler: videoChatButtonPressed)) } if UserDefaults.standard.bool(forKey: "location_streaming") { let isLocationStreaming = dcContext.isSendingLocationsToChat(chatId: chatId) From e00d09fcb4b030d07a28421feb387c64502476a0 Mon Sep 17 00:00:00 2001 From: Casper Zandbergen Date: Thu, 16 Jan 2025 13:36:20 +0100 Subject: [PATCH 3/4] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4df8ba3c5..c8934a11c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ ## Unreleased +- More modern attach menu (#2522) - Drag images from a chat and drop it to another chat or even to another app (use a second finger for navigating!) (#2509) - Paste .webp images via drag'n'drop (#2507) -- Add "Copy Image" to messages' conext menu +- Add "Copy Image" to messages' conext menu (#2508) - Long-tap a proxy to share or delete (#2521) - "Message Info" is moved to messages' context menu at "More Options / ..." (#2510) - Allow cancelling profile edits, force a name when editing profile (#2506) From cc421780955019edc62dfd0c7bc2547fb5ef047b Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 17 Jan 2025 15:11:46 +0100 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: bjoern --- deltachat-ios/Chat/ChatViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index bca1f9bb8..dda30103e 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -1217,9 +1217,9 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { actions.append(action(localized: "gallery", systemImage: galleryImage, handler: showPhotoVideoLibrary)) actions.append(action(localized: "files", systemImage: "folder", handler: showDocumentLibrary)) if dcContext.hasWebxdc() { - actions.append(action(localized: "webxdc_apps", systemImage: "app", handler: showWebxdcSelector)) + actions.append(action(localized: "webxdc_apps", systemImage: "square.grid.2x2", handler: showWebxdcSelector)) } - actions.append(action(localized: "voice_message", systemImage: "waveform", handler: showVoiceMessageRecorder)) + actions.append(action(localized: "voice_message", systemImage: "mic", handler: showVoiceMessageRecorder)) if let config = dcContext.getConfig("webrtc_instance"), !config.isEmpty { let videoChatImage = if #available(iOS 17, *) { "video.bubble" } else { "video" } actions.append(action(localized: "videochat", systemImage: videoChatImage, handler: videoChatButtonPressed))