Skip to content

Commit

Permalink
feat: Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthieu-dgl committed Sep 19, 2024
1 parent f2f9850 commit 4a9e5f1
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 41 deletions.
6 changes: 3 additions & 3 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 @@ -83,7 +83,7 @@ struct AttachmentUploadCell: View {
}

#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
60 changes: 47 additions & 13 deletions Mail/Views/Thread/Message/Attachment/AttachmentsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ 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] = [:]

private var attachments: [Attachment] {
return message.attachments.filter { $0.disposition == .attachment || $0.contentId == nil }
Expand Down Expand Up @@ -63,18 +64,34 @@ 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
) {
if progress > 0 && progress < 1 {
CircleIndeterminateProgressView(progress: progress)
} else {
nil
}
}
}
}
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) {
if progress > 0 && progress < 1 {
CircleIndeterminateProgressView(progress: progress)
} else {
nil
}
}
}
}
}
Expand All @@ -93,11 +110,21 @@ struct AttachmentsView: View {
.textStyle(.bodySmallSecondary)
.frame(maxWidth: .infinity, alignment: .leading)
.multilineTextAlignment(.leading)

Button(MailResourcesStrings.Localizable.buttonDownloadAll, action: downloadAllAttachments)

HStack{
let progress = downloadProgressState[message.swissTransferAttachment?.uuid ?? ""] ?? 0
CircleIndeterminateProgressView(progress: progress)
.opacity(progress == 1 ? 0 : 1)
Button {
downloadAllAttachments()
} label: {
Text(MailResourcesStrings.Localizable.buttonDownloadAll)

}
.buttonStyle(.ikBorderless(isInlined: true))
.controlSize(.small)
.ikButtonLoading(downloadInProgress)
}

}
}
.padding(.horizontal, value: .medium)
Expand Down Expand Up @@ -131,9 +158,12 @@ struct AttachmentsView: View {
Task {
await tryOrDisplayError {
let attachmentURL = try await mailboxManager.apiFetcher.downloadSwissTransferAttachment(
stUuid: stUuid,
fileUuid: fileUuid
)
stUuid: stUuid, fileUuid: fileUuid
) { progress in
Task { @MainActor in
downloadProgressState[stUuid] = progress
}
}
attachmentsURL = AttachmentsURL(urls: [attachmentURL])
}
}
Expand All @@ -153,7 +183,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 Down
32 changes: 25 additions & 7 deletions MailCore/API/MailApiFetcher/MailAPIFetcher+Attachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,33 @@ public extension MailApiFetcher {
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 +64,14 @@ public extension MailApiFetcher {
endpoint.url,
to: destination
)
if let progressObserver {
Task {
for await progress in download.downloadProgress() {
progressObserver(progress.fractionCompleted)
print(progress.fractionCompleted)
}
}
}
return try await download.serializingDownloadedFileURL().value
}
}
2 changes: 1 addition & 1 deletion 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
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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -80,7 +80,7 @@ public final class AttachmentsManagerWorker {
return formatter
}()

var attachmentUploadTasks = SendableDictionary<String, AttachmentUploadTask>()
var attachmentUploadTasks = SendableDictionary<String, AttachmentTask>()

var detachedAttachments: [Attachment] {
liveAttachments.map { $0.detached() }
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -259,29 +259,29 @@ 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
}

return attachment
}

func attachmentUploadTask(for uuid: String) -> AttachmentUploadTask? {
func attachmentUploadTask(for uuid: String) -> AttachmentTask? {
guard let attachment = attachmentUploadTasks[uuid] else {
return nil
}

return attachment
}

@MainActor private func updateAttachmentUploadTask(_ uploadTask: AttachmentUploadTask, task: Task<String?, Never>?) {
@MainActor private func updateAttachmentUploadTask(_ uploadTask: AttachmentTask, task: Task<String?, Never>?) {
uploadTask.task = task
}

@MainActor private func updateAttachmentUploadTaskProgress(_ uploadTask: AttachmentUploadTask, progress: Double) {
@MainActor private func updateAttachmentUploadTaskProgress(_ uploadTask: AttachmentTask, progress: Double) {
uploadTask.progress = progress
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions MailCoreUI/Components/CircleIndeterminateProgressView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
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 <http://www.gnu.org/licenses/>.
*/

import SwiftUI

public struct CircleIndeterminateProgressView: View {
let progress: Double

public init(progress: Double) {
self.progress = progress
}

public var body: some View {
ZStack {

Check warning on line 29 in MailCoreUI/Components/CircleIndeterminateProgressView.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Indent code in accordance with the scope level. (indent)
Circle()

Check warning on line 30 in MailCoreUI/Components/CircleIndeterminateProgressView.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Indent code in accordance with the scope level. (indent)
.stroke(lineWidth: 5)

Check warning on line 31 in MailCoreUI/Components/CircleIndeterminateProgressView.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Indent code in accordance with the scope level. (indent)
.opacity(0.1)

Check warning on line 32 in MailCoreUI/Components/CircleIndeterminateProgressView.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Indent code in accordance with the scope level. (indent)
.foregroundColor(Color.gray)

Check warning on line 33 in MailCoreUI/Components/CircleIndeterminateProgressView.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Indent code in accordance with the scope level. (indent)
.frame(height: 20)

Check warning on line 34 in MailCoreUI/Components/CircleIndeterminateProgressView.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Indent code in accordance with the scope level. (indent)

Circle()

Check warning on line 36 in MailCoreUI/Components/CircleIndeterminateProgressView.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Indent code in accordance with the scope level. (indent)
.trim(from: 0.0, to: min(progress, 1.0))

Check warning on line 37 in MailCoreUI/Components/CircleIndeterminateProgressView.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Indent code in accordance with the scope level. (indent)
.stroke(style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))

Check warning on line 38 in MailCoreUI/Components/CircleIndeterminateProgressView.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Indent code in accordance with the scope level. (indent)
.foregroundColor(Color.accentColor)

Check warning on line 39 in MailCoreUI/Components/CircleIndeterminateProgressView.swift

View workflow job for this annotation

GitHub Actions / SwiftFormat

Indent code in accordance with the scope level. (indent)
.rotationEffect(Angle(degrees: 270.0))
.animation(.linear, value: progress)
.frame(height: 20)
}
}
}

#Preview {
CircleIndeterminateProgressView(progress: 1)
}

0 comments on commit 4a9e5f1

Please sign in to comment.