177 lines
6.2 KiB
Swift
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))
|
|
}
|
|
|
|
}
|