diff --git a/Mail/Views/New Message/Attachments/AttachmentUploadCell.swift b/Mail/Views/New Message/Attachments/AttachmentUploadCell.swift index bda7839eb..1be3d1579 100644 --- a/Mail/Views/New Message/Attachments/AttachmentUploadCell.swift +++ b/Mail/Views/New Message/Attachments/AttachmentUploadCell.swift @@ -30,11 +30,11 @@ struct AttachmentUploadCell: View { private let attachment: Attachment private let attachmentRemoved: ((Attachment) -> Void)? - @ObservedObject var uploadTask: AttachmentUploadTask + @ObservedObject var uploadTask: AttachmentTask @ModalState(wrappedValue: nil, context: ContextKeys.compose) private var previewedAttachment: Attachment? - init(uploadTask: AttachmentUploadTask, attachment: Attachment, attachmentRemoved: ((Attachment) -> Void)?) { + init(uploadTask: AttachmentTask, attachment: Attachment, attachmentRemoved: ((Attachment) -> Void)?) { self.uploadTask = uploadTask self.attachment = attachment self.attachmentRemoved = attachmentRemoved @@ -76,14 +76,14 @@ struct AttachmentUploadCell: View { previewedAttachment = attachment if !FileManager.default.fileExists(atPath: attachment.getLocalURL(mailboxManager: mailboxManager).path) { Task { - await mailboxManager.saveAttachmentLocally(attachment: attachment) + await mailboxManager.saveAttachmentLocally(attachment: attachment, progressObserver: nil) } } } } #Preview { - AttachmentUploadCell(uploadTask: AttachmentUploadTask(), attachment: PreviewHelper.sampleAttachment) { _ in + AttachmentUploadCell(uploadTask: AttachmentTask(), attachment: PreviewHelper.sampleAttachment) { _ in /* Preview */ } } diff --git a/Mail/Views/New Message/Attachments/AttachmentsManager.swift b/Mail/Views/New Message/Attachments/AttachmentsManager.swift index cb884a9f4..aed325655 100644 --- a/Mail/Views/New Message/Attachments/AttachmentsManager.swift +++ b/Mail/Views/New Message/Attachments/AttachmentsManager.swift @@ -46,8 +46,8 @@ import SwiftUI func completeUploadedAttachments() async /// Lookup and return _or_ new object representing a finished task instead. - func attachmentUploadTaskOrFinishedTask(for uuid: String) -> AttachmentUploadTask - + func attachmentUploadTaskOrFinishedTask(for uuid: String) -> AttachmentTask +// /// Removes an attachment for a specific primary key /// - Parameter attachmentUUID: primary key of the object func removeAttachment(_ attachmentUUID: String) @@ -101,7 +101,7 @@ import SwiftUI await worker.processHTMLAttachments(attachments) } - func attachmentUploadTaskOrFinishedTask(for uuid: String) -> AttachmentUploadTask { + func attachmentUploadTaskOrFinishedTask(for uuid: String) -> AttachmentTask { worker.attachmentUploadTaskOrFinishedTask(for: uuid) } diff --git a/Mail/Views/Thread/Message/Attachment/AttachmentsView.swift b/Mail/Views/Thread/Message/Attachment/AttachmentsView.swift index 4ccc5c575..3a498873a 100644 --- a/Mail/Views/Thread/Message/Attachment/AttachmentsView.swift +++ b/Mail/Views/Thread/Message/Attachment/AttachmentsView.swift @@ -28,14 +28,18 @@ import SwiftModalPresentation import SwiftUI struct AttachmentsView: View { - @ModalState private var previewedAttachment: Attachment? - @State private var downloadInProgress = false - @ModalState private var attachmentsURL: AttachmentsURL? + @LazyInjectService private var matomo: MatomoUtils @EnvironmentObject var mailboxManager: MailboxManager @ObservedRealmObject var message: Message - @LazyInjectService private var matomo: MatomoUtils + @ModalState private var previewedAttachment: Attachment? + @ModalState private var attachmentsURL: AttachmentsURL? + @State private var downloadInProgress = false + @State private var downloadProgressState: [String: Double] = [:] + @State private var trackDownloadTask: [String: Task] = [:] + @State private var isDownloadDisabled = false + @State private var showProgressCircle = false private var attachments: [Attachment] { return message.attachments.filter { $0.disposition == .attachment || $0.contentId == nil } @@ -64,19 +68,33 @@ struct AttachmentsView: View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: IKPadding.small) { ForEach(attachments) { attachment in + let progress = downloadProgressState[attachment.uuid] ?? 0 + Button { openAttachment(attachment) } label: { - AttachmentView(attachment: attachment) + AttachmentView( + attachment: attachment, + isDownloading: progress > 0 && progress < 1, + downloadProgress: progress + ) } + .disabled(isDownloadDisabled) } if let swissTransferAttachment = message.swissTransferAttachment { ForEach(swissTransferAttachment.files) { file in + let progress = downloadProgressState[file.uuid] ?? 0 + Button { downloadSwissTransferAttachment(stUuid: swissTransferAttachment.uuid, fileUuid: file.uuid) } label: { - AttachmentView(swissTransferFile: file) + AttachmentView( + swissTransferFile: file, + isDownloading: progress > 0 && progress < 1, + downloadProgress: progress + ) } + .disabled(isDownloadDisabled) } } } @@ -95,10 +113,21 @@ struct AttachmentsView: View { .frame(maxWidth: .infinity, alignment: .leading) .multilineTextAlignment(.leading) - Button(MailResourcesStrings.Localizable.buttonDownloadAll, action: downloadAllAttachments) + HStack { + let progress = downloadProgressState[message.swissTransferAttachment?.uuid ?? ""] ?? 0 + if showProgressCircle { + ProgressView(value: progress) + .progressViewStyle(MailCircularProgressViewStyle()) + } + Button { + downloadAllAttachments() + } label: { + Text(MailResourcesStrings.Localizable.buttonDownloadAll) + } + .disabled(isDownloadDisabled) .buttonStyle(.ikBorderless(isInlined: true)) .controlSize(.small) - .ikButtonLoading(downloadInProgress) + } } } .padding(.horizontal, value: .medium) @@ -114,34 +143,54 @@ struct AttachmentsView: View { DocumentPicker(pickerType: .exportContent(attachmentsURL.urls)) .ignoresSafeArea() } + .onDisappear { + for (_, task) in trackDownloadTask { + task.cancel() + } + } } private func openAttachment(_ attachment: Attachment) { + isDownloadDisabled = true matomo.track(eventWithCategory: .attachmentActions, name: "open") previewedAttachment = attachment if !FileManager.default.fileExists(atPath: attachment.getLocalURL(mailboxManager: mailboxManager).path) { - Task { - await mailboxManager.saveAttachmentLocally(attachment: attachment) + downloadProgressState[attachment.uuid] = 0.0 + trackDownloadTask[attachment.uuid] = Task { @MainActor in + await mailboxManager.saveAttachmentLocally(attachment: attachment) { progress in + Task { @MainActor in + downloadProgressState[attachment.uuid] = progress + } + } + downloadProgressState[attachment.uuid] = 1.0 } } + isDownloadDisabled = false } private func downloadSwissTransferAttachment(stUuid: String, fileUuid: String) { + isDownloadDisabled = true matomo.track(eventWithCategory: .attachmentActions, name: "openSwissTransfer") - Task { + trackDownloadTask[fileUuid] = Task { await tryOrDisplayError { let attachmentURL = try await mailboxManager.apiFetcher.downloadSwissTransferAttachment( - stUuid: stUuid, - fileUuid: fileUuid - ) + stUuid: stUuid, fileUuid: fileUuid + ) { progress in + Task { @MainActor in + downloadProgressState[fileUuid] = progress + } + } attachmentsURL = AttachmentsURL(urls: [attachmentURL]) } } + isDownloadDisabled = false } private func downloadAllAttachments() { downloadInProgress = true + isDownloadDisabled = true + showProgressCircle = true Task { await tryOrDisplayError { matomo.track(eventWithCategory: .message, name: "downloadAll") @@ -154,7 +203,11 @@ struct AttachmentsView: View { if let swissTransferAttachment = message.swissTransferAttachment { group.addTask { try await mailboxManager.apiFetcher - .downloadAllSwissTransferAttachment(stUuid: swissTransferAttachment.uuid) + .downloadAllSwissTransferAttachment(stUuid: swissTransferAttachment.uuid) { progress in + Task { @MainActor in + downloadProgressState[swissTransferAttachment.uuid] = progress + } + } } } for try await url in group { @@ -164,6 +217,8 @@ struct AttachmentsView: View { } } downloadInProgress = false + isDownloadDisabled = false + showProgressCircle = false } } } diff --git a/Mail/Views/Thread/Message/BodyImageProcessor.swift b/Mail/Views/Thread/Message/BodyImageProcessor.swift index 428d2dc7f..02bc4fc13 100644 --- a/Mail/Views/Thread/Message/BodyImageProcessor.swift +++ b/Mail/Views/Thread/Message/BodyImageProcessor.swift @@ -33,7 +33,7 @@ struct BodyImageProcessor { let base64Images: [ImageBase64AndMime?] = await attachments .concurrentMap(customConcurrency: Constants.concurrentNetworkCalls) { attachment in do { - let attachmentData = try await mailboxManager.attachmentData(attachment) + let attachmentData = try await mailboxManager.attachmentData(attachment, progressObserver: nil) // Skip compression on non static images types or already heic sources guard attachment.mimeType.contains("jpg") diff --git a/MailCore/API/MailApiFetcher/MailAPIFetcher+Attachment.swift b/MailCore/API/MailApiFetcher/MailAPIFetcher+Attachment.swift index b288ab3e3..f4716c1a8 100644 --- a/MailCore/API/MailApiFetcher/MailAPIFetcher+Attachment.swift +++ b/MailCore/API/MailApiFetcher/MailAPIFetcher+Attachment.swift @@ -21,31 +21,49 @@ import Foundation import InfomaniakCore public extension MailApiFetcher { - func attachment(attachment: Attachment) async throws -> Data { + func attachment(attachment: Attachment, progressObserver: ((Double) -> Void)? = nil) async throws -> Data { guard let resource = attachment.resource else { throw MailError.resourceError } let request = authenticatedRequest(.resource(resource)) + if let progressObserver { + Task { + for await progress in request.downloadProgress() { + progressObserver(progress.fractionCompleted) + print("attachement", progress.fractionCompleted) + } + } + } return try await request.serializingData().value } - func downloadAttachments(message: Message) async throws -> URL { - try await downloadResource(endpoint: .downloadAttachments(messageResource: message.resource)) + func downloadAttachments(message: Message, progressObserver: ((Double) -> Void)? = nil) async throws -> URL { + try await downloadResource( + endpoint: .downloadAttachments(messageResource: message.resource), + progressObserver: progressObserver + ) } func swissTransferAttachment(stUuid: String) async throws -> SwissTransferAttachment { try await perform(request: authenticatedRequest(.swissTransfer(stUuid: stUuid))) } - func downloadSwissTransferAttachment(stUuid: String, fileUuid: String) async throws -> URL { - try await downloadResource(endpoint: .downloadSwissTransferAttachment(stUuid: stUuid, fileUuid: fileUuid)) + func downloadSwissTransferAttachment(stUuid: String, fileUuid: String, + progressObserver: ((Double) -> Void)? = nil) async throws -> URL { + try await downloadResource( + endpoint: .downloadSwissTransferAttachment(stUuid: stUuid, fileUuid: fileUuid), + progressObserver: progressObserver + ) } - func downloadAllSwissTransferAttachment(stUuid: String) async throws -> URL { - try await downloadResource(endpoint: .downloadAllSwissTransferAttachments(stUuid: stUuid)) + func downloadAllSwissTransferAttachment(stUuid: String, progressObserver: ((Double) -> Void)? = nil) async throws -> URL { + try await downloadResource( + endpoint: .downloadAllSwissTransferAttachments(stUuid: stUuid), + progressObserver: progressObserver + ) } - private func downloadResource(endpoint: Endpoint) async throws -> URL { + private func downloadResource(endpoint: Endpoint, progressObserver: ((Double) -> Void)? = nil) async throws -> URL { let destination = DownloadRequest.suggestedDownloadDestination(options: [ .createIntermediateDirectories, .removePreviousFile @@ -54,6 +72,14 @@ public extension MailApiFetcher { endpoint.url, to: destination ) + if let progressObserver { + Task { + for await progress in download.downloadProgress() { + progressObserver(progress.fractionCompleted) + print("Download ressource: \(progress.fractionCompleted)") + } + } + } return try await download.serializingDownloadedFileURL().value } } diff --git a/MailCore/API/MailApiFetcher/MailApiFetchable.swift b/MailCore/API/MailApiFetcher/MailApiFetchable.swift index 7f758294e..80272b65a 100644 --- a/MailCore/API/MailApiFetcher/MailApiFetchable.swift +++ b/MailCore/API/MailApiFetcher/MailApiFetchable.swift @@ -58,7 +58,7 @@ public protocol MailApiCommonFetchable { func unstar(mailbox: Mailbox, messages: [Message]) async throws -> MessageActionResult - func downloadAttachments(message: Message) async throws -> URL + func downloadAttachments(message: Message, progressObserver: ((Double) -> Void)?) async throws -> URL func blockSender(message: Message) async throws -> NullableResponse @@ -112,7 +112,7 @@ public protocol MailApiExtendedFetchable { func delete(mailbox: Mailbox, messages: [Message]) async throws -> [Empty] - func attachment(attachment: Attachment) async throws -> Data + func attachment(attachment: Attachment, progressObserver: ((Double) -> Void)?) async throws -> Data func draft(mailbox: Mailbox, draftUuid: String) async throws -> Draft diff --git a/MailCore/Cache/Attachments/AttachmentsManagerWorker/AttachmentUploadTask.swift b/MailCore/Cache/Attachments/AttachmentsManagerWorker/AttachmentTask.swift similarity index 87% rename from MailCore/Cache/Attachments/AttachmentsManagerWorker/AttachmentUploadTask.swift rename to MailCore/Cache/Attachments/AttachmentsManagerWorker/AttachmentTask.swift index 1982a5f7c..fbb07a36a 100644 --- a/MailCore/Cache/Attachments/AttachmentsManagerWorker/AttachmentUploadTask.swift +++ b/MailCore/Cache/Attachments/AttachmentsManagerWorker/AttachmentTask.swift @@ -18,12 +18,12 @@ import Foundation -/// The progress of uploading an Attachment -@MainActor public final class AttachmentUploadTask: ObservableObject { +/// The progress of a task about an Attachment +@MainActor public final class AttachmentTask: ObservableObject { @Published public var progress: Double = 0 var task: Task? @Published public var error: MailError? - public var uploadDone: Bool { + public var attachmentTask: Bool { return progress >= 1 } diff --git a/MailCore/Cache/Attachments/AttachmentsManagerWorker/AttachmentsManagerWorker.swift b/MailCore/Cache/Attachments/AttachmentsManagerWorker/AttachmentsManagerWorker.swift index e8ff25db6..8e879fb92 100644 --- a/MailCore/Cache/Attachments/AttachmentsManagerWorker/AttachmentsManagerWorker.swift +++ b/MailCore/Cache/Attachments/AttachmentsManagerWorker/AttachmentsManagerWorker.swift @@ -58,7 +58,7 @@ public protocol AttachmentsManagerWorkable { func importAttachments(attachments: [Attachable], draft: Draft, disposition: AttachmentDisposition) async /// Lookup and return _or_ new object representing a finished task instead. - @MainActor func attachmentUploadTaskOrFinishedTask(for uuid: String) -> AttachmentUploadTask + @MainActor func attachmentUploadTaskOrFinishedTask(for uuid: String) -> AttachmentTask } /// Transactionable @@ -80,7 +80,7 @@ public final class AttachmentsManagerWorker { return formatter }() - var attachmentUploadTasks = SendableDictionary() + var attachmentUploadTasks = SendableDictionary() var detachedAttachments: [Attachment] { liveAttachments.map { $0.detached() } @@ -120,7 +120,7 @@ public final class AttachmentsManagerWorker { } func addLocalAttachment(attachment: Attachment) async -> Attachment? { - attachmentUploadTasks[attachment.uuid] = await AttachmentUploadTask() + attachmentUploadTasks[attachment.uuid] = await AttachmentTask() var detached: Attachment? try? writeTransaction { writableRealm in @@ -259,9 +259,9 @@ public final class AttachmentsManagerWorker { return remoteAttachment } - func attachmentUploadTaskOrCreate(for uuid: String) async -> AttachmentUploadTask { + func attachmentUploadTaskOrCreate(for uuid: String) async -> AttachmentTask { guard let attachment = attachmentUploadTask(for: uuid) else { - let newTask = await AttachmentUploadTask() + let newTask = await AttachmentTask() attachmentUploadTasks[uuid] = newTask return newTask } @@ -269,7 +269,7 @@ public final class AttachmentsManagerWorker { return attachment } - func attachmentUploadTask(for uuid: String) -> AttachmentUploadTask? { + func attachmentUploadTask(for uuid: String) -> AttachmentTask? { guard let attachment = attachmentUploadTasks[uuid] else { return nil } @@ -277,11 +277,11 @@ public final class AttachmentsManagerWorker { return attachment } - @MainActor private func updateAttachmentUploadTask(_ uploadTask: AttachmentUploadTask, task: Task?) { + @MainActor private func updateAttachmentUploadTask(_ uploadTask: AttachmentTask, task: Task?) { uploadTask.task = task } - @MainActor private func updateAttachmentUploadTaskProgress(_ uploadTask: AttachmentUploadTask, progress: Double) { + @MainActor private func updateAttachmentUploadTaskProgress(_ uploadTask: AttachmentTask, progress: Double) { uploadTask.progress = progress } @@ -318,7 +318,7 @@ extension AttachmentsManagerWorker: AttachmentsManagerWorkable { } public var allAttachmentsUploaded: Bool { - return attachmentUploadTasks.values.allSatisfy(\.uploadDone) + return attachmentUploadTasks.values.allSatisfy(\.attachmentTask) } public func setUpdateDelegate(_ updateDelegate: AttachmentsContentUpdatable) { @@ -427,9 +427,9 @@ extension AttachmentsManagerWorker: AttachmentsManagerWorkable { return allSanitizedHtml } - @MainActor public func attachmentUploadTaskOrFinishedTask(for uuid: String) -> AttachmentUploadTask { + @MainActor public func attachmentUploadTaskOrFinishedTask(for uuid: String) -> AttachmentTask { guard let attachment = attachmentUploadTask(for: uuid) else { - let finishedTask = AttachmentUploadTask() + let finishedTask = AttachmentTask() updateAttachmentUploadTaskProgress(finishedTask, progress: 1) attachmentUploadTasks[uuid] = finishedTask return finishedTask diff --git a/MailCore/Cache/MailboxManager/MailboxManageable.swift b/MailCore/Cache/MailboxManager/MailboxManageable.swift index 85580230d..43f7c2113 100644 --- a/MailCore/Cache/MailboxManager/MailboxManageable.swift +++ b/MailCore/Cache/MailboxManager/MailboxManageable.swift @@ -42,8 +42,8 @@ public protocol MailboxManagerMessageable { func fetchOneNewPage(folder: Folder) async throws -> Bool func fetchOneOldPage(folder: Folder) async throws -> Int? func message(message: Message) async throws - func attachmentData(_ attachment: Attachment) async throws -> Data - func saveAttachmentLocally(attachment: Attachment) async + func attachmentData(_ attachment: Attachment, progressObserver: ((Double) -> Void)?) async throws -> Data + func saveAttachmentLocally(attachment: Attachment, progressObserver: ((Double) -> Void)?) async func markAsSeen(message: Message, seen: Bool) async throws func move(messages: [Message], to folderRole: FolderRole, origin: Folder?) async throws -> UndoAction func move(messages: [Message], to folder: Folder, origin: Folder?) async throws -> UndoAction diff --git a/MailCore/Cache/MailboxManager/MailboxManager+Message.swift b/MailCore/Cache/MailboxManager/MailboxManager+Message.swift index 8d53b3148..e6d10f9dc 100644 --- a/MailCore/Cache/MailboxManager/MailboxManager+Message.swift +++ b/MailCore/Cache/MailboxManager/MailboxManager+Message.swift @@ -34,12 +34,12 @@ public extension MailboxManager { } } - func attachmentData(_ attachment: Attachment) async throws -> Data { + func attachmentData(_ attachment: Attachment, progressObserver: ((Double) -> Void)?) async throws -> Data { guard !Task.isCancelled else { throw CancellationError() } - let data = try await apiFetcher.attachment(attachment: attachment) + let data = try await apiFetcher.attachment(attachment: attachment, progressObserver: progressObserver) let safeAttachment = ThreadSafeReference(to: attachment) try? writeTransaction { writableRealm in @@ -53,9 +53,9 @@ public extension MailboxManager { return data } - func saveAttachmentLocally(attachment: Attachment) async { + func saveAttachmentLocally(attachment: Attachment, progressObserver: ((Double) -> Void)?) async { do { - let data = try await attachmentData(attachment) + let data = try await attachmentData(attachment, progressObserver: progressObserver) let url = attachment.getLocalURL(userId: account.userId, mailboxId: mailbox.mailboxId) let parentFolder = url.deletingLastPathComponent() if !FileManager.default.fileExists(atPath: parentFolder.path) { diff --git a/MailCoreUI/Components/AttachmentView.swift b/MailCoreUI/Components/AttachmentView.swift index 5819776be..b864952ac 100644 --- a/MailCoreUI/Components/AttachmentView.swift +++ b/MailCoreUI/Components/AttachmentView.swift @@ -27,38 +27,63 @@ public struct AttachmentView: View { private let icon: MailResourcesImages @ViewBuilder let accessory: () -> Content? + let isDownloading: Bool + let downloadProgress: Double public init( title: String, subtitle: String, icon: MailResourcesImages, + isDownloading: Bool = false, + downloadProgress: Double = 0, accessory: @escaping () -> Content? = { EmptyView() } ) { self.title = title self.subtitle = subtitle self.icon = icon + self.isDownloading = isDownloading + self.downloadProgress = downloadProgress self.accessory = accessory } - public init(attachment: Attachment, accessory: @escaping () -> Content? = { EmptyView() }) { + public init( + attachment: Attachment, + isDownloading: Bool = false, + downloadProgress: Double = 0, + accessory: @escaping () -> Content? = { EmptyView() } + ) { title = attachment.name subtitle = attachment.size.formatted(.defaultByteCount) icon = attachment.icon + self.isDownloading = isDownloading + self.downloadProgress = downloadProgress self.accessory = accessory } - public init(swissTransferFile: File, accessory: @escaping () -> Content? = { EmptyView() }) { + public init( + swissTransferFile: File, + isDownloading: Bool = false, + downloadProgress: Double = 0, + accessory: @escaping () -> Content? = { EmptyView() } + ) { title = swissTransferFile.name subtitle = swissTransferFile.size.formatted(.defaultByteCount) icon = swissTransferFile.icon + self.isDownloading = isDownloading + self.downloadProgress = downloadProgress self.accessory = accessory } public var body: some View { HStack(spacing: IKPadding.small) { - icon - .iconSize(.large) - .foregroundStyle(MailResourcesAsset.textSecondaryColor) + if isDownloading { + ProgressView(value: downloadProgress) + .progressViewStyle(MailCircularProgressViewStyle()) + } else { + icon + .iconSize(.large) + .foregroundStyle(MailResourcesAsset.textSecondaryColor) + } VStack(alignment: .leading, spacing: 0) { Text(title) diff --git a/MailCoreUI/Components/MailCircularProgressViewStyle.swift b/MailCoreUI/Components/MailCircularProgressViewStyle.swift new file mode 100644 index 000000000..fd1b5550a --- /dev/null +++ b/MailCoreUI/Components/MailCircularProgressViewStyle.swift @@ -0,0 +1,38 @@ +/* + Infomaniak Mail - iOS App + Copyright (C) 2024 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 MailResources +import SwiftUI + +public struct MailCircularProgressViewStyle: ProgressViewStyle { + public init() {} + + public func makeBody(configuration: Configuration) -> some View { + ZStack { + Circle() + .stroke(lineWidth: 3) + .opacity(0.1) + + Circle() + .trim(from: 0, to: configuration.fractionCompleted ?? 0) + .stroke(Color.accentColor, lineWidth: 3) + .rotationEffect(.degrees(-90)) + } + .frame(width: 16) + } +} diff --git a/MailTests/Folders/ITFolderListViewModel.swift b/MailTests/Folders/ITFolderListViewModel.swift index 34dff7107..7fa063622 100644 --- a/MailTests/Folders/ITFolderListViewModel.swift +++ b/MailTests/Folders/ITFolderListViewModel.swift @@ -153,9 +153,9 @@ struct MCKMailboxManageable_FolderListViewModel: MailboxManageable, MCKTransacti func message(message: MailCore.Message) async throws {} - func attachmentData(_ attachment: MailCore.Attachment) async throws -> Data { Data() } + func attachmentData(_ attachment: MailCore.Attachment, progressObserver: ((Double) -> Void)?) async throws -> Data { Data() } - func saveAttachmentLocally(attachment: MailCore.Attachment) async {} + func saveAttachmentLocally(attachment: MailCore.Attachment, progressObserver: ((Double) -> Void)?) async {} func markAsSeen(message: MailCore.Message, seen: Bool) async throws {} diff --git a/MailTests/Search/ITSearchViewModel.swift b/MailTests/Search/ITSearchViewModel.swift index a2ca59087..9cd281d05 100644 --- a/MailTests/Search/ITSearchViewModel.swift +++ b/MailTests/Search/ITSearchViewModel.swift @@ -183,9 +183,9 @@ final class MCKMailboxManageable_SearchViewModel: MailboxManageable, MCKTransact func message(message: MailCore.Message) async throws {} - func attachmentData(_ attachment: MailCore.Attachment) async throws -> Data { Data() } + func attachmentData(_ attachment: MailCore.Attachment, progressObserver: ((Double) -> Void)?) async throws -> Data { Data() } - func saveAttachmentLocally(attachment: MailCore.Attachment) async {} + func saveAttachmentLocally(attachment: MailCore.Attachment, progressObserver: ((Double) -> Void)?) async {} func markAsSeen(message: MailCore.Message, seen: Bool) async throws {}