Tusker/Tusker/Screens/Large Image/LoadingLargeImageViewContro...

177 lines
6.2 KiB
Swift

// LoadingLargeImageViewController.swift
// Tusker
//
// Created by Shadowfacts on 6/14/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
import TuskerComponents
class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableViewController {
private var attachment: Attachment?
let url: URL
let cache: ImageCache
let imageDescription: String?
private(set) var loaded = false
private(set) var largeImageVC: LargeImageViewController?
private var loadingVC: LoadingViewController?
private var imageRequest: ImageCache.Request?
private var initialControlsVisible: Bool = true
var controlsVisible: Bool {
get {
return largeImageVC?.controlsVisible ?? initialControlsVisible
}
set {
if let largeImageVC = largeImageVC {
largeImageVC.setControlsVisible(newValue, animated: false)
} else {
initialControlsVisible = newValue
}
}
}
var shrinkGestureEnabled = true
weak var animationSourceView: UIImageView?
var largeImageController: LargeImageViewController? { largeImageVC }
var animationImage: UIImage? { largeImageVC?.animationImage ?? animationSourceView?.image }
var dismissInteractionController: LargeImageInteractionController?
var isInteractivelyAnimatingDismissal: Bool = false {
didSet {
#if !os(visionOS)
setNeedsStatusBarAppearanceUpdate()
#endif
}
}
override var prefersStatusBarHidden: Bool {
return !isInteractivelyAnimatingDismissal
}
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .none
}
override var childForHomeIndicatorAutoHidden: UIViewController? {
return largeImageVC
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
init(url: URL, cache: ImageCache, imageDescription: String?) {
self.url = url
self.cache = cache
self.imageDescription = imageDescription
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .fullScreen
}
convenience init(attachment: Attachment) {
self.init(url: attachment.url, cache: .attachments, imageDescription: attachment.description)
self.attachment = attachment
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .dark
view.backgroundColor = .black
// always load full resolution from disk for large image, in case the cache is scaled
if let entry = cache.get(url, loadOriginal: true) {
// todo: if load original is true, is there any way entry.data could be nil?
// feels like the data param of createLargeImage shouldn't be optional
createLargeImage(data: entry.data, image: entry.image, url: url)
} else {
createPreview()
loadingVC = LoadingViewController()
embedChild(loadingVC!)
imageRequest = cache.get(url, loadOriginal: true) { [weak self] (data, image) in
guard let self = self, let image = image else { return }
self.imageRequest = nil
DispatchQueue.main.async {
self.loadingVC?.removeViewAndController()
self.createLargeImage(data: data, image: image, url: self.url)
}
}
}
if shrinkGestureEnabled {
dismissInteractionController = LargeImageInteractionController(viewController: self)
}
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
imageRequest?.cancel()
}
}
private func createLargeImage(data: Data?, image: UIImage, url: URL) {
guard !loaded else { return }
loaded = true
let content: LargeImageContentView
// todo: p sure grayscaling gifs has never worked
if url.pathExtension == "gif", let data = data {
// todo: pulling the gif controller out of the source view feels icky
// is it possible for the source view's gif controller to have different data than we just got?
// should this be a property set by the animation controller instead?
let gifController = (animationSourceView as? GIFImageView)?.gifController ?? GIFController(gifData: data)
content = LargeImageGifContentView(url: url, gifController: gifController)
} else {
if let transformedImage = ImageGrayscalifier.convertIfNecessary(url: url, image: image) {
content = LargeImageImageContentView(url: url, data: data, image: transformedImage)
} else {
content = LargeImageImageContentView(url: url, data: data, image: image)
}
}
setContent(content)
}
private func setContent(_ content: LargeImageContentView) {
if let existing = largeImageVC {
existing.contentView = content
} else {
largeImageVC = LargeImageViewController(contentView: content, description: imageDescription, sourceView: animationSourceView)
largeImageVC!.initialControlsVisible = initialControlsVisible
largeImageVC!.shrinkGestureEnabled = false
embedChild(largeImageVC!)
}
}
private func createPreview() {
guard !self.loaded,
var image = animationSourceView?.image else { return }
if Preferences.shared.grayscaleImages,
let source = image.cgImage,
let grayscale = ImageGrayscalifier.convert(url: nil, cgImage: source) {
image = grayscale
}
setContent(LargeImageImageContentView(url: url, data: nil, image: image))
}
}