diff --git a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift index f1b5db70c..98722a829 100644 --- a/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift +++ b/Mail/Views/Bottom sheets/Actions/ActionsViewModel.swift @@ -385,7 +385,7 @@ enum ActionsTarget: Equatable, Identifiable { case .threads(let threads, _): try await mailboxManager.moveOrDelete(threads: threads) case .message(let message): - try await mailboxManager.moveOrDelete(message: message) + try await mailboxManager.moveOrDelete(messages: [message]) } } diff --git a/MailCore/Cache/MailboxManager.swift b/MailCore/Cache/MailboxManager.swift index 54f5fa10c..0abe94cc8 100644 --- a/MailCore/Cache/MailboxManager.swift +++ b/MailCore/Cache/MailboxManager.swift @@ -404,46 +404,9 @@ public class MailboxManager: ObservableObject { return try await move(messages: messages, to: folder) } - /// Move to trash or delete threads, depending on its current state - /// - Parameter threads: Threads to remove public func moveOrDelete(threads: [Thread]) async throws { - // All threads comes from the same folder - guard let parentFolder = threads.first?.folder else { return } - - if parentFolder.toolType == .search { - for thread in threads { - await deleteInSearch(thread: thread) // Review this ? - } - } - - if parentFolder.role == .trash || parentFolder.role == .draft || parentFolder.role == .spam { - var messages = threads.flatMap(\.messages) - messages.append(contentsOf: messages.flatMap(\.duplicates)) - try await delete(messages: messages) - } else { - var messages = threads.flatMap(\.messages).filter { $0.scheduled == false } - messages.append(contentsOf: messages.flatMap(\.duplicates)) - let undoRedoAction = try await move(messages: messages, to: .trash) - let folderName = FolderRole.trash.localizedName - Task.detached { - await IKSnackBar.showCancelableSnackBar(message: MailResourcesStrings.Localizable.snackbarThreadMoved(folderName), - cancelSuccessMessage: MailResourcesStrings.Localizable - .snackbarMoveCancelled, - undoRedoAction: undoRedoAction, - mailboxManager: self) - } - } - } - - private func deleteInSearch(thread: Thread) async { - await backgroundRealm.execute { realm in - guard let searchFolder = realm.object(ofType: Folder.self, forPrimaryKey: Constants.searchFolderId), - let thread = thread.fresh(using: realm) else { return } - - try? realm.safeWrite { - searchFolder.threads.remove(thread) - } - } + let messagesToMoveOrDelete = threads.flatMap(\.messages) + try await moveOrDelete(messages: messagesToMoveOrDelete) } public func toggleStar(threads: [Thread]) async throws { @@ -1021,26 +984,52 @@ public class MailboxManager: ObservableObject { } } - /// Move to trash or delete message, depending on its current state - /// - Parameter message: Message to remove - public func moveOrDelete(message: Message) async throws { - if message.folder?.role == .trash - || message.folder?.role == .spam - || message.folder?.role == .draft { - var messages = [message] - messages.append(contentsOf: message.duplicates) - try await delete(messages: messages) + private func moveOrDeleteMessagesInSameFolder(messages: [Message]) async throws { + let messagesToMoveOrDelete = messages + messages.flatMap(\.duplicates) + + let firstMessageFolderRole = messages.first?.folder?.role + if firstMessageFolderRole == .trash + || firstMessageFolderRole == .spam + || firstMessageFolderRole == .draft { + try await delete(messages: messagesToMoveOrDelete) + async let _ = IKSnackBar.showSnackBar(message: deletionSnackbarMessage(for: messages, permanentlyDelete: true)) } else { - var messages = [message] - messages.append(contentsOf: message.duplicates) - let undoRedoAction = try await move(messages: messages, to: .trash) - Task.detached { - await IKSnackBar.showCancelableSnackBar( - message: MailResourcesStrings.Localizable.snackbarMessageMoved(FolderRole.trash.localizedName), - cancelSuccessMessage: MailResourcesStrings.Localizable.snackbarMoveCancelled, - undoRedoAction: undoRedoAction, - mailboxManager: self - ) + let undoRedoAction = try await move(messages: messagesToMoveOrDelete, to: .trash) + async let _ = IKSnackBar.showCancelableSnackBar( + message: deletionSnackbarMessage(for: messages, permanentlyDelete: false), + cancelSuccessMessage: MailResourcesStrings.Localizable.snackbarMoveCancelled, + undoRedoAction: undoRedoAction, + mailboxManager: self + ) + } + } + + private func deletionSnackbarMessage(for messages: [Message], permanentlyDelete: Bool) -> String { + if let firstMessageThreadMessagesCount = messages.first?.originalThread?.messages.count, + messages.count == 1 && firstMessageThreadMessagesCount > 1 { + return permanentlyDelete ? + MailResourcesStrings.Localizable.snackbarMessageDeletedPermanently : + MailResourcesStrings.Localizable.snackbarMessageMoved(FolderRole.trash.localizedName) + } else { + let uniqueThreadCount = Set(messages.compactMap(\.originalThread?.uid)).count + if permanentlyDelete { + return MailResourcesStrings.Localizable.snackbarThreadDeletedPermanently(uniqueThreadCount) + } else if uniqueThreadCount == 1 { + return MailResourcesStrings.Localizable.snackbarThreadMoved(FolderRole.trash.localizedName) + } else { + return MailResourcesStrings.Localizable.snackbarThreadsMoved(FolderRole.trash.localizedName) + } + } + } + + public func moveOrDelete(messages: [Message]) async throws { + let messagesGroupedByFolderId = Dictionary(grouping: messages, by: \.folderId) + + await withThrowingTaskGroup(of: Void.self) { group in + for messagesInSameFolder in messagesGroupedByFolderId.values { + group.addTask { + try await self.moveOrDeleteMessagesInSameFolder(messages: messagesInSameFolder) + } } } }