173 lines
6.7 KiB
Swift
173 lines
6.7 KiB
Swift
// GalleryViewController.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 6/13/19.
|
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import Pachyderm
|
|
import AVFoundation
|
|
import AVKit
|
|
|
|
class GalleryViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, LargeImageAnimatableViewController {
|
|
|
|
let attachments: [Attachment]
|
|
let sourceViews: WeakArray<UIImageView>
|
|
let startIndex: Int
|
|
|
|
let pages: [UIViewController]
|
|
|
|
var currentIndex: Int {
|
|
guard let vc = viewControllers?.first,
|
|
let index = pages.firstIndex(of: vc) else {
|
|
fatalError()
|
|
}
|
|
return index
|
|
}
|
|
|
|
var animationSourceView: UIImageView? { sourceViews[currentIndex] }
|
|
var largeImageController: LargeImageViewController? {
|
|
// use protocol because page controllers may be loading or non-loading VCs
|
|
(pages[currentIndex] as? LargeImageAnimatableViewController)?.largeImageController
|
|
}
|
|
var animationImage: UIImage? {
|
|
if let page = pages[currentIndex] as? LargeImageAnimatableViewController,
|
|
let image = page.animationImage {
|
|
return image
|
|
} else {
|
|
return animationSourceView?.image
|
|
}
|
|
}
|
|
var animationGifData: Data? {
|
|
let attachment = attachments[currentIndex]
|
|
if attachment.url.pathExtension == "gif" {
|
|
return ImageCache.attachments.get(attachment.url)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
var dismissInteractionController: LargeImageInteractionController?
|
|
|
|
override var prefersStatusBarHidden: Bool {
|
|
return true
|
|
}
|
|
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
|
return .none
|
|
}
|
|
override var childForHomeIndicatorAutoHidden: UIViewController? {
|
|
return viewControllers?.first
|
|
}
|
|
|
|
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
|
return .allButUpsideDown
|
|
} else {
|
|
return .all
|
|
}
|
|
}
|
|
|
|
init(attachments: [Attachment], sourceViews: [UIImageView?], startIndex: Int) {
|
|
self.attachments = attachments
|
|
self.sourceViews = WeakArray(sourceViews)
|
|
self.startIndex = startIndex
|
|
|
|
self.pages = attachments.enumerated().map { (index, attachment) in
|
|
switch attachment.kind {
|
|
case .image:
|
|
let vc = LoadingLargeImageViewController(attachment: attachment)
|
|
vc.shrinkGestureEnabled = false
|
|
vc.animationSourceView = sourceViews[index]
|
|
return vc
|
|
case .video, .audio:
|
|
let vc = GalleryPlayerViewController()
|
|
vc.player = AVPlayer(url: attachment.url)
|
|
return vc
|
|
case .gifv:
|
|
// Passing the source view to the LargeImageGifvContentView is a crappy workaround for not
|
|
// having the video size directly inside the content view. This will break when there
|
|
// are more than 4 attachments and there is a gifv at index >= 3 (the More... button will show
|
|
// in place of the fourth attachment, so there aren't source views for the attachments at index >= 3).
|
|
// Really, what should happen is the LargeImageGifvContentView should get the size of the video from
|
|
// the AVFoundation instead of the source view.
|
|
// This isn't a priority as only Mastodon converts gifs to gifvs, and Mastodon (in its default configuration,
|
|
// I don't know about forks) doesn't allow more than four attachments, meaning there will always be a source view.
|
|
let gifvContentView = LargeImageGifvContentView(attachment: attachment, source: sourceViews[index]!)
|
|
let vc = LargeImageViewController(contentView: gifvContentView, description: attachment.description, sourceView: nil)
|
|
vc.shrinkGestureEnabled = false
|
|
return vc
|
|
default:
|
|
fatalError()
|
|
}
|
|
}
|
|
|
|
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal)
|
|
|
|
setViewControllers([pages[startIndex]], direction: .forward, animated: false)
|
|
|
|
modalPresentationStyle = .fullScreen
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
self.dataSource = self
|
|
self.delegate = self
|
|
|
|
overrideUserInterfaceStyle = .dark
|
|
|
|
dismissInteractionController = LargeImageInteractionController(viewController: self)
|
|
}
|
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
|
|
if let vc = pages[currentIndex] as? AVPlayerViewController {
|
|
// when the gallery is first shown, after the transition finishes, the controls for the player controller appear semi-transparent
|
|
// hiding the controls and then immediately reshowing them makes sure they're visible when the gallery is presented
|
|
vc.showsPlaybackControls = false
|
|
vc.showsPlaybackControls = true
|
|
|
|
// begin playing the video as soon as we appear
|
|
vc.player?.play()
|
|
}
|
|
}
|
|
|
|
// MARK: - Page View Controller Data Source
|
|
|
|
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
|
|
guard let index = pages.firstIndex(of: viewController),
|
|
index > 0 else {
|
|
return nil
|
|
}
|
|
return pages[index - 1]
|
|
}
|
|
|
|
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
|
guard let index = pages.firstIndex(of: viewController),
|
|
index < pages.count - 1 else {
|
|
return nil
|
|
}
|
|
return pages[index + 1]
|
|
}
|
|
|
|
// MARK: - Page View Controller Delegate
|
|
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
|
|
if let pending = pendingViewControllers.first as? LoadingLargeImageViewController,
|
|
let current = viewControllers!.first as? LoadingLargeImageViewController {
|
|
pending.controlsVisible = current.controlsVisible
|
|
}
|
|
|
|
if let pending = pendingViewControllers.first as? AVPlayerViewController {
|
|
// show controls and begin playing when the player page becomes visible
|
|
pending.showsPlaybackControls = true
|
|
pending.player?.play()
|
|
}
|
|
}
|
|
|
|
}
|