From b07f197e2cb97c29a3dc3ac511f285c2fc6ac05b Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 4 Apr 2024 13:38:56 +0200 Subject: [PATCH] cleanup and improve chat's context menu - add functions canReply() and canReplyPrivately() for consistency and to cleanup code - if possible, show the reaction options in the first row; as this is limited to 4 options, we have now three default reactions plus "..." there. more default reactions will go to the reaction picker, see #2112; this closes #2113 - "buerocratic, dangerous" options go to "more". this makes the menu smaller and avoids scrolling as it happens even on larger screens. - as a side-effect, iOS 12 menu is cleaned up as well --- deltachat-ios/Chat/ChatViewController.swift | 156 +++++------------- .../Chat/Send Reaction/DefaultReactions.swift | 4 - 2 files changed, 41 insertions(+), 119 deletions(-) diff --git a/deltachat-ios/Chat/ChatViewController.swift b/deltachat-ios/Chat/ChatViewController.swift index a3313c815..32d4e0625 100644 --- a/deltachat-ios/Chat/ChatViewController.swift +++ b/deltachat-ios/Chat/ChatViewController.swift @@ -756,12 +756,8 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let messageId = messageIds[indexPath.row] - if messageId == DC_MSG_ID_MARKER1 || messageId == DC_MSG_ID_DAYMARKER || !dcChat.canSend { - return nil - } - let message = dcContext.getMessage(id: messageId) - if message.isInfo || message.type == DC_MSG_VIDEOCHAT_INVITATION { + if !canReply(message: message) { return nil } @@ -984,6 +980,14 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { self.reloadData() } + private func canReply(message: DcMsg) -> Bool { + return message.id != DC_MSG_ID_MARKER1 && message.id != DC_MSG_ID_DAYMARKER && !message.isInfo && message.type != DC_MSG_VIDEOCHAT_INVITATION && dcChat.canSend + } + + private func canReplyPrivately(message: DcMsg) -> Bool { + return dcChat.isGroup && !message.isFromCurrentSender + } + private func isLastRowScrolledToBottom() -> Bool { return isLastRowVisible(checkTopCellPostion: false, checkBottomCellPosition: true) } @@ -1693,18 +1697,6 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { // MARK: - Actions - @objc private func copyMessage(_ sender: Any) { - guard let menuItem = UIMenuController.shared.menuItems?.first as? LegacyMenuItem, - let indexPath = menuItem.indexPath else { return } - - copyMessage(at: indexPath) - } - - private func copyMessage(at indexPath: IndexPath) { - let id = self.messageIds[indexPath.row] - self.copyToClipboard(ids: [id]) - } - @objc private func info(_ sender: Any) { guard let menuItem = UIMenuController.shared.menuItems?.first as? LegacyMenuItem, let indexPath = menuItem.indexPath else { return } @@ -1720,19 +1712,6 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { } } - @objc private func deleteMessage(_ sender: Any) { - guard let menuItem = UIMenuController.shared.menuItems?.first as? LegacyMenuItem, - let indexPath = menuItem.indexPath else { return } - - deleteMessage(at: indexPath) - } - - private func deleteMessage(at indexPath: IndexPath) { - becomeFirstResponder() - let msg = dcContext.getMessage(id: messageIds[indexPath.row]) - askToDeleteMessage(id: msg.id) - } - @objc private func forward(_ sender: Any) { guard let menuItem = UIMenuController.shared.menuItems?.first as? LegacyMenuItem, let indexPath = menuItem.indexPath else { return } @@ -1804,43 +1783,19 @@ class ChatViewController: UITableViewController, UITableViewDropDelegate { var menu: [LegacyMenuItem] = [] - let showReaction = message.isInfo == false && message.isSetupMessage == false && message.type != DC_MSG_VIDEOCHAT_INVITATION && dcChat.canSend - if showReaction { + if canReply(message: message) { menu.append(LegacyMenuItem(title: String.localized("react"), action: #selector(ChatViewController.react(_:)), indexPath: indexPath)) + menu.append(LegacyMenuItem(title: String.localized("notify_reply_button"), action: #selector(ChatViewController.reply(_:)), indexPath: indexPath)) } - let messageIsFromMe = message.isFromCurrentSender - - let replyMenuEntries: [LegacyMenuItem] - if message.isInfo { - replyMenuEntries = [] - } else if dcChat.canSend && self.isGroupChat && messageIsFromMe == false { - replyMenuEntries = [ - LegacyMenuItem(title: String.localized("notify_reply_button"), action: #selector(ChatViewController.reply(_:)), indexPath: indexPath), - LegacyMenuItem(title: String.localized("reply_privately"), action: #selector(ChatViewController.replyPrivately(_:)), indexPath: indexPath), - ] - } else if self.isGroupChat && messageIsFromMe == false { - replyMenuEntries = [ - LegacyMenuItem(title: String.localized("reply_privately"), action: #selector(ChatViewController.replyPrivately(_:)), indexPath: indexPath), - ] - } else if dcChat.canSend { - replyMenuEntries = [ - LegacyMenuItem(title: String.localized("notify_reply_button"), action: #selector(ChatViewController.reply(_:)), indexPath: indexPath), - ] - } else { - replyMenuEntries = [] - } - - if replyMenuEntries.isEmpty == false { - menu.append(contentsOf: replyMenuEntries) + if canReplyPrivately(message: message) { + menu.append(LegacyMenuItem(title: String.localized("reply_privately"), action: #selector(ChatViewController.replyPrivately(_:)), indexPath: indexPath)) } menu.append(contentsOf: [ LegacyMenuItem(title: String.localized("forward"), action: #selector(ChatViewController.forward(_:)), indexPath: indexPath), LegacyMenuItem(title: String.localized("info"), action: #selector(ChatViewController.info(_:)), indexPath: indexPath), - LegacyMenuItem(title: String.localized("global_menu_edit_copy_desktop"), action: #selector(ChatViewController.copyMessage(_:)), indexPath: indexPath), - LegacyMenuItem(title: String.localized("delete"), action: #selector(ChatViewController.deleteMessage(_:)), indexPath: indexPath), - LegacyMenuItem(title: String.localized("select_more"), action: #selector(ChatViewController.selectMore(_:)), indexPath: indexPath) + LegacyMenuItem(title: String.localized("menu_more_options"), action: #selector(ChatViewController.selectMore(_:)), indexPath: indexPath) ]) return menu @@ -1974,21 +1929,14 @@ extension ChatViewController { return preview } - private func reactionsMenu(indexPath: IndexPath) -> UIMenu { - + private func reactionsMenuItems(appendTo: inout [UIMenuElement], indexPath: IndexPath) { let messageId = messageIds[indexPath.row] let myReactions = getMyReactions(messageId: messageId) - var reactionsMenuItems = DefaultReactions.allCases.map { reaction in + for reaction in DefaultReactions.allCases { let sentThisReaction = myReactions.contains(where: { $0 == reaction.emoji }) - let selectedImage: UIImage? - if sentThisReaction { - selectedImage = UIImage(systemName: "checkmark") - } else { - selectedImage = nil - } - - return UIAction(title: reaction.emoji, image: selectedImage) { [weak self] _ in + let title = sentThisReaction ? (reaction.emoji + "✓") : reaction.emoji + appendTo.append(UIAction(title: title) { [weak self] _ in guard let self else { return } let messageId = self.messageIds[indexPath.row] @@ -1997,10 +1945,10 @@ extension ChatViewController { } else { dcContext.sendReaction(messageId: messageId, reaction: reaction.emoji) } - } + }) } - reactionsMenuItems.append( + appendTo.append( UIAction(title: "•••") { [weak self] _ in guard let self else { return } reactionMessageId = self.messageIds[indexPath.row] @@ -2019,14 +1967,6 @@ extension ChatViewController { present(navigationController, animated: true) } ) - - let menu = UIMenu( - title: String.localized("react"), - image: UIImage(systemName: "face.smiling"), - options: [], - children: reactionsMenuItems - ) - return menu } // context menu for iOS 13+ @@ -2041,57 +1981,43 @@ extension ChatViewController { previewProvider: nil, actionProvider: { [weak self] _ in guard let self else { return nil } - let message = dcContext.getMessage(id: messageId) - var children: [UIMenuElement] = [] - let showReaction = message.isInfo == false && message.isSetupMessage == false && message.type != DC_MSG_VIDEOCHAT_INVITATION && dcChat.canSend - if showReaction { - children.append(reactionsMenu(indexPath: indexPath)) - } - - let replyMenuChildren: [UIMenuElement] - let messageIsFromMe = message.isFromCurrentSender - - if message.isInfo { - replyMenuChildren = [] - } else if dcChat.canSend && self.isGroupChat && messageIsFromMe == false { - replyMenuChildren = [ - UIAction.menuAction(localizationKey: "notify_reply_button", systemImageName: "arrowshape.turn.up.left.fill", indexPath: indexPath, action: { self.reply(at: $0 ) }), - UIAction.menuAction(localizationKey: "reply_privately", systemImageName: "arrowshape.turn.up.left", indexPath: indexPath, action: { self.replyPrivatelyToMessage(at: $0 ) }), - ] - } else if self.isGroupChat && messageIsFromMe == false { - replyMenuChildren = [ - UIAction.menuAction(localizationKey: "reply_privately", systemImageName: "arrowshape.turn.up.left", indexPath: indexPath, action: { self.replyPrivatelyToMessage(at: $0 ) }), - ] - } else if dcChat.canSend { - replyMenuChildren = [ - UIAction.menuAction(localizationKey: "notify_reply_button", systemImageName: "arrowshape.turn.up.left.fill", indexPath: indexPath, action: { self.reply(at: $0 ) }), - ] - } else { - replyMenuChildren = [] + if canReply(message: message) { + if #available(iOS 16.0, *) { + // together with preferredElementSize below, this shows the reaction options in a row + reactionsMenuItems(appendTo: &children, indexPath: indexPath) + } else { + var items: [UIMenuElement] = [] + reactionsMenuItems(appendTo: &items, indexPath: indexPath) + children.append(UIMenu(title: String.localized("react"), image: UIImage(systemName: "face.smiling"), children: items)) + } + children.append( + UIAction.menuAction(localizationKey: "notify_reply_button", systemImageName: "arrowshape.turn.up.left.fill", indexPath: indexPath, action: { self.reply(at: $0 ) }) + ) } - if replyMenuChildren.isEmpty == false { + if canReplyPrivately(message: message) { children.append( - UIMenu(options: [.displayInline], children: replyMenuChildren) + UIAction.menuAction(localizationKey: "reply_privately", systemImageName: "arrowshape.turn.up.left", indexPath: indexPath, action: { self.replyPrivatelyToMessage(at: $0 ) }) ) } // these are always there children.append(contentsOf: [ - UIAction.menuAction(localizationKey: "forward", systemImageName: "arrowshape.forward", indexPath: indexPath, action: { self.forward(at: $0 ) }), + UIAction.menuAction(localizationKey: "forward", systemImageName: "arrowshape.forward.fill", indexPath: indexPath, action: { self.forward(at: $0 ) }), UIAction.menuAction(localizationKey: "info", systemImageName: "info", indexPath: indexPath, action: { self.info(at: $0 ) }), - UIAction.menuAction(localizationKey: "global_menu_edit_copy_desktop", systemImageName: "doc.on.doc", indexPath: indexPath, action: { self.copyMessage(at: $0 ) }), - UIAction.menuAction(localizationKey: "delete", attributes: [.destructive], systemImageName: "trash", indexPath: indexPath, action: { self.deleteMessage(at: $0 ) }), - UIMenu(options: [.displayInline], children: [ - UIAction.menuAction(localizationKey: "select_more", systemImageName: "checkmark.circle", indexPath: indexPath, action: { self.selectMore(at: $0 ) }), + UIAction.menuAction(localizationKey: "menu_more_options", systemImageName: "checkmark.circle", indexPath: indexPath, action: { self.selectMore(at: $0 ) }), ]) ]) - return UIMenu(children: children) + let menu = UIMenu(children: children) + if #available(iOS 16.0, *) { + menu.preferredElementSize = .small + } + return menu } ) } diff --git a/deltachat-ios/Chat/Send Reaction/DefaultReactions.swift b/deltachat-ios/Chat/Send Reaction/DefaultReactions.swift index 9471c66ea..5716bc30c 100644 --- a/deltachat-ios/Chat/Send Reaction/DefaultReactions.swift +++ b/deltachat-ios/Chat/Send Reaction/DefaultReactions.swift @@ -5,16 +5,12 @@ enum DefaultReactions: CaseIterable { case thumbsUp case thumbsDown case heart - case haha - case sad var emoji: String { switch self { case .thumbsUp: return "👍" case .thumbsDown: return "👎" case .heart: return "❤️" - case .haha: return "😂" - case .sad: return "🙁" } } }