diff --git a/Tusker/Screens/Compose/CompositionAttachment.swift b/Tusker/Screens/Compose/CompositionAttachment.swift deleted file mode 100644 index 42c758ab8e..0000000000 --- a/Tusker/Screens/Compose/CompositionAttachment.swift +++ /dev/null @@ -1,185 +0,0 @@ -// -// CompositionAttachment.swift -// Tusker -// -// Created by Shadowfacts on 1/1/20. -// Copyright © 2020 Shadowfacts. All rights reserved. -// - -import UIKit -import Photos -import MobileCoreServices - -enum CompositionAttachmentData { - case asset(PHAsset) - case image(UIImage) - case video(URL) - - var type: AttachmentType { - switch self { - case let .asset(asset): - return asset.attachmentType! - case .image(_): - return .image - case .video(_): - return .video - } - } - - var isAsset: Bool { - switch self { - case .asset(_): - return true - default: - return false - } - } - - var canSaveToDraft: Bool { - switch self { - case .video(_): - return false - default: - return true - } - } - - func getData(completion: @escaping (Data, String) -> Void) { - switch self { - case let .image(image): - completion(image.pngData()!, "image/png") - case let .asset(asset): - if asset.mediaType == .image { - let options = PHImageRequestOptions() - options.version = .current - options.deliveryMode = .highQualityFormat - options.resizeMode = .none - options.isNetworkAccessAllowed = true - PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { (data, dataUTI, orientation, info) in - guard var data = data, let dataUTI = dataUTI else { fatalError() } - - let mimeType: String - if dataUTI == "public.heic" { - // neither Mastodon nor Pleroma handles HEIC well, so convert to JPEG - let image = CIImage(data: data)! - let context = CIContext() - let colorSpace = image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)! - data = context.jpegRepresentation(of: image, colorSpace: colorSpace, options: [:])! - mimeType = "image/jpeg" - } else { - mimeType = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, kUTTagClassMIMEType)!.takeRetainedValue() as String - } - - completion(data, mimeType) - } - } else if asset.mediaType == .video { - let options = PHVideoRequestOptions() - options.deliveryMode = .automatic - options.isNetworkAccessAllowed = true - options.version = .current - PHImageManager.default().requestExportSession(forVideo: asset, options: options, exportPreset: AVAssetExportPresetHighestQuality) { (exportSession, info) in - guard let exportSession = exportSession else { fatalError("failed to create export session") } - CompositionAttachmentData.exportVideoData(session: exportSession, completion: completion) - } - } else { - fatalError("assetType must be either image or video") - } - case let .video(url): - let asset = AVURLAsset(url: url) - guard let session = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else { - fatalError("failed to create export session") - } - CompositionAttachmentData.exportVideoData(session: session, completion: completion) - } - } - - private static func exportVideoData(session: AVAssetExportSession, completion: @escaping (Data, String) -> Void) { - session.outputFileType = .mp4 - session.outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("exported_video_\(UUID())").appendingPathExtension("mp4") - session.exportAsynchronously { - guard session.status == .completed else { fatalError("video export failed: \(String(describing: session.error))") } - do { - let data = try Data(contentsOf: session.outputURL!) - completion(data, "video/mp4") - } catch { - fatalError("Unable to load video: \(error)") - } - } - } - - enum AttachmentType { - case image, video - } -} - -extension PHAsset { - var attachmentType: CompositionAttachmentData.AttachmentType? { - switch self.mediaType { - case .image: - return .image - case .video: - return .video - default: - return nil - } - } -} - -extension CompositionAttachmentData: Codable { - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - switch self { - case let .asset(asset): - try container.encode("asset", forKey: .type) - try container.encode(asset.localIdentifier, forKey: .assetIdentifier) - case let .image(image): - try container.encode("image", forKey: .type) - try container.encode(image.pngData()!, forKey: .imageData) - case .video(_): - throw EncodingError.invalidValue(self, EncodingError.Context(codingPath: [], debugDescription: "video CompositionAttachments cannot be encoded")) - } - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - switch try container.decode(String.self, forKey: .type) { - case "asset": - let identifier = try container.decode(String.self, forKey: .assetIdentifier) - guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil).firstObject else { - throw DecodingError.dataCorruptedError(forKey: .assetIdentifier, in: container, debugDescription: "Could not fetch asset with local identifier") - } - self = .asset(asset) - case "image": - guard let image = UIImage(data: try container.decode(Data.self, forKey: .imageData)) else { - throw DecodingError.dataCorruptedError(forKey: .imageData, in: container, debugDescription: "Could not decode UIImage from image data") - } - self = .image(image) - default: - throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "CompositionAttachment type must be one of 'image' or 'asset'") - } - } - - enum CodingKeys: CodingKey { - case type - case imageData - /// The local identifier of the PHAsset for this attachment - case assetIdentifier - } -} - -extension CompositionAttachmentData: Equatable { - static func ==(lhs: CompositionAttachmentData, rhs: CompositionAttachmentData) -> Bool { - switch (lhs, rhs) { - case let (.asset(a), .asset(b)): - return a.localIdentifier == b.localIdentifier - case let (.image(a), .image(b)): - return a == b - case let (.video(a), .video(b)): - return a == b - default: - return false - } - } -}