Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a progress view when downloading swistransfer attachments #1547

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
8 changes: 4 additions & 4 deletions Mail/Views/New Message/Attachments/AttachmentUploadCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 */
}
}
6 changes: 3 additions & 3 deletions Mail/Views/New Message/Attachments/AttachmentsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}

Expand Down
85 changes: 70 additions & 15 deletions Mail/Views/Thread/Message/Attachment/AttachmentsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Void, Error>] = [:]
@State private var isDownloadDisabled = false
@State private var showProgressCircle = false

private var attachments: [Attachment] {
return message.attachments.filter { $0.disposition == .attachment || $0.contentId == nil }
Expand Down Expand Up @@ -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)
}
}
}
Expand All @@ -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)
Expand All @@ -114,34 +143,54 @@ struct AttachmentsView: View {
DocumentPicker(pickerType: .exportContent(attachmentsURL.urls))
.ignoresSafeArea()
}
.onDisappear {
for (_, task) in trackDownloadTask {
task.cancel()
}
}
Matthieu-dgl marked this conversation as resolved.
Show resolved Hide resolved
}

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")
Expand All @@ -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 {
Expand All @@ -164,6 +217,8 @@ struct AttachmentsView: View {
}
}
downloadInProgress = false
isDownloadDisabled = false
showProgressCircle = false
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Mail/Views/Thread/Message/BodyImageProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
42 changes: 34 additions & 8 deletions MailCore/API/MailApiFetcher/MailAPIFetcher+Attachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}
4 changes: 2 additions & 2 deletions MailCore/API/MailApiFetcher/MailApiFetchable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String?, Never>?
@Published public var error: MailError?
public var uploadDone: Bool {
public var attachmentTask: Bool {
return progress >= 1
}

Expand Down
Loading
Loading