diff --git a/Mail/Components/MailButton.swift b/Mail/Components/MailButton.swift index 96db5370b..572e5041d 100644 --- a/Mail/Components/MailButton.swift +++ b/Mail/Components/MailButton.swift @@ -113,9 +113,9 @@ struct MailButton: View { } } .opacity(loading ? 0 : 1) - if loading { - LoadingButtonProgressView(style: style) - } + + LoadingButtonProgressView(style: style) + .opacity(loading ? 1 : 0) } .frame(maxWidth: fullWidth ? UIConstants.componentsMaxWidth : nil) } diff --git a/Mail/Helpers/DocumentPicker.swift b/Mail/Helpers/DocumentPicker.swift index e8164051f..ad5bebe14 100644 --- a/Mail/Helpers/DocumentPicker.swift +++ b/Mail/Helpers/DocumentPicker.swift @@ -21,17 +21,29 @@ import UIKit import UniformTypeIdentifiers struct DocumentPicker: UIViewControllerRepresentable { - var completion: ([URL]) -> Void + enum PickerType { + case selectContent([UTType], ([URL]) -> Void) + case exportContent([URL]) + } + @Environment(\.dismiss) private var dismiss + let pickerType: PickerType + func makeCoordinator() -> DocumentPicker.Coordinator { return DocumentPicker.Coordinator(parent: self) } - func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIDocumentPickerViewController { - let supportedTypes: [UTType] = [UTType.item] - let picker = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true) - picker.allowsMultipleSelection = true + func makeUIViewController(context: Context) -> UIDocumentPickerViewController { + let picker: UIDocumentPickerViewController + switch pickerType { + case let .selectContent(types, _): + picker = UIDocumentPickerViewController(forOpeningContentTypes: types, asCopy: true) + picker.allowsMultipleSelection = true + case let .exportContent(urls): + picker = UIDocumentPickerViewController(forExporting: urls, asCopy: true) + } + picker.delegate = context.coordinator return picker } @@ -51,7 +63,9 @@ struct DocumentPicker: UIViewControllerRepresentable { } func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - parent.completion(urls) + if case let .selectContent(_, completion) = parent.pickerType { + completion(urls) + } parent.dismiss() } } diff --git a/Mail/Views/New Message/ComposeMessageView.swift b/Mail/Views/New Message/ComposeMessageView.swift index f01509581..1f78e5c05 100644 --- a/Mail/Views/New Message/ComposeMessageView.swift +++ b/Mail/Views/New Message/ComposeMessageView.swift @@ -228,14 +228,16 @@ struct ComposeMessageView: View { .ignoresSafeArea() } .sheet(isPresented: $isShowingFileSelection) { - DocumentPicker { urls in + DocumentPicker(pickerType: .selectContent([.item]) { urls in attachmentsManager.importAttachments(attachments: urls) - } + }) + .ignoresSafeArea() } .sheet(isPresented: $isShowingPhotoLibrary) { ImagePicker { results in attachmentsManager.importAttachments(attachments: results) } + .ignoresSafeArea() } .customAlert(isPresented: $alert.isShowing) { switch alert.state { diff --git a/Mail/Views/Thread/AttachmentsView.swift b/Mail/Views/Thread/AttachmentsView.swift index a3ec4126b..7ae4adfa9 100644 --- a/Mail/Views/Thread/AttachmentsView.swift +++ b/Mail/Views/Thread/AttachmentsView.swift @@ -24,8 +24,18 @@ import MailResources import RealmSwift import SwiftUI +struct AllAttachmentsURL: Identifiable { + var id: String { url.absoluteString } + let url: URL +} + struct AttachmentsView: View { + @State private var showDocumentPicker = false + @State private var previewedAttachment: Attachment? + @State private var downloadInProgress = false + @State private var allAttachmentsURL: AllAttachmentsURL? + @EnvironmentObject var mailboxManager: MailboxManager @ObservedRealmObject var message: Message @@ -71,11 +81,18 @@ struct AttachmentsView: View { .textStyle(.bodySmallSecondary) MailButton(label: MailResourcesStrings.Localizable.buttonDownloadAll) { - // TODO: Download all attachments - matomo.track(eventWithCategory: .message, name: "downloadAll") - showWorkInProgressSnackBar() + Task { + await tryOrDisplayError { + matomo.track(eventWithCategory: .message, name: "downloadAll") + downloadInProgress = true + let attachmentURL = try await mailboxManager.apiFetcher.downloadAttachments(message: message) + allAttachmentsURL = AllAttachmentsURL(url: attachmentURL) + downloadInProgress = false + } + } } .mailButtonStyle(.smallLink) + .mailButtonLoading(downloadInProgress) Spacer() } @@ -84,6 +101,10 @@ struct AttachmentsView: View { .sheet(item: $previewedAttachment) { previewedAttachment in AttachmentPreview(attachment: previewedAttachment) } + .sheet(item: $allAttachmentsURL) { allAttachmentsURL in + DocumentPicker(pickerType: .exportContent([allAttachmentsURL.url])) + .ignoresSafeArea() + } } } diff --git a/MailCore/API/Endpoint.swift b/MailCore/API/Endpoint.swift index 98083c448..d6037301d 100644 --- a/MailCore/API/Endpoint.swift +++ b/MailCore/API/Endpoint.swift @@ -183,6 +183,10 @@ public extension Endpoint { return .mailbox(uuid: uuid).appending(path: "/message/unstar") } + static func downloadAttachments(messageResource: String) -> Endpoint { + return .resource(messageResource).appending(path: "/attachmentsArchive") + } + static func blockSender(messageResource: String) -> Endpoint { return .resource(messageResource).appending(path: "/blacklist") } diff --git a/MailCore/API/MailApiFetcher.swift b/MailCore/API/MailApiFetcher.swift index 99232b814..4f3078e15 100644 --- a/MailCore/API/MailApiFetcher.swift +++ b/MailCore/API/MailApiFetcher.swift @@ -42,14 +42,14 @@ public class MailApiFetcher: ApiFetcher { ) async throws -> (data: T, responseAt: Int?) { do { return try await super.perform(request: request) - } catch InfomaniakError.apiError(let apiError) { + } catch let InfomaniakError.apiError(apiError) { throw MailApiError.mailApiErrorWithFallback(apiErrorCode: apiError.code) - } catch InfomaniakError.serverError(statusCode: let statusCode) { + } catch let InfomaniakError.serverError(statusCode: statusCode) { throw MailServerError(httpStatus: statusCode) } catch { if let afError = error.asAFError { - if case .responseSerializationFailed(let reason) = afError, - case .decodingFailed(let error) = reason { + if case let .responseSerializationFailed(reason) = afError, + case let .decodingFailed(error) = reason { var rawJson = "No data" if let data = request.data, let stringData = String(data: data, encoding: .utf8) { @@ -293,6 +293,18 @@ public class MailApiFetcher: ApiFetcher { parameters: ["uids": messages.map(\.uid)])).data } + public func downloadAttachments(message: Message) async throws -> URL { + let destination = DownloadRequest.suggestedDownloadDestination(options: [ + .createIntermediateDirectories, + .removePreviousFile + ]) + let download = authenticatedSession.download( + Endpoint.downloadAttachments(messageResource: message.resource).url, + to: destination + ) + return try await download.serializingDownloadedFileURL().value + } + public func blockSender(message: Message) async throws -> Bool { try await perform(request: authenticatedRequest(.blockSender(messageResource: message.resource), method: .post)).data }