// // CompositionAttachment.swift // Tusker // // Created by Shadowfacts on 3/14/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import Foundation import UIKit import UniformTypeIdentifiers final class CompositionAttachment: NSObject, Codable, ObservableObject { static let typeIdentifier = "space.vaccor.Tusker.composition-attachment" let id: UUID @Published var data: CompositionAttachmentData @Published var attachmentDescription: String init(data: CompositionAttachmentData, description: String = "") { self.id = UUID() self.data = data self.attachmentDescription = description } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(UUID.self, forKey: .id) self.data = try container.decode(CompositionAttachmentData.self, forKey: .data) self.attachmentDescription = try container.decode(String.self, forKey: .attachmentDescription) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(data, forKey: .data) try container.encode(attachmentDescription, forKey: .attachmentDescription) } static func ==(lhs: CompositionAttachment, rhs: CompositionAttachment) -> Bool { return lhs.id == rhs.id } enum CodingKeys: String, CodingKey { case id case data case attachmentDescription } } extension CompositionAttachment: Identifiable {} private let imageType = UTType.image.identifier private let mp4Type = UTType.mpeg4Movie.identifier private let quickTimeType = UTType.quickTimeMovie.identifier private let dataType = UTType.data.identifier private let gifType = UTType.gif.identifier extension CompositionAttachment: NSItemProviderWriting { static var writableTypeIdentifiersForItemProvider: [String] { [typeIdentifier] } func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? { if typeIdentifier == CompositionAttachment.typeIdentifier { do { completionHandler(try PropertyListEncoder().encode(self), nil) } catch { completionHandler(nil, error) } } else { completionHandler(nil, ItemProviderError.incompatibleTypeIdentifier) } return nil } enum ItemProviderError: Error { case incompatibleTypeIdentifier var localizedDescription: String { switch self { case .incompatibleTypeIdentifier: return "Cannot provide data for given type" } } } } extension CompositionAttachment: NSItemProviderReading { static var readableTypeIdentifiersForItemProvider: [String] { // todo: is there a better way of handling movies than manually adding all possible UTI types? // just using kUTTypeMovie doesn't work, because we need the actually type in order to get the file extension // without the file extension, getting the thumbnail and exporting the video for attachment upload fails [typeIdentifier] + UIImage.readableTypeIdentifiersForItemProvider + [mp4Type, quickTimeType] + NSURL.readableTypeIdentifiersForItemProvider } static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> CompositionAttachment { if typeIdentifier == CompositionAttachment.typeIdentifier { return try PropertyListDecoder().decode(CompositionAttachment.self, from: data) } else if typeIdentifier == gifType { return CompositionAttachment(data: .gif(data)) } else if UIImage.readableTypeIdentifiersForItemProvider.contains(typeIdentifier), let image = try? UIImage.object(withItemProviderData: data, typeIdentifier: typeIdentifier) { return CompositionAttachment(data: .image(image)) } else if let type = UTType(typeIdentifier), type == .mpeg4Movie || type == .quickTimeMovie { let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) let temporaryFileName = ProcessInfo().globallyUniqueString let fileExt = type.preferredFilenameExtension! let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(temporaryFileName).appendingPathExtension(fileExt) try data.write(to: temporaryFileURL) return CompositionAttachment(data: .video(temporaryFileURL)) } else if NSURL.readableTypeIdentifiersForItemProvider.contains(typeIdentifier), let url = try? NSURL.object(withItemProviderData: data, typeIdentifier: typeIdentifier) as URL { return CompositionAttachment(data: .video(url)) } else { throw ItemProviderError.incompatibleTypeIdentifier } } }