forked from shadowfacts/Tusker
192 lines
6.8 KiB
Swift
192 lines
6.8 KiB
Swift
//
|
|
// AttachmentThumbnailController.swift
|
|
// ComposeUI
|
|
//
|
|
// Created by Shadowfacts on 11/10/21.
|
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import Photos
|
|
import TuskerComponents
|
|
|
|
class AttachmentThumbnailController: ViewController {
|
|
unowned let parent: ComposeController
|
|
let attachment: DraftAttachment
|
|
|
|
@Published private var image: UIImage?
|
|
@Published private var gifController: GIFController?
|
|
@Published private var fullSize: Bool = false
|
|
|
|
init(attachment: DraftAttachment, parent: ComposeController) {
|
|
self.attachment = attachment
|
|
self.parent = parent
|
|
}
|
|
|
|
func loadImageIfNecessary(fullSize: Bool) {
|
|
if (gifController != nil) || (image != nil && self.fullSize) {
|
|
return
|
|
}
|
|
self.fullSize = fullSize
|
|
|
|
switch attachment.data {
|
|
case .editing(_, let kind, let url):
|
|
switch kind {
|
|
case .image:
|
|
Task { @MainActor in
|
|
self.image = await parent.fetchAttachment(url)
|
|
}
|
|
|
|
case .video, .gifv:
|
|
let asset = AVURLAsset(url: url)
|
|
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
|
if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
|
|
self.image = UIImage(cgImage: cgImage)
|
|
}
|
|
|
|
case .audio, .unknown:
|
|
break
|
|
}
|
|
|
|
case .asset(let id):
|
|
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil).firstObject else {
|
|
return
|
|
}
|
|
let isGIF = PHAssetResource.assetResources(for: asset).contains(where: { $0.uniformTypeIdentifier == UTType.gif.identifier })
|
|
if isGIF {
|
|
PHImageManager.default().requestImageDataAndOrientation(for: asset, options: nil) { data, typeIdentifier, orientation, info in
|
|
guard let data else { return }
|
|
if typeIdentifier == UTType.gif.identifier {
|
|
self.gifController = GIFController(gifData: data)
|
|
} else {
|
|
let image = UIImage(data: data)
|
|
DispatchQueue.main.async {
|
|
self.image = image
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
let size: CGSize
|
|
if fullSize {
|
|
size = PHImageManagerMaximumSize
|
|
} else {
|
|
// currently only used as thumbnail in ComposeAttachmentRow
|
|
size = CGSize(width: 80, height: 80)
|
|
}
|
|
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
|
DispatchQueue.main.async {
|
|
self.image = image
|
|
}
|
|
}
|
|
}
|
|
|
|
case .drawing(let drawing):
|
|
image = drawing.imageInLightMode(from: drawing.bounds)
|
|
|
|
case .file(let url, let type):
|
|
if type.conforms(to: .movie) {
|
|
let asset = AVURLAsset(url: url)
|
|
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
|
if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
|
|
self.image = UIImage(cgImage: cgImage)
|
|
}
|
|
} else if let data = try? Data(contentsOf: url) {
|
|
if type == .gif {
|
|
self.gifController = GIFController(gifData: data)
|
|
} else if type.conforms(to: .image),
|
|
let image = UIImage(data: data) {
|
|
// using prepareThumbnail on images from PHPicker results in extremely high memory usage,
|
|
// crashing share extension. see FB12186346
|
|
// if fullSize {
|
|
image.prepareForDisplay { prepared in
|
|
DispatchQueue.main.async {
|
|
self.image = image
|
|
}
|
|
}
|
|
// } else {
|
|
// image.prepareThumbnail(of: CGSize(width: 80, height: 80)) { prepared in
|
|
// DispatchQueue.main.async {
|
|
// self.image = prepared
|
|
// }
|
|
// }
|
|
// }
|
|
}
|
|
}
|
|
|
|
case .none:
|
|
break
|
|
}
|
|
}
|
|
|
|
var view: some SwiftUI.View {
|
|
View()
|
|
}
|
|
|
|
struct View: SwiftUI.View {
|
|
@EnvironmentObject private var controller: AttachmentThumbnailController
|
|
@Environment(\.attachmentThumbnailConfiguration) private var config
|
|
|
|
var body: some SwiftUI.View {
|
|
content
|
|
.onAppear {
|
|
controller.loadImageIfNecessary(fullSize: config.fullSize)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var content: some SwiftUI.View {
|
|
if let gifController = controller.gifController {
|
|
GIFViewWrapper(controller: gifController)
|
|
} else if let image = controller.image {
|
|
Image(uiImage: image)
|
|
.resizable()
|
|
.aspectRatio(config.aspectRatio, contentMode: config.contentMode)
|
|
} else {
|
|
Image(systemName: "photo")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct AttachmentThumbnailConfiguration {
|
|
let aspectRatio: CGFloat?
|
|
let contentMode: ContentMode
|
|
let fullSize: Bool
|
|
|
|
init(aspectRatio: CGFloat? = nil, contentMode: ContentMode = .fit, fullSize: Bool = false) {
|
|
self.aspectRatio = aspectRatio
|
|
self.contentMode = contentMode
|
|
self.fullSize = fullSize
|
|
}
|
|
}
|
|
|
|
private struct AttachmentThumbnailConfigurationEnvironmentKey: EnvironmentKey {
|
|
static let defaultValue = AttachmentThumbnailConfiguration()
|
|
}
|
|
|
|
extension EnvironmentValues {
|
|
var attachmentThumbnailConfiguration: AttachmentThumbnailConfiguration {
|
|
get { self[AttachmentThumbnailConfigurationEnvironmentKey.self] }
|
|
set { self[AttachmentThumbnailConfigurationEnvironmentKey.self] = newValue }
|
|
}
|
|
}
|
|
|
|
private struct GIFViewWrapper: UIViewRepresentable {
|
|
typealias UIViewType = GIFImageView
|
|
|
|
@State var controller: GIFController
|
|
|
|
func makeUIView(context: Context) -> GIFImageView {
|
|
let view = GIFImageView()
|
|
controller.attach(to: view)
|
|
controller.startAnimating()
|
|
view.contentMode = .scaleAspectFit
|
|
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
view.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
|
|
return view
|
|
}
|
|
|
|
func updateUIView(_ uiView: GIFImageView, context: Context) {
|
|
}
|
|
}
|