2020-06-18 03:33:48 +00:00
|
|
|
//
|
|
|
|
// LargeImageContentView.swift
|
|
|
|
// Tusker
|
|
|
|
//
|
|
|
|
// Created by Shadowfacts on 6/17/20.
|
|
|
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
import Pachyderm
|
|
|
|
import AVFoundation
|
2022-06-08 21:12:53 +00:00
|
|
|
import VisionKit
|
2020-06-18 03:33:48 +00:00
|
|
|
|
2020-11-01 18:59:58 +00:00
|
|
|
protocol LargeImageContentView: UIView {
|
2020-06-18 03:33:48 +00:00
|
|
|
var animationImage: UIImage? { get }
|
|
|
|
var activityItemsForSharing: [Any] { get }
|
2022-11-20 20:48:29 +00:00
|
|
|
var owner: LargeImageViewController? { get set }
|
2022-10-28 23:16:00 +00:00
|
|
|
func setControlsVisible(_ controlsVisible: Bool)
|
2020-11-01 18:59:58 +00:00
|
|
|
func grayscaleStateChanged()
|
2020-06-18 03:33:48 +00:00
|
|
|
}
|
|
|
|
|
2022-06-29 00:28:48 +00:00
|
|
|
class LargeImageImageContentView: UIImageView, LargeImageContentView {
|
2022-06-08 21:12:53 +00:00
|
|
|
|
2022-07-02 18:33:15 +00:00
|
|
|
#if !targetEnvironment(macCatalyst)
|
2022-06-08 21:12:53 +00:00
|
|
|
@available(iOS 16.0, *)
|
|
|
|
private static let analyzer = ImageAnalyzer()
|
2022-10-28 23:16:00 +00:00
|
|
|
private var _analysisInteraction: AnyObject?
|
|
|
|
@available(iOS 16.0, *)
|
|
|
|
private var analysisInteraction: ImageAnalysisInteraction? { _analysisInteraction as? ImageAnalysisInteraction }
|
2022-07-02 18:33:15 +00:00
|
|
|
#endif
|
2021-11-11 15:29:07 +00:00
|
|
|
|
2020-06-18 03:33:48 +00:00
|
|
|
var animationImage: UIImage? { image! }
|
|
|
|
var activityItemsForSharing: [Any] {
|
2023-01-02 21:59:55 +00:00
|
|
|
guard let data else {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
return [ImageActivityItemSource(data: data, url: url, image: image)]
|
2020-06-18 03:33:48 +00:00
|
|
|
}
|
2022-11-20 20:48:29 +00:00
|
|
|
weak var owner: LargeImageViewController?
|
2020-06-18 03:33:48 +00:00
|
|
|
|
2023-01-02 21:59:55 +00:00
|
|
|
private let url: URL
|
|
|
|
private let data: Data?
|
2020-11-01 18:59:58 +00:00
|
|
|
|
2023-01-02 21:59:55 +00:00
|
|
|
init(url: URL, data: Data?, image: UIImage) {
|
|
|
|
self.url = url
|
|
|
|
self.data = data
|
|
|
|
|
2020-06-18 03:33:48 +00:00
|
|
|
super.init(image: image)
|
|
|
|
|
|
|
|
contentMode = .scaleAspectFit
|
2022-06-08 21:12:53 +00:00
|
|
|
isUserInteractionEnabled = true
|
2020-06-18 03:33:48 +00:00
|
|
|
|
2022-07-02 18:33:15 +00:00
|
|
|
#if !targetEnvironment(macCatalyst)
|
2022-06-08 21:12:53 +00:00
|
|
|
if #available(iOS 16.0, *),
|
|
|
|
ImageAnalyzer.isSupported {
|
|
|
|
let interaction = ImageAnalysisInteraction()
|
2022-10-28 23:16:00 +00:00
|
|
|
self._analysisInteraction = interaction
|
2022-06-08 21:12:53 +00:00
|
|
|
interaction.delegate = self
|
|
|
|
interaction.preferredInteractionTypes = .automatic
|
|
|
|
addInteraction(interaction)
|
|
|
|
Task {
|
|
|
|
do {
|
|
|
|
let result = try await LargeImageImageContentView.analyzer.analyze(image, configuration: ImageAnalyzer.Configuration([.text, .machineReadableCode]))
|
|
|
|
interaction.analysis = result
|
|
|
|
} catch {
|
|
|
|
// if analysis fails, we just don't show anything
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-02 18:33:15 +00:00
|
|
|
#endif
|
2020-06-18 03:33:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
|
2022-10-28 23:16:00 +00:00
|
|
|
func setControlsVisible(_ controlsVisible: Bool) {
|
|
|
|
#if !targetEnvironment(macCatalyst)
|
|
|
|
if #available(iOS 16.0, *),
|
|
|
|
let analysisInteraction {
|
|
|
|
// note: passing animated: true here doesn't seem to do anything by itself as of iOS 16.2 (20C5032e)
|
|
|
|
// so the LargeImageViewController handles animating, but we still need to pass true here otherwise it doesn't animate
|
|
|
|
analysisInteraction.setSupplementaryInterfaceHidden(!controlsVisible, animated: true)
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-11-01 18:59:58 +00:00
|
|
|
func grayscaleStateChanged() {
|
2023-01-02 21:59:55 +00:00
|
|
|
guard let data else {
|
2020-11-01 18:59:58 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let image: UIImage?
|
|
|
|
if Preferences.shared.grayscaleImages {
|
|
|
|
image = ImageGrayscalifier.convert(url: nil, data: data)
|
|
|
|
} else {
|
|
|
|
image = UIImage(data: data)
|
|
|
|
}
|
|
|
|
|
|
|
|
if let image = image {
|
|
|
|
self.image = image
|
|
|
|
}
|
|
|
|
}
|
2022-06-29 00:28:48 +00:00
|
|
|
}
|
|
|
|
|
2022-07-02 18:33:15 +00:00
|
|
|
#if !targetEnvironment(macCatalyst)
|
2022-06-29 00:28:48 +00:00
|
|
|
@available(iOS 16.0, *)
|
|
|
|
extension LargeImageImageContentView: ImageAnalysisInteractionDelegate {
|
2022-06-08 21:12:53 +00:00
|
|
|
func presentingViewController(for interaction: ImageAnalysisInteraction) -> UIViewController? {
|
|
|
|
return owner
|
|
|
|
}
|
2020-06-18 03:33:48 +00:00
|
|
|
}
|
2022-07-02 18:33:15 +00:00
|
|
|
#endif
|
2020-06-18 03:33:48 +00:00
|
|
|
|
2021-11-13 19:52:02 +00:00
|
|
|
class LargeImageGifContentView: GIFImageView, LargeImageContentView {
|
|
|
|
var animationImage: UIImage? { image }
|
|
|
|
var activityItemsForSharing: [Any] {
|
2023-01-02 21:59:55 +00:00
|
|
|
[ImageActivityItemSource(data: gifController!.gifData, url: url, image: image)]
|
2021-11-13 19:52:02 +00:00
|
|
|
}
|
2022-11-20 20:48:29 +00:00
|
|
|
weak var owner: LargeImageViewController?
|
2021-11-13 19:52:02 +00:00
|
|
|
|
2023-01-02 21:59:55 +00:00
|
|
|
private let url: URL
|
|
|
|
|
|
|
|
init(url: URL, gifController: GIFController) {
|
|
|
|
self.url = url
|
|
|
|
|
2021-11-13 19:52:02 +00:00
|
|
|
super.init(image: gifController.lastFrame?.image)
|
|
|
|
|
|
|
|
contentMode = .scaleAspectFit
|
|
|
|
|
|
|
|
gifController.attach(to: self)
|
|
|
|
// todo: doing this in the init feels wrong
|
|
|
|
gifController.startAnimating()
|
|
|
|
}
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
|
2022-10-28 23:16:00 +00:00
|
|
|
func setControlsVisible(_ controlsVisible: Bool) {
|
|
|
|
}
|
|
|
|
|
2021-11-13 19:52:02 +00:00
|
|
|
func grayscaleStateChanged() {
|
|
|
|
// todo
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-18 03:33:48 +00:00
|
|
|
class LargeImageGifvContentView: GifvAttachmentView, LargeImageContentView {
|
|
|
|
private(set) var animationImage: UIImage?
|
|
|
|
var activityItemsForSharing: [Any] {
|
2023-01-02 21:59:55 +00:00
|
|
|
[GifvActivityItemSource(asset: asset, attachment: attachment)]
|
2020-06-18 03:33:48 +00:00
|
|
|
}
|
2022-11-20 20:48:29 +00:00
|
|
|
weak var owner: LargeImageViewController?
|
2020-06-18 03:33:48 +00:00
|
|
|
|
2023-01-02 21:59:55 +00:00
|
|
|
private let attachment: Attachment
|
2020-06-18 03:33:48 +00:00
|
|
|
private let asset: AVURLAsset
|
|
|
|
|
2022-11-25 18:20:31 +00:00
|
|
|
private var videoSize: CGSize?
|
2020-06-18 03:33:48 +00:00
|
|
|
override var intrinsicContentSize: CGSize {
|
2022-11-25 18:20:31 +00:00
|
|
|
videoSize ?? CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
|
2020-06-18 03:33:48 +00:00
|
|
|
}
|
2023-01-02 21:59:55 +00:00
|
|
|
|
2020-06-18 03:33:48 +00:00
|
|
|
init(attachment: Attachment, source: UIImageView) {
|
|
|
|
precondition(attachment.kind == .gifv)
|
|
|
|
|
2023-01-02 21:59:55 +00:00
|
|
|
self.attachment = attachment
|
2020-06-18 03:33:48 +00:00
|
|
|
self.asset = AVURLAsset(url: attachment.url)
|
|
|
|
|
|
|
|
super.init(asset: asset, gravity: .resizeAspect)
|
|
|
|
|
|
|
|
self.animationImage = source.image
|
2020-08-16 23:14:32 +00:00
|
|
|
|
|
|
|
self.player.play()
|
2022-11-25 18:20:31 +00:00
|
|
|
|
|
|
|
Task {
|
|
|
|
do {
|
|
|
|
if let track = try await asset.loadTracks(withMediaType: .video).first {
|
|
|
|
let (size, transform) = try await track.load(.naturalSize, .preferredTransform)
|
|
|
|
self.videoSize = size.applying(transform)
|
|
|
|
self.invalidateIntrinsicContentSize()
|
|
|
|
}
|
|
|
|
} catch {
|
|
|
|
}
|
|
|
|
}
|
2020-06-18 03:33:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
2020-11-01 18:59:58 +00:00
|
|
|
|
2022-10-28 23:16:00 +00:00
|
|
|
func setControlsVisible(_ controlsVisible: Bool) {
|
|
|
|
}
|
|
|
|
|
2020-11-01 18:59:58 +00:00
|
|
|
func grayscaleStateChanged() {
|
|
|
|
// no-op, GifvAttachmentView observes the grayscale state itself
|
|
|
|
}
|
2023-01-02 21:59:55 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fileprivate class ImageActivityItemSource: NSObject, UIActivityItemSource {
|
|
|
|
let data: Data
|
|
|
|
let url: URL
|
|
|
|
let image: UIImage?
|
|
|
|
|
|
|
|
init(data: Data, url: URL, image: UIImage?) {
|
|
|
|
self.data = data
|
|
|
|
self.url = url
|
|
|
|
self.image = image
|
|
|
|
}
|
|
|
|
|
|
|
|
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
|
|
|
return url
|
|
|
|
}
|
|
|
|
|
|
|
|
func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? {
|
|
|
|
return image
|
|
|
|
}
|
|
|
|
|
|
|
|
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
|
|
|
do {
|
|
|
|
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
|
|
|
|
try data.write(to: tempURL)
|
|
|
|
return tempURL
|
|
|
|
} catch {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivity.ActivityType?) -> String {
|
|
|
|
return (UTType(filenameExtension: url.pathExtension) ?? .image).identifier
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fileprivate class GifvActivityItemSource: NSObject, UIActivityItemSource {
|
|
|
|
let asset: AVAsset
|
|
|
|
let attachment: Attachment
|
|
|
|
|
|
|
|
init(asset: AVAsset, attachment: Attachment) {
|
|
|
|
self.asset = asset
|
|
|
|
self.attachment = attachment
|
|
|
|
}
|
|
|
|
|
|
|
|
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
|
|
|
return attachment.url
|
|
|
|
}
|
|
|
|
|
|
|
|
func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? {
|
|
|
|
let generator = AVAssetImageGenerator(asset: self.asset)
|
|
|
|
generator.appliesPreferredTrackTransform = true
|
|
|
|
if let image = try? generator.copyCGImage(at: .zero, actualTime: nil) {
|
|
|
|
return UIImage(cgImage: image)
|
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
|
|
|
do {
|
|
|
|
let data = try Data(contentsOf: attachment.url)
|
|
|
|
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(attachment.url.lastPathComponent)
|
|
|
|
try data.write(to: tempURL)
|
|
|
|
return tempURL
|
|
|
|
} catch {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivity.ActivityType?) -> String {
|
|
|
|
return (UTType(filenameExtension: attachment.url.pathExtension) ?? .video).identifier
|
|
|
|
}
|
2020-06-18 03:33:48 +00:00
|
|
|
}
|