Skip to content

Commit

Permalink
Merge pull request #1089 from Infomaniak/shortcut
Browse files Browse the repository at this point in the history
feat(Mac+iPad): Shortcut added
  • Loading branch information
PhilippeWeidmann authored Oct 31, 2023
2 parents 3b134fc + cd4add4 commit 831b20d
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 0 deletions.
131 changes: 131 additions & 0 deletions Mail/Utils/ShortcutModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
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 <http://www.gnu.org/licenses/>.
*/

import Foundation
import InfomaniakCoreUI
import InfomaniakDI
import MailCore
import MailResources
import SwiftUI

struct ShortcutModifier: ViewModifier {
@EnvironmentObject private var actionsManager: ActionsManager
@EnvironmentObject private var navigationState: NavigationState

@LazyInjectService private var matomo: MatomoUtils
@LazyInjectService private var platformDetector: PlatformDetectable

@ObservedObject var viewModel: ThreadListViewModel
@ObservedObject var multipleSelectionViewModel: ThreadListMultipleSelectionViewModel

func body(content: Content) -> some View {
ZStack {
VStack {
Button(MailResourcesStrings.Localizable.actionDelete, action: shortcutDelete)
.keyboardShortcut(.delete, modifiers: [])

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])

Button(MailResourcesStrings.Localizable.shortcutNextAction, action: shortcutNext)
.keyboardShortcut(.downArrow, modifiers: platformDetector.isMac ? [] : [.command])

Button(MailResourcesStrings.Localizable.shortcutPreviousAction, action: shortcutPrevious)
.keyboardShortcut(.upArrow, modifiers: platformDetector.isMac ? [] : [.command])
}
.frame(width: 0, height: 0)
.hidden()

content
}
}

private func shortcutDelete() {
matomo.track(eventWithCategory: .shortcutAction, name: "delete")

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())
)
}
}

private func shortcutReply() {
matomo.track(eventWithCategory: .shortcutAction, name: "reply")

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 shortcutNewMessage() {
matomo.track(eventWithCategory: .shortcutAction, name: "newMessage")

navigationState.editedDraft = EditedDraft.new()
}

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()
}
}

extension View {
func shortcutModifier(viewModel: ThreadListViewModel,
multipleSelectionViewModel: ThreadListMultipleSelectionViewModel) -> some View {
modifier(ShortcutModifier(viewModel: viewModel,
multipleSelectionViewModel: multipleSelectionViewModel))
}
}
1 change: 1 addition & 0 deletions Mail/Views/Thread List/ThreadListCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ struct ThreadListCell: View {
))
.contentShape(Rectangle())
.onTapGesture { didTapCell() }
.actionsContextMenu(thread: thread)
.onLongPressGesture { didLongPressCell() }
.swipeActions(
thread: thread,
Expand Down
1 change: 1 addition & 0 deletions Mail/Views/Thread List/ThreadListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 14 additions & 0 deletions Mail/Views/Thread List/ThreadListViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions MailCore/Cache/Actions/ActionOrigin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public struct ActionOrigin {
case floatingPanel
case toolbar
case multipleSelection
case shortcut
}

public private(set) var type: ActionOriginType
Expand Down Expand Up @@ -75,4 +76,9 @@ public struct ActionOrigin {
nearestFlushAlert: nearestFlushAlert,
nearestMessagesToMoveSheet: nearestMessagesToMoveSheet)
}

public static func shortcut(originFolder: Folder? = nil,
nearestFlushAlert: Binding<FlushAlertState?>? = nil) -> ActionOrigin {
ActionOrigin(type: .shortcut, folder: originFolder, nearestFlushAlert: nearestFlushAlert)
}
}
1 change: 1 addition & 0 deletions MailCore/Utils/Matomo+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,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

Expand Down
7 changes: 7 additions & 0 deletions MailCore/Utils/PlatformDetectable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand All @@ -50,6 +53,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
Expand Down

0 comments on commit 831b20d

Please sign in to comment.