diff --git a/Packages/StatusKit/Sources/StatusKit/Editor/Components/UTTypeSupported.swift b/Packages/StatusKit/Sources/StatusKit/Editor/Components/UTTypeSupported.swift index 6f1d3ecb..9087cea3 100644 --- a/Packages/StatusKit/Sources/StatusKit/Editor/Components/UTTypeSupported.swift +++ b/Packages/StatusKit/Sources/StatusKit/Editor/Components/UTTypeSupported.swift @@ -7,97 +7,27 @@ import UniformTypeIdentifiers extension StatusEditor { @MainActor - enum UTTypeSupported: String, CaseIterable { - case url = "public.url" - case text = "public.text" - case plaintext = "public.plain-text" - case image = "public.image" - case jpeg = "public.jpeg" - case png = "public.png" - case tiff = "public.tiff" - - case video = "public.video" - case movie = "public.movie" - case mp4 = "public.mpeg-4" - case gif = "public.gif" - case gif2 = "com.compuserve.gif" - case quickTimeMovie = "com.apple.quicktime-movie" - case adobeRawImage = "com.adobe.raw-image" - - case uiimage = "com.apple.uikit.image" - - // Have to implement this manually here due to compiler not implicitly - // inserting `nonisolated`, which leads to a warning: - // - // Main actor-isolated static property 'allCases' cannot be used to - // satisfy nonisolated protocol requirement - // - public nonisolated static var allCases: [UTTypeSupported] { - [.url, .text, .plaintext, .image, .jpeg, .png, .tiff, .video, - .movie, .mp4, .gif, .gif2, .quickTimeMovie, .uiimage, .adobeRawImage] - } - - static func types() -> [UTType] { - [.url, .text, .plainText, .image, .jpeg, .png, .tiff, .video, .mpeg4Movie, .gif, .movie, .quickTimeMovie] - } - - var isVideo: Bool { - switch self { - case .video, .movie, .mp4, .quickTimeMovie: - true - default: - false - } - } - - var isGif: Bool { - switch self { - case .gif, .gif2: - true - default: - false - } - } - + struct UTTypeSupported { + let value: String + func loadItemContent(item: NSItemProvider) async throws -> Any? { - // Many warnings here about non-sendable type `[AnyHashable: Any]?` crossing - // actor boundaries. Many Radars have been filed. - if isVideo, let transferable = await getVideoTransferable(item: item) { + if let transferable = await getVideoTransferable(item: item) { return transferable - } else if isGif, let transferable = await getGifTransferable(item: item) { + } else if let transferable = await getGifTransferable(item: item) { return transferable - } - let compressor = Compressor() - let result = try await item.loadItem(forTypeIdentifier: rawValue) - if self == .jpeg || self == .png || self == .tiff || self == .image || self == .uiimage || self == .adobeRawImage { - if let image = result as? UIImage, - let compressedData = try? await compressor.compressImageForUpload(image), - let compressedImage = UIImage(data: compressedData) - { - return compressedImage - } else if let imageURL = result as? URL, - let compressedData = await compressor.compressImageFrom(url: imageURL), - let image = UIImage(data: compressedData) - { - return image - } else if let data = result as? Data, - let image = UIImage(data: data) - { + } else if let transferable = await getImageTansferable(item: item) { + return transferable + } else { + let result = try await item.loadItem(forTypeIdentifier: value) + if let url = result as? URL { + return url.absoluteString + } else if let text = result as? String { + return text + } else if let image = result as? UIImage { return image } } - if let transferable = await getImageTansferable(item: item) { - return transferable - } - if let url = result as? URL { - return url.absoluteString - } else if let text = result as? String { - return text - } else if let image = result as? UIImage { - return image - } else { - return nil - } + return nil } private func getVideoTransferable(item: NSItemProvider) async -> MovieFileTranseferable? { @@ -142,44 +72,65 @@ extension StatusEditor { } extension StatusEditor { - struct MovieFileTranseferable: Transferable { + final class MovieFileTranseferable: Transferable { let url: URL + init(url: URL) { + self.url = url + _ = url.startAccessingSecurityScopedResource() + } + + deinit { + url.stopAccessingSecurityScopedResource() + } + static var transferRepresentation: some TransferRepresentation { - FileRepresentation(contentType: .movie) { movie in - SentTransferredFile(movie.url) - } importing: { received in - Self(url: received.localURL) + FileRepresentation(importedContentType: .movie) { receivedTransferrable in + return MovieFileTranseferable(url: receivedTransferrable.localURL) } } } - struct GifFileTranseferable: Transferable { + final class GifFileTranseferable: Transferable { let url: URL + init(url: URL) { + self.url = url + _ = url.startAccessingSecurityScopedResource() + } + + deinit { + url.stopAccessingSecurityScopedResource() + } + var data: Data? { try? Data(contentsOf: url) } static var transferRepresentation: some TransferRepresentation { - FileRepresentation(contentType: .gif) { gif in - SentTransferredFile(gif.url) - } importing: { received in - Self(url: received.localURL) + FileRepresentation(importedContentType: .gif) { receivedTransferrable in + return GifFileTranseferable(url: receivedTransferrable.localURL) } } } } public extension StatusEditor { - struct ImageFileTranseferable: Transferable, Sendable { + final class ImageFileTranseferable: Transferable, Sendable { public let url: URL + + init(url: URL) { + self.url = url + _ = url.startAccessingSecurityScopedResource() + } + + deinit { + url.stopAccessingSecurityScopedResource() + } public static var transferRepresentation: some TransferRepresentation { - FileRepresentation(contentType: .image) { image in - SentTransferredFile(image.url) - } importing: { received in - Self(url: received.localURL) + FileRepresentation(importedContentType: .image) { receivedTransferrable in + return ImageFileTranseferable(url: receivedTransferrable.localURL) } } } @@ -187,6 +138,9 @@ public extension StatusEditor { public extension ReceivedTransferredFile { var localURL: URL { + if self.isOriginalFile { + return file + } let copy = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(self.file.pathExtension)") try? FileManager.default.copyItem(at: self.file, to: copy) return copy diff --git a/Packages/StatusKit/Sources/StatusKit/Editor/MainView.swift b/Packages/StatusKit/Sources/StatusKit/Editor/MainView.swift index 5f9b0534..8c3b94e7 100644 --- a/Packages/StatusKit/Sources/StatusKit/Editor/MainView.swift +++ b/Packages/StatusKit/Sources/StatusKit/Editor/MainView.swift @@ -125,7 +125,7 @@ extension StatusEditor { } } } - .onDrop(of: StatusEditor.UTTypeSupported.types(), delegate: focusedSEVM) + .onDrop(of: [.image, .video, .gif], delegate: focusedSEVM) .onChange(of: currentAccount.account?.id) { mainSEVM.currentAccount = currentAccount.account for p in followUpSEVMs { diff --git a/Packages/StatusKit/Sources/StatusKit/Editor/ViewModel.swift b/Packages/StatusKit/Sources/StatusKit/Editor/ViewModel.swift index 56df0fdf..d1f2f7d7 100644 --- a/Packages/StatusKit/Sources/StatusKit/Editor/ViewModel.swift +++ b/Packages/StatusKit/Sources/StatusKit/Editor/ViewModel.swift @@ -451,9 +451,8 @@ extension StatusEditor { Task { var initialText: String = "" for item in items { - if let identifier = item.registeredTypeIdentifiers.first, - let handledItemType = UTTypeSupported(rawValue: identifier) - { + if let identifier = item.registeredTypeIdentifiers.first { + let handledItemType = UTTypeSupported(value: identifier) do { let compressor = Compressor() let content = try await handledItemType.loadItemContent(item: item) @@ -900,7 +899,7 @@ extension StatusEditor { extension StatusEditor.ViewModel: DropDelegate { public func performDrop(info: DropInfo) -> Bool { - let item = info.itemProviders(for: StatusEditor.UTTypeSupported.types()) + let item = info.itemProviders(for: [.image, .video, .gif]) processItemsProvider(items: item) return true }