| 1 | import Foundation |
| 2 | import UniformTypeIdentifiers |
| 3 | |
| 4 | // Dragging a photo/video OUT of the feed onto the Finder/desktop. An |
| 5 | // NSItemProvider file representation rather than Transferable's |
| 6 | // FileRepresentation: the latter's drag promise is named after the TYPE |
| 7 | // (a file called "Data" lands on the desktop) — the provider's suggestedName |
| 8 | // carries the attachment's real filename per item. The load handler runs only |
| 9 | // when a drop actually lands: fetch + decrypt (usually a BlobDataCache hit), |
| 10 | // write to a temp file, hand it over. Writing plaintext here is the same |
| 11 | // deliberate-user-export as the Save flows. |
| 12 | public struct AttachmentDragItem { |
| 13 | public let filename: String |
| 14 | public let contentType: UTType |
| 15 | public let fetch: @Sendable () async -> Data? |
| 16 | |
| 17 | public init(filename: String, mimeType: String?, isImage: Bool, |
| 18 | fetch: @escaping @Sendable () async -> Data?) { |
| 19 | self.filename = filename |
| 20 | self.contentType = mimeType.flatMap { UTType(mimeType: $0) } |
| 21 | ?? (isImage ? .jpeg : .mpeg4Movie) |
| 22 | self.fetch = fetch |
| 23 | } |
| 24 | |
| 25 | public func makeProvider() -> NSItemProvider { |
| 26 | let provider = NSItemProvider() |
| 27 | provider.suggestedName = filename |
| 28 | let filename = filename |
| 29 | let fetch = fetch |
| 30 | provider.registerFileRepresentation(forTypeIdentifier: contentType.identifier, |
| 31 | fileOptions: [], visibility: .all) { completion in |
| 32 | Task { |
| 33 | guard let data = await fetch() else { |
| 34 | completion(nil, false, CocoaError(.fileNoSuchFile)) |
| 35 | return |
| 36 | } |
| 37 | do { |
| 38 | let dir = FileManager.default.temporaryDirectory |
| 39 | .appending(path: UUID().uuidString, directoryHint: .isDirectory) |
| 40 | try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) |
| 41 | let url = dir.appending(path: filename) |
| 42 | try data.write(to: url) |
| 43 | completion(url, false, nil) |
| 44 | } catch { |
| 45 | completion(nil, false, error) |
| 46 | } |
| 47 | } |
| 48 | return nil |
| 49 | } |
| 50 | return provider |
| 51 | } |
| 52 | } |
| 53 | |