From 68579ecff866fd110e9239832cdb34000e1d9c0e Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Mon, 23 Oct 2023 14:37:52 +0200 Subject: [PATCH 1/9] feat: ShortcutModifier added --- Mail/Utils/ShortcutModifier.swift | 64 +++++++++++++++++++++ Mail/Views/Thread List/ThreadListView.swift | 1 + MailCore/Cache/Actions/ActionOrigin.swift | 6 ++ 3 files changed, 71 insertions(+) create mode 100644 Mail/Utils/ShortcutModifier.swift diff --git a/Mail/Utils/ShortcutModifier.swift b/Mail/Utils/ShortcutModifier.swift new file mode 100644 index 000000000..39f09a46c --- /dev/null +++ b/Mail/Utils/ShortcutModifier.swift @@ -0,0 +1,64 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2022 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import Foundation +import MailCore +import SwiftUI + +struct ShortcutModifier: ViewModifier { + @EnvironmentObject private var actionsManager: ActionsManager + + @ObservedObject var viewModel: ThreadListViewModel + @ObservedObject var multipleSelectionViewModel: ThreadListMultipleSelectionViewModel + + func body(content: Content) -> some View { + ZStack { + VStack { + Button("Delete shortcut", action: shortcutDelete) + .keyboardShortcut(.delete) + } + + content + } + } + + private func shortcutDelete() { + let messages: [Message] + if multipleSelectionViewModel.isEnabled { + messages = multipleSelectionViewModel.selectedItems.flatMap(\.messages) + } else { + guard let unwrapMessages = viewModel.selectedThread?.messages.toArray() else { return } + messages = unwrapMessages + } + Task { + try await actionsManager.performAction( + target: messages, + action: .delete, + origin: .shortcut(originFolder: viewModel.folder.freezeIfNeeded()) + ) + } + } +} + +extension View { + func shortcutModifier(viewModel: ThreadListViewModel, + multipleSelectionViewModel: ThreadListMultipleSelectionViewModel) -> some View { + modifier(ShortcutModifier(viewModel: viewModel, + multipleSelectionViewModel: multipleSelectionViewModel)) + } +} diff --git a/Mail/Views/Thread List/ThreadListView.swift b/Mail/Views/Thread List/ThreadListView.swift index 81552000a..dec2e5360 100644 --- a/Mail/Views/Thread List/ThreadListView.swift +++ b/Mail/Views/Thread List/ThreadListView.swift @@ -168,6 +168,7 @@ struct ThreadListView: View { matomo.track(eventWithCategory: .newMessage, name: "openFromFab") navigationState.editedDraft = EditedDraft.new() } + .shortcutModifier(viewModel: viewModel, multipleSelectionViewModel: multipleSelectionViewModel) .onAppear { networkMonitor.start() if viewModel.isCompact { diff --git a/MailCore/Cache/Actions/ActionOrigin.swift b/MailCore/Cache/Actions/ActionOrigin.swift index b18369d93..51c22a592 100644 --- a/MailCore/Cache/Actions/ActionOrigin.swift +++ b/MailCore/Cache/Actions/ActionOrigin.swift @@ -25,6 +25,7 @@ public struct ActionOrigin { case floatingPanel case toolbar case multipleSelection + case shortcut } public private(set) var type: ActionOriginType @@ -75,4 +76,9 @@ public struct ActionOrigin { nearestFlushAlert: nearestFlushAlert, nearestMessagesToMoveSheet: nearestMessagesToMoveSheet) } + + public static func shortcut(originFolder: Folder? = nil, + nearestFlushAlert: Binding? = nil) -> ActionOrigin { + ActionOrigin(type: .shortcut, folder: originFolder, nearestFlushAlert: nearestFlushAlert) + } } From 0a6032758e9f866fe5d8778c821dc2a82a650f33 Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Mon, 23 Oct 2023 15:17:44 +0200 Subject: [PATCH 2/9] feat: Added some shortcuts on threadList --- Mail/Utils/ShortcutModifier.swift | 43 ++++++++++++++++++- .../Thread List/ThreadListViewModel.swift | 14 ++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Mail/Utils/ShortcutModifier.swift b/Mail/Utils/ShortcutModifier.swift index 39f09a46c..b95919fc8 100644 --- a/Mail/Utils/ShortcutModifier.swift +++ b/Mail/Utils/ShortcutModifier.swift @@ -30,8 +30,22 @@ struct ShortcutModifier: ViewModifier { ZStack { VStack { Button("Delete shortcut", action: shortcutDelete) - .keyboardShortcut(.delete) + .keyboardShortcut(.delete, modifiers: []) + + Button("Reply shortcut", action: shortcutReply) + .keyboardShortcut("r") + + Button("Refresh shortcut", action: shortcutRefresh) + .keyboardShortcut("n", modifiers: [.shift, .command]) + + Button("Next thread", action: shortcutNext) + .keyboardShortcut(.downArrow, modifiers: []) + + Button("Previous thread", action: shortcutPrevious) + .keyboardShortcut(.upArrow, modifiers: []) } + .frame(width: 0, height: 0) + .hidden() content } @@ -53,6 +67,33 @@ struct ShortcutModifier: ViewModifier { ) } } + + private func shortcutReply() { + guard !multipleSelectionViewModel.isEnabled, + let message = viewModel.selectedThread? + .lastMessageToExecuteAction(currentMailboxEmail: viewModel.mailboxManager.mailbox.email) else { return } + Task { + try await actionsManager.performAction( + target: [message], + action: .reply, + origin: .shortcut(originFolder: viewModel.folder.freezeIfNeeded()) + ) + } + } + + private func shortcutRefresh() { + Task { + await viewModel.fetchThreads() + } + } + + private func shortcutNext() { + viewModel.nextThread() + } + + private func shortcutPrevious() { + viewModel.previousThread() + } } extension View { diff --git a/Mail/Views/Thread List/ThreadListViewModel.swift b/Mail/Views/Thread List/ThreadListViewModel.swift index dda65ef31..ed1d6f6fe 100644 --- a/Mail/Views/Thread List/ThreadListViewModel.swift +++ b/Mail/Views/Thread List/ThreadListViewModel.swift @@ -249,6 +249,20 @@ final class DateSection: Identifiable, Equatable { selectedThread = threads[validIndex] } + func nextThread() { + guard !filteredThreads.isEmpty, let oldIndex = selectedThreadIndex, oldIndex < filteredThreads.count - 1 else { return } + let newIndex = oldIndex + 1 + selectedThread = filteredThreads[newIndex] + selectedThreadIndex = newIndex + } + + func previousThread() { + guard !filteredThreads.isEmpty, let oldIndex = selectedThreadIndex, oldIndex > 0 else { return } + let newIndex = oldIndex - 1 + selectedThread = filteredThreads[newIndex] + selectedThreadIndex = newIndex + } + func resetFilterIfNeeded(filteredThreads: [Thread]) { if filteredThreads.isEmpty && filterUnreadOn { DispatchQueue.main.sync { From 870ceb3f6d8ac8de3a7cbd31a6a3211d88f57dec Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Tue, 24 Oct 2023 16:48:00 +0200 Subject: [PATCH 3/9] chore: Matomo added --- Mail/Utils/ShortcutModifier.swift | 10 ++++++++++ MailCore/Utils/Matomo+Extension.swift | 1 + 2 files changed, 11 insertions(+) diff --git a/Mail/Utils/ShortcutModifier.swift b/Mail/Utils/ShortcutModifier.swift index b95919fc8..86c42336b 100644 --- a/Mail/Utils/ShortcutModifier.swift +++ b/Mail/Utils/ShortcutModifier.swift @@ -52,6 +52,8 @@ struct ShortcutModifier: ViewModifier { } private func shortcutDelete() { + matomo.track(eventWithCategory: .shortcutAction, name: "delete") + let messages: [Message] if multipleSelectionViewModel.isEnabled { messages = multipleSelectionViewModel.selectedItems.flatMap(\.messages) @@ -69,6 +71,8 @@ struct ShortcutModifier: ViewModifier { } private func shortcutReply() { + matomo.track(eventWithCategory: .shortcutAction, name: "reply") + guard !multipleSelectionViewModel.isEnabled, let message = viewModel.selectedThread? .lastMessageToExecuteAction(currentMailboxEmail: viewModel.mailboxManager.mailbox.email) else { return } @@ -82,16 +86,22 @@ struct ShortcutModifier: ViewModifier { } private func shortcutRefresh() { + matomo.track(eventWithCategory: .shortcutAction, name: "refresh") + Task { await viewModel.fetchThreads() } } private func shortcutNext() { + guard !multipleSelectionViewModel.isEnabled else { return } + matomo.track(eventWithCategory: .shortcutAction, name: "nextThread") viewModel.nextThread() } private func shortcutPrevious() { + guard !multipleSelectionViewModel.isEnabled else { return } + matomo.track(eventWithCategory: .shortcutAction, name: "previousThread") viewModel.previousThread() } } diff --git a/MailCore/Utils/Matomo+Extension.swift b/MailCore/Utils/Matomo+Extension.swift index cbcfd9207..448150a50 100644 --- a/MailCore/Utils/Matomo+Extension.swift +++ b/MailCore/Utils/Matomo+Extension.swift @@ -67,6 +67,7 @@ public extension MatomoUtils.EventCategory { static let threadActions = MatomoUtils.EventCategory(displayName: "threadActions") static let swipeActions = MatomoUtils.EventCategory(displayName: "swipeActions") static let notificationAction = MatomoUtils.EventCategory(displayName: "notificationAction") + static let shortcutAction = MatomoUtils.EventCategory(displayName: "shortcutAction") // Settings From ac07b24cea9626fc8e701f24f035286f40d3afba Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Tue, 24 Oct 2023 16:48:32 +0200 Subject: [PATCH 4/9] chore: Matomo added --- Mail/Utils/ShortcutModifier.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mail/Utils/ShortcutModifier.swift b/Mail/Utils/ShortcutModifier.swift index 86c42336b..3b1cbddbf 100644 --- a/Mail/Utils/ShortcutModifier.swift +++ b/Mail/Utils/ShortcutModifier.swift @@ -17,12 +17,15 @@ */ import Foundation +import InfomaniakCoreUI +import InfomaniakDI import MailCore import SwiftUI struct ShortcutModifier: ViewModifier { @EnvironmentObject private var actionsManager: ActionsManager + @LazyInjectService private var matomo: MatomoUtils @ObservedObject var viewModel: ThreadListViewModel @ObservedObject var multipleSelectionViewModel: ThreadListMultipleSelectionViewModel From 8033fff691cecc5ae46772b7cdc40ff666a1cc74 Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Tue, 24 Oct 2023 16:49:12 +0200 Subject: [PATCH 5/9] fix: PlatformDetector to add arrow shortcuts on iPad --- Mail/Helpers/AppAssembly.swift | 3 +++ Mail/Utils/ShortcutModifier.swift | 6 ++++-- MailCore/Utils/PlatformDetectable.swift | 4 ++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Mail/Helpers/AppAssembly.swift b/Mail/Helpers/AppAssembly.swift index f538ec9b7..96bb955c3 100644 --- a/Mail/Helpers/AppAssembly.swift +++ b/Mail/Helpers/AppAssembly.swift @@ -124,6 +124,9 @@ enum ApplicationAssembly { }, Factory(type: AppLaunchCounter.self) { _, _ in AppLaunchCounter() + }, + Factory(type: PlatformDetector.self) { _, _ in + PlatformDetector() } ] diff --git a/Mail/Utils/ShortcutModifier.swift b/Mail/Utils/ShortcutModifier.swift index 3b1cbddbf..3db84615e 100644 --- a/Mail/Utils/ShortcutModifier.swift +++ b/Mail/Utils/ShortcutModifier.swift @@ -26,6 +26,8 @@ struct ShortcutModifier: ViewModifier { @EnvironmentObject private var actionsManager: ActionsManager @LazyInjectService private var matomo: MatomoUtils + @LazyInjectService private var platformDetector: PlatformDetector + @ObservedObject var viewModel: ThreadListViewModel @ObservedObject var multipleSelectionViewModel: ThreadListMultipleSelectionViewModel @@ -42,10 +44,10 @@ struct ShortcutModifier: ViewModifier { .keyboardShortcut("n", modifiers: [.shift, .command]) Button("Next thread", action: shortcutNext) - .keyboardShortcut(.downArrow, modifiers: []) + .keyboardShortcut(.downArrow, modifiers: platformDetector.isMac ? [] : [.command]) Button("Previous thread", action: shortcutPrevious) - .keyboardShortcut(.upArrow, modifiers: []) + .keyboardShortcut(.upArrow, modifiers: platformDetector.isMac ? [] : [.command]) } .frame(width: 0, height: 0) .hidden() diff --git a/MailCore/Utils/PlatformDetectable.swift b/MailCore/Utils/PlatformDetectable.swift index 65c6a1fac..0f96960d1 100644 --- a/MailCore/Utils/PlatformDetectable.swift +++ b/MailCore/Utils/PlatformDetectable.swift @@ -50,6 +50,10 @@ public struct PlatformDetector: PlatformDetectable { public var isiOSAppOnMac: Bool = ProcessInfo().isiOSAppOnMac + public var isMac: Bool { + isMacCatalyst || isiOSAppOnMac + } + public var isInExtension: Bool = { guard Bundle.main.bundlePath.hasSuffix(".appex") else { return false From f1251ac55f5d2879927cf7749d6c43c45dcaa6b0 Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Wed, 25 Oct 2023 07:29:09 +0200 Subject: [PATCH 6/9] fix: Translated shortcut actions --- Mail/Utils/ShortcutModifier.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Mail/Utils/ShortcutModifier.swift b/Mail/Utils/ShortcutModifier.swift index 3db84615e..3f2b5f533 100644 --- a/Mail/Utils/ShortcutModifier.swift +++ b/Mail/Utils/ShortcutModifier.swift @@ -20,6 +20,7 @@ import Foundation import InfomaniakCoreUI import InfomaniakDI import MailCore +import MailResources import SwiftUI struct ShortcutModifier: ViewModifier { @@ -34,19 +35,19 @@ struct ShortcutModifier: ViewModifier { func body(content: Content) -> some View { ZStack { VStack { - Button("Delete shortcut", action: shortcutDelete) + Button(MailResourcesStrings.Localizable.actionDelete, action: shortcutDelete) .keyboardShortcut(.delete, modifiers: []) - Button("Reply shortcut", action: shortcutReply) + Button(MailResourcesStrings.Localizable.actionReply, action: shortcutReply) .keyboardShortcut("r") - Button("Refresh shortcut", action: shortcutRefresh) + Button(MailResourcesStrings.Localizable.shortcutRefreshAction, action: shortcutRefresh) .keyboardShortcut("n", modifiers: [.shift, .command]) - Button("Next thread", action: shortcutNext) + Button(MailResourcesStrings.Localizable.shortcutNextAction, action: shortcutNext) .keyboardShortcut(.downArrow, modifiers: platformDetector.isMac ? [] : [.command]) - Button("Previous thread", action: shortcutPrevious) + Button(MailResourcesStrings.Localizable.shortcutPreviousAction, action: shortcutPrevious) .keyboardShortcut(.upArrow, modifiers: platformDetector.isMac ? [] : [.command]) } .frame(width: 0, height: 0) From 0d5291733b64a8338e4a90635888a1b469dfafd4 Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Wed, 25 Oct 2023 08:49:42 +0200 Subject: [PATCH 7/9] feat: Add newMessage shortcut --- Mail/Utils/ShortcutModifier.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Mail/Utils/ShortcutModifier.swift b/Mail/Utils/ShortcutModifier.swift index 3f2b5f533..007fda65f 100644 --- a/Mail/Utils/ShortcutModifier.swift +++ b/Mail/Utils/ShortcutModifier.swift @@ -25,6 +25,7 @@ import SwiftUI struct ShortcutModifier: ViewModifier { @EnvironmentObject private var actionsManager: ActionsManager + @EnvironmentObject private var navigationState: NavigationState @LazyInjectService private var matomo: MatomoUtils @LazyInjectService private var platformDetector: PlatformDetector @@ -41,6 +42,9 @@ struct ShortcutModifier: ViewModifier { Button(MailResourcesStrings.Localizable.actionReply, action: shortcutReply) .keyboardShortcut("r") + Button(MailResourcesStrings.Localizable.buttonNewMessage, action: shortcutNewMessage) + .keyboardShortcut("n") + Button(MailResourcesStrings.Localizable.shortcutRefreshAction, action: shortcutRefresh) .keyboardShortcut("n", modifiers: [.shift, .command]) @@ -91,6 +95,12 @@ struct ShortcutModifier: ViewModifier { } } + private func shortcutNewMessage() { + matomo.track(eventWithCategory: .shortcutAction, name: "newMessage") + + navigationState.editedDraft = EditedDraft.new() + } + private func shortcutRefresh() { matomo.track(eventWithCategory: .shortcutAction, name: "refresh") From 8018937c51a96b4fe33e055a5a7f0651597b706f Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Wed, 25 Oct 2023 10:27:47 +0200 Subject: [PATCH 8/9] fix: Use platformDetectable --- Mail/Helpers/AppAssembly.swift | 3 --- Mail/Utils/ShortcutModifier.swift | 2 +- MailCore/Utils/PlatformDetectable.swift | 3 +++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Mail/Helpers/AppAssembly.swift b/Mail/Helpers/AppAssembly.swift index 96bb955c3..f538ec9b7 100644 --- a/Mail/Helpers/AppAssembly.swift +++ b/Mail/Helpers/AppAssembly.swift @@ -124,9 +124,6 @@ enum ApplicationAssembly { }, Factory(type: AppLaunchCounter.self) { _, _ in AppLaunchCounter() - }, - Factory(type: PlatformDetector.self) { _, _ in - PlatformDetector() } ] diff --git a/Mail/Utils/ShortcutModifier.swift b/Mail/Utils/ShortcutModifier.swift index 007fda65f..791db8e7c 100644 --- a/Mail/Utils/ShortcutModifier.swift +++ b/Mail/Utils/ShortcutModifier.swift @@ -28,7 +28,7 @@ struct ShortcutModifier: ViewModifier { @EnvironmentObject private var navigationState: NavigationState @LazyInjectService private var matomo: MatomoUtils - @LazyInjectService private var platformDetector: PlatformDetector + @LazyInjectService private var platformDetector: PlatformDetectable @ObservedObject var viewModel: ThreadListViewModel @ObservedObject var multipleSelectionViewModel: ThreadListMultipleSelectionViewModel diff --git a/MailCore/Utils/PlatformDetectable.swift b/MailCore/Utils/PlatformDetectable.swift index 0f96960d1..0239884c4 100644 --- a/MailCore/Utils/PlatformDetectable.swift +++ b/MailCore/Utils/PlatformDetectable.swift @@ -28,6 +28,9 @@ public protocol PlatformDetectable { /// We are running an iOS App on Mac var isiOSAppOnMac: Bool { get } + /// We are running on Mac + var isMac: Bool { get } + /// We are running in extension mode var isInExtension: Bool { get } From cd4add443e17e954da626ec2b6b65a77f44fba28 Mon Sep 17 00:00:00 2001 From: Ambroise Decouttere Date: Mon, 30 Oct 2023 13:03:23 +0100 Subject: [PATCH 9/9] fix: Add right click to mac --- Mail/Views/Thread List/ThreadListCell.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Mail/Views/Thread List/ThreadListCell.swift b/Mail/Views/Thread List/ThreadListCell.swift index f9d3c1ada..2f86cfad3 100644 --- a/Mail/Views/Thread List/ThreadListCell.swift +++ b/Mail/Views/Thread List/ThreadListCell.swift @@ -73,6 +73,7 @@ struct ThreadListCell: View { )) .contentShape(Rectangle()) .onTapGesture { didTapCell() } + .actionsContextMenu(thread: thread) .onLongPressGesture { didLongPressCell() } .swipeActions( thread: thread,