From 1e1b40cdd769cba1011b7c3e2defe3e377b1a489 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 9 Jun 2023 10:58:51 +0200 Subject: [PATCH 1/4] feat: Save all attachments --- Mail/Helpers/DocumentPicker.swift | 25 ++++++++++++----- .../New Message/ComposeMessageView.swift | 4 +-- Mail/Views/Thread/AttachmentsView.swift | 27 ++++++++++++++++--- MailCore/API/Endpoint.swift | 4 +++ MailCore/API/MailApiFetcher.swift | 20 +++++++++++--- 5 files changed, 65 insertions(+), 15 deletions(-) diff --git a/Mail/Helpers/DocumentPicker.swift b/Mail/Helpers/DocumentPicker.swift index e8164051f..b98a75d3f 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,8 @@ struct DocumentPicker: UIViewControllerRepresentable { } func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - parent.completion(urls) + // TODO: Get completion + //parent.completion(urls) parent.dismiss() } } diff --git a/Mail/Views/New Message/ComposeMessageView.swift b/Mail/Views/New Message/ComposeMessageView.swift index f01509581..f3c19f881 100644 --- a/Mail/Views/New Message/ComposeMessageView.swift +++ b/Mail/Views/New Message/ComposeMessageView.swift @@ -228,9 +228,9 @@ struct ComposeMessageView: View { .ignoresSafeArea() } .sheet(isPresented: $isShowingFileSelection) { - DocumentPicker { urls in + DocumentPicker(pickerType: .selectContent([.item]){ urls in attachmentsManager.importAttachments(attachments: urls) - } + }) } .sheet(isPresented: $isShowingPhotoLibrary) { ImagePicker { results in diff --git a/Mail/Views/Thread/AttachmentsView.swift b/Mail/Views/Thread/AttachmentsView.swift index a3ec4126b..c3745b585 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 { + let id = UUID() + 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 } From 70b9e5d8cef09b521ba1ec1c52b4e72f4e78b184 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 9 Jun 2023 11:28:53 +0200 Subject: [PATCH 2/4] feat: Ignore safe area --- Mail/Helpers/DocumentPicker.swift | 5 +++-- Mail/Views/New Message/ComposeMessageView.swift | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Mail/Helpers/DocumentPicker.swift b/Mail/Helpers/DocumentPicker.swift index b98a75d3f..ad5bebe14 100644 --- a/Mail/Helpers/DocumentPicker.swift +++ b/Mail/Helpers/DocumentPicker.swift @@ -63,8 +63,9 @@ struct DocumentPicker: UIViewControllerRepresentable { } func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - // TODO: Get completion - //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 f3c19f881..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(pickerType: .selectContent([.item]){ 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 { From dfd1bb4feed73114c2fdb165705685bcbe399b77 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 9 Jun 2023 13:23:50 +0200 Subject: [PATCH 3/4] fix(MailButton): Button's height changes when the loader is visible --- Mail/Components/MailButton.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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) } From 34fdea5fd6eb6c197d56abc8d7273788e3795599 Mon Sep 17 00:00:00 2001 From: Valentin Perignon Date: Fri, 9 Jun 2023 13:35:13 +0200 Subject: [PATCH 4/4] refactor(AllAttachmentsURL): `id` depends of url --- Mail/Views/Thread/AttachmentsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mail/Views/Thread/AttachmentsView.swift b/Mail/Views/Thread/AttachmentsView.swift index c3745b585..7ae4adfa9 100644 --- a/Mail/Views/Thread/AttachmentsView.swift +++ b/Mail/Views/Thread/AttachmentsView.swift @@ -25,7 +25,7 @@ import RealmSwift import SwiftUI struct AllAttachmentsURL: Identifiable { - let id = UUID() + var id: String { url.absoluteString } let url: URL }