mirror of
https://github.com/ProtonMail/ios-mail.git
synced 2026-06-14 09:54:45 +00:00
114 lines
4.1 KiB
Swift
114 lines
4.1 KiB
Swift
//
|
|
// Copyright (c) 2025 Proton Technologies AG
|
|
//
|
|
// This file is part of Proton Mail.
|
|
//
|
|
// Proton Mail 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.
|
|
//
|
|
// Proton Mail 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 Proton Mail. If not, see https://www.gnu.org/licenses/.
|
|
|
|
import InboxComposer
|
|
import UIKit
|
|
import UniformTypeIdentifiers
|
|
import proton_app_uniffi
|
|
|
|
public final class DraftStubWriter: Sendable {
|
|
typealias InitDraft = @Sendable () throws -> String
|
|
typealias SaveDraft = @Sendable (IosShareExtDraft) throws -> Void
|
|
|
|
private let initDraft: InitDraft
|
|
private let saveDraft: SaveDraft
|
|
|
|
init(initDraft: @escaping InitDraft, saveDraft: @escaping SaveDraft) {
|
|
self.initDraft = initDraft
|
|
self.saveDraft = saveDraft
|
|
}
|
|
|
|
public convenience init() {
|
|
let mailCacheDir = FileManager.default.sharedCacheDirectory.path()
|
|
|
|
self.init(
|
|
initDraft: { try iosShareExtInitDraft(mailCacheDir: mailCacheDir).get() },
|
|
saveDraft: { try iosShareExtSaveDraft(mailCacheDir: mailCacheDir, draft: $0).get() }
|
|
)
|
|
}
|
|
|
|
public func createDraftStub(basedOn sharedContent: SharedContent) async throws {
|
|
let attachmentDirectory = try initDraft()
|
|
|
|
let (inlineAttachments, nonInlineAttachments) = try await segregate(
|
|
attachments: sharedContent.attachments,
|
|
andStoreIn: .init(filePath: attachmentDirectory)
|
|
)
|
|
|
|
let draftStub = IosShareExtDraft(
|
|
subject: sharedContent.subject,
|
|
body: sharedContent.body,
|
|
inlineAttachments: inlineAttachments,
|
|
attachments: nonInlineAttachments
|
|
)
|
|
|
|
try saveDraft(draftStub)
|
|
}
|
|
|
|
private func segregate(
|
|
attachments: [NSItemProvider],
|
|
andStoreIn attachmentDirectory: URL,
|
|
) async throws -> (inline: [IosShareExtAttachment], nonInline: [IosShareExtAttachment]) {
|
|
var inlineAttachments: [IosShareExtAttachment] = []
|
|
var nonInlineAttachments: [IosShareExtAttachment] = []
|
|
|
|
for attachment in attachments {
|
|
let url = try await saveFileRepresentation(of: attachment, intoDirectory: attachmentDirectory)
|
|
let path = url.path(percentEncoded: false)
|
|
let attachmentInfo = IosShareExtAttachment(path: path, name: attachment.suggestedName)
|
|
|
|
if attachment.hasImageRepresentation {
|
|
if try url.isScreenshotInPlistFormat() {
|
|
try await extractImageContent(of: attachment, into: url)
|
|
}
|
|
|
|
inlineAttachments.append(attachmentInfo)
|
|
} else {
|
|
nonInlineAttachments.append(attachmentInfo)
|
|
}
|
|
}
|
|
|
|
return (inlineAttachments, nonInlineAttachments)
|
|
}
|
|
|
|
private func saveFileRepresentation(of attachment: NSItemProvider, intoDirectory persistentDirectory: URL) async throws -> URL {
|
|
try await attachment.performOnFileRepresentation { shortLivedURL in
|
|
try FileManager.default.moveToUniqueURL(file: shortLivedURL, to: persistentDirectory)
|
|
}
|
|
}
|
|
|
|
private func extractImageContent(of attachment: NSItemProvider, into url: URL) async throws {
|
|
let image = try await attachment.loadItem(forTypeIdentifier: UTType.image.identifier) as? UIImage
|
|
try image?.jpegData(compressionQuality: JPEG.compressionQuality)?.write(to: url)
|
|
}
|
|
}
|
|
|
|
private extension URL {
|
|
func isScreenshotInPlistFormat() throws -> Bool {
|
|
let plistFileSignature = "bplist00".data(using: .ascii)!
|
|
let handle = try FileHandle(forReadingFrom: self)
|
|
|
|
defer {
|
|
try? handle.close()
|
|
}
|
|
|
|
let fileSignature = try handle.read(upToCount: plistFileSignature.count)
|
|
return fileSignature == plistFileSignature
|
|
}
|
|
}
|