diff --git a/Mail/Views/Attachment/AttachmentPreview.swift b/Mail/Views/Attachment/AttachmentPreview.swift index 68d39d17e..6ee1c3a67 100644 --- a/Mail/Views/Attachment/AttachmentPreview.swift +++ b/Mail/Views/Attachment/AttachmentPreview.swift @@ -37,8 +37,11 @@ struct AttachmentPreview: View { var body: some View { NavigationView { Group { - if let url = attachment.localUrl, FileManager.default.fileExists(atPath: url.path) { - PreviewController(url: url) + if FileManager.default.fileExists(atPath: attachment.localUrl.path) { + PreviewController(url: attachment.localUrl) + } else if let temporaryLocalUrl = attachment.temporaryLocalUrl, + FileManager.default.fileExists(atPath: temporaryLocalUrl) { + PreviewController(url: URL(fileURLWithPath: temporaryLocalUrl)) } else { ProgressView() } @@ -51,9 +54,8 @@ struct AttachmentPreview: View { ToolbarItemGroup(placement: .bottomBar) { Button { - guard let attachmentURL = attachment.localUrl else { return } matomo.track(eventWithCategory: .message, name: "download") - downloadedAttachmentURL = IdentifiableURL(url: attachmentURL) + downloadedAttachmentURL = IdentifiableURL(url: attachment.localUrl) } label: { Label { Text(MailResourcesStrings.Localizable.buttonDownload) diff --git a/Mail/Views/New Message/Attachments/AttachmentUploadCell.swift b/Mail/Views/New Message/Attachments/AttachmentUploadCell.swift index 7ee0004e1..b55d37b38 100644 --- a/Mail/Views/New Message/Attachments/AttachmentUploadCell.swift +++ b/Mail/Views/New Message/Attachments/AttachmentUploadCell.swift @@ -23,28 +23,32 @@ import MailResources import SwiftUI struct AttachmentUploadCell: View { - private let detachedAttachment: Attachment + @EnvironmentObject private var mailboxManager: MailboxManager + + private let attachment: Attachment private let attachmentRemoved: ((Attachment) -> Void)? @ObservedObject var uploadTask: AttachmentUploadTask + @State private var previewedAttachment: Attachment? + init(uploadTask: AttachmentUploadTask, attachment: Attachment, attachmentRemoved: ((Attachment) -> Void)?) { self.uploadTask = uploadTask - detachedAttachment = attachment.detached() + self.attachment = attachment self.attachmentRemoved = attachmentRemoved } var body: some View { AttachmentView( - attachment: detachedAttachment, - subtitle: uploadTask.error != nil ? (uploadTask.error!.errorDescription ?? "") : detachedAttachment.size + attachment: attachment.detached(), + subtitle: uploadTask.error != nil ? (uploadTask.error!.errorDescription ?? "") : attachment.size .formatted(.defaultByteCount) ) { Button { if let attachmentRemoved { @InjectService var matomo: MatomoUtils matomo.track(eventWithCategory: .attachmentActions, name: "delete") - attachmentRemoved(detachedAttachment) + attachmentRemoved(attachment) } } label: { IKIcon(MailResourcesAsset.close, size: .small) @@ -56,6 +60,22 @@ struct AttachmentUploadCell: View { IndeterminateProgressView(indeterminate: uploadTask.progress == 0, progress: uploadTask.progress) .opacity(uploadTask.progress == 1 ? 0 : 1) } + .onTapGesture { + showAttachmentPreview() + } + .sheet(item: $previewedAttachment) { previewedAttachment in + AttachmentPreview(attachment: previewedAttachment) + } + } + + private func showAttachmentPreview() { + guard let attachment = attachment.thaw()?.freezeIfNeeded() else { return } + previewedAttachment = attachment + if !FileManager.default.fileExists(atPath: attachment.localUrl.path) { + Task { + await mailboxManager.saveAttachmentLocally(attachment: attachment) + } + } } } diff --git a/Mail/Views/Thread/Message/Attachment/AttachmentsView.swift b/Mail/Views/Thread/Message/Attachment/AttachmentsView.swift index b912a5009..79ce5e5ce 100644 --- a/Mail/Views/Thread/Message/Attachment/AttachmentsView.swift +++ b/Mail/Views/Thread/Message/Attachment/AttachmentsView.swift @@ -48,7 +48,7 @@ struct AttachmentsView: View { Button { matomo.track(eventWithCategory: .attachmentActions, name: "open") previewedAttachment = attachment - if !FileManager.default.fileExists(atPath: attachment.localUrl?.path ?? "") { + if !FileManager.default.fileExists(atPath: attachment.localUrl.path) { Task { await mailboxManager.saveAttachmentLocally(attachment: attachment) } diff --git a/MailCore/Cache/AttachmentsManagerWorker/AttachmentsManagerWorker.swift b/MailCore/Cache/AttachmentsManagerWorker/AttachmentsManagerWorker.swift index 6f4f71450..317ee2e92 100644 --- a/MailCore/Cache/AttachmentsManagerWorker/AttachmentsManagerWorker.swift +++ b/MailCore/Cache/AttachmentsManagerWorker/AttachmentsManagerWorker.swift @@ -253,6 +253,7 @@ public final class AttachmentsManagerWorker { self.updateAttachmentUploadTaskProgress(attachment, progress: progress) } } + remoteAttachment.temporaryLocalUrl = url.path await updateAttachment(oldAttachment: localAttachment, newAttachment: remoteAttachment) return remoteAttachment } @@ -308,7 +309,11 @@ extension AttachmentsManagerWorker: AttachmentsManagerWorkable { guard let liveDraft else { return [] } - return liveDraft.attachments.filter { $0.contentId == nil && !$0.isInvalidated }.toArray() + return liveDraft.attachments.filter { attachment in + guard !attachment.isInvalidated else { return false } + guard let contentId = attachment.contentId else { return true } + return contentId.isEmpty + }.toArray() } public var allAttachmentsUploaded: Bool { diff --git a/MailCore/Cache/MailboxManager/MailboxManager+Message.swift b/MailCore/Cache/MailboxManager/MailboxManager+Message.swift index 6af8e3fdf..58c63bf1b 100644 --- a/MailCore/Cache/MailboxManager/MailboxManager+Message.swift +++ b/MailCore/Cache/MailboxManager/MailboxManager+Message.swift @@ -314,13 +314,12 @@ public extension MailboxManager { func saveAttachmentLocally(attachment: Attachment) async { do { let data = try await attachmentData(attachment) - if let url = attachment.localUrl { - let parentFolder = url.deletingLastPathComponent() - if !FileManager.default.fileExists(atPath: parentFolder.path) { - try FileManager.default.createDirectory(at: parentFolder, withIntermediateDirectories: true) - } - try data.write(to: url) + let url = attachment.localUrl + let parentFolder = url.deletingLastPathComponent() + if !FileManager.default.fileExists(atPath: parentFolder.path) { + try FileManager.default.createDirectory(at: parentFolder, withIntermediateDirectories: true) } + try data.write(to: url) } catch { // Handle error print("Failed to save attachment: \(error)") diff --git a/MailCore/Cache/MailboxManager/MailboxManager.swift b/MailCore/Cache/MailboxManager/MailboxManager.swift index b78af00a7..11f3c1a22 100644 --- a/MailCore/Cache/MailboxManager/MailboxManager.swift +++ b/MailCore/Cache/MailboxManager/MailboxManager.swift @@ -73,7 +73,7 @@ public final class MailboxManager: ObservableObject, MailboxManageable, RealmAcc let realmName = "\(mailbox.userId)-\(mailbox.mailboxId).realm" realmConfiguration = Realm.Configuration( fileURL: MailboxManager.constants.rootDocumentsURL.appendingPathComponent(realmName), - schemaVersion: 23, + schemaVersion: 24, migrationBlock: { migration, oldSchemaVersion in // No migration needed from 0 to 16 if oldSchemaVersion < 17 { diff --git a/MailCore/Models/Attachment.swift b/MailCore/Models/Attachment.swift index 7a60d67b1..4528a53ef 100644 --- a/MailCore/Models/Attachment.swift +++ b/MailCore/Models/Attachment.swift @@ -37,14 +37,14 @@ public class Attachment: /* Hashable, */ EmbeddedObject, Codable, Identifiable { @Persisted public var driveUrl: String? @Persisted(originProperty: "attachments") var parentLink: LinkingObjects @Persisted public var saved = false + @Persisted public var temporaryLocalUrl: String? public var parent: Message? { return parentLink.first } - public var localUrl: URL? { - guard let message = parent else { return nil } - return FileManager.default.temporaryDirectory.appendingPathComponent("\(message.uid)_\(partId)/\(name)") + public var localUrl: URL { + return FileManager.default.temporaryDirectory.appendingPathComponent("\(uuid)_\(partId)/\(name)") } public var uti: UTType? { @@ -159,6 +159,7 @@ public class Attachment: /* Hashable, */ EmbeddedObject, Codable, Identifiable { contentId = remoteAttachment.contentId resource = remoteAttachment.resource driveUrl = remoteAttachment.driveUrl + temporaryLocalUrl = remoteAttachment.temporaryLocalUrl } }