mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-05-19 08:48:16 +00:00
d1209e6704
Co-authored-by: Jim Dovey <jimdovey@apple.com>
220 lines
6.5 KiB
Swift
220 lines
6.5 KiB
Swift
import AVFoundation
|
|
import Foundation
|
|
import PhotosUI
|
|
import SwiftUI
|
|
import UIKit
|
|
import UniformTypeIdentifiers
|
|
|
|
@MainActor
|
|
enum StatusEditorUTTypeSupported: 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 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
|
|
//
|
|
nonisolated public static var allCases: [StatusEditorUTTypeSupported] {
|
|
[.url, .text, .plaintext, .image, .jpeg, .png, .tiff, .video,
|
|
.movie, .mp4, .gif, .gif2, .quickTimeMovie, .uiimage]
|
|
}
|
|
|
|
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:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
var isGif: Bool {
|
|
switch self {
|
|
case .gif, .gif2:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func loadItemContent(item: NSItemProvider) async throws -> Any? {
|
|
// Many warnings here about non-sendable type `[AnyHashable: Any]?` crossing
|
|
// actor boundaries. Many Radars have been filed.
|
|
let result = try await item.loadItem(forTypeIdentifier: rawValue)
|
|
if isVideo, let transferable = await getVideoTransferable(item: item) {
|
|
return transferable
|
|
} else if isGif, let transferable = await getGifTransferable(item: item) {
|
|
return transferable
|
|
}
|
|
if self == .jpeg || self == .png || self == .tiff || self == .image || self == .uiimage {
|
|
if let image = result as? UIImage {
|
|
return image
|
|
} else if let imageURL = result as? URL,
|
|
let data = try? Data(contentsOf: imageURL),
|
|
let image = UIImage(data: data)
|
|
{
|
|
return image
|
|
} else if let data = result as? Data,
|
|
let image = UIImage(data: data)
|
|
{
|
|
return image
|
|
} else 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
|
|
}
|
|
}
|
|
|
|
private func getVideoTransferable(item: NSItemProvider) async -> MovieFileTranseferable? {
|
|
return await withCheckedContinuation { continuation in
|
|
_ = item.loadTransferable(type: MovieFileTranseferable.self) { result in
|
|
switch result {
|
|
case let .success(success):
|
|
continuation.resume(with: .success(success))
|
|
case .failure:
|
|
continuation.resume(with: .success(nil))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func getGifTransferable(item: NSItemProvider) async -> GifFileTranseferable? {
|
|
return await withCheckedContinuation { continuation in
|
|
_ = item.loadTransferable(type: GifFileTranseferable.self) { result in
|
|
switch result {
|
|
case let .success(success):
|
|
continuation.resume(with: .success(success))
|
|
case .failure:
|
|
continuation.resume(with: .success(nil))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func getImageTansferable(item: NSItemProvider) async -> ImageFileTranseferable? {
|
|
return await withCheckedContinuation { continuation in
|
|
_ = item.loadTransferable(type: ImageFileTranseferable.self) { result in
|
|
switch result {
|
|
case let .success(success):
|
|
continuation.resume(with: .success(success))
|
|
case .failure:
|
|
continuation.resume(with: .success(nil))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct MovieFileTranseferable: Transferable {
|
|
private let url: URL
|
|
var compressedVideoURL: URL? {
|
|
get async {
|
|
await withCheckedContinuation { continuation in
|
|
let urlAsset = AVURLAsset(url: url, options: nil)
|
|
guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPreset1920x1080) else {
|
|
continuation.resume(returning: nil)
|
|
return
|
|
}
|
|
let outputURL = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(url.pathExtension)")
|
|
exportSession.outputURL = outputURL
|
|
exportSession.outputFileType = .mp4
|
|
exportSession.shouldOptimizeForNetworkUse = true
|
|
exportSession.exportAsynchronously { () in
|
|
continuation.resume(returning: outputURL)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static var transferRepresentation: some TransferRepresentation {
|
|
FileRepresentation(contentType: .movie) { movie in
|
|
SentTransferredFile(movie.url)
|
|
} importing: { received in
|
|
Self(url: localURLFor(received: received))
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ImageFileTranseferable: Transferable {
|
|
let url: URL
|
|
|
|
lazy var data: Data? = try? Data(contentsOf: url)
|
|
lazy var image: UIImage? = UIImage(data: data ?? Data())
|
|
|
|
static var transferRepresentation: some TransferRepresentation {
|
|
FileRepresentation(contentType: .image) { image in
|
|
SentTransferredFile(image.url)
|
|
} importing: { received in
|
|
Self(url: localURLFor(received: received))
|
|
}
|
|
}
|
|
}
|
|
|
|
struct GifFileTranseferable: Transferable {
|
|
let url: URL
|
|
|
|
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: localURLFor(received: received))
|
|
}
|
|
}
|
|
}
|
|
|
|
private func localURLFor(received: ReceivedTransferredFile) -> URL {
|
|
let copy = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(received.file.pathExtension)")
|
|
try? FileManager.default.copyItem(at: received.file, to: copy)
|
|
return copy
|
|
}
|
|
|
|
public extension URL {
|
|
func mimeType() -> String {
|
|
if let mimeType = UTType(filenameExtension: pathExtension)?.preferredMIMEType {
|
|
return mimeType
|
|
} else {
|
|
return "application/octet-stream"
|
|
}
|
|
}
|
|
}
|
|
|
|
extension UIImage {
|
|
func resized(to size: CGSize) -> UIImage {
|
|
UIGraphicsImageRenderer(size: size).image { _ in
|
|
draw(in: CGRect(origin: .zero, size: size))
|
|
}
|
|
}
|
|
}
|