forked from shadowfacts/Tusker
parent
befcc18e4d
commit
6e4f89df4a
|
@ -7,6 +7,8 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import AVFoundation
|
||||
import AVKit
|
||||
|
||||
class GalleryViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
|
||||
|
||||
|
@ -16,10 +18,10 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
let sourcesInfo: [LargeImageViewController.SourceInfo?]
|
||||
let startIndex: Int
|
||||
|
||||
let pages: [AttachmentViewController]
|
||||
let pages: [UIViewController]
|
||||
|
||||
var currentIndex: Int {
|
||||
guard let vc = viewControllers?.first as? AttachmentViewController,
|
||||
guard let vc = viewControllers?.first,
|
||||
let index = pages.firstIndex(of: vc) else {
|
||||
fatalError()
|
||||
}
|
||||
|
@ -39,7 +41,18 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
self.sourcesInfo = sourcesInfo
|
||||
self.startIndex = startIndex
|
||||
|
||||
self.pages = attachments.map(AttachmentViewController.init)
|
||||
self.pages = attachments.map {
|
||||
switch $0.kind {
|
||||
case .image:
|
||||
return AttachmentViewController(attachment: $0)
|
||||
case .video:
|
||||
let vc = AVPlayerViewController()
|
||||
vc.player = AVPlayer(url: $0.url)
|
||||
return vc
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal)
|
||||
|
||||
|
@ -61,12 +74,24 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
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 attachment = viewController as? AttachmentViewController,
|
||||
let index = pages.firstIndex(of: attachment),
|
||||
guard let index = pages.firstIndex(of: viewController),
|
||||
index > 0 else {
|
||||
return nil
|
||||
}
|
||||
|
@ -74,8 +99,7 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
}
|
||||
|
||||
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
||||
guard let attachment = viewController as? AttachmentViewController,
|
||||
let index = pages.firstIndex(of: attachment),
|
||||
guard let index = pages.firstIndex(of: viewController),
|
||||
index < pages.count - 1 else {
|
||||
return nil
|
||||
}
|
||||
|
@ -84,9 +108,16 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
|
||||
// MARK: - Page View Controller Delegate
|
||||
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
|
||||
let pending = pendingViewControllers.first as! AttachmentViewController
|
||||
let current = viewControllers!.first as! AttachmentViewController
|
||||
pending.controlsVisible = current.controlsVisible
|
||||
if let pending = pendingViewControllers.first as? AttachmentViewController,
|
||||
let current = viewControllers!.first as? AttachmentViewController {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ class GalleryExpandAnimationController: NSObject, UIViewControllerAnimatedTransi
|
|||
}
|
||||
|
||||
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
||||
guard let (sourceFrame, sourceCornerRadius) = toVC.sourcesInfo[toVC.startIndex] else {
|
||||
guard let (image, sourceFrame, sourceCornerRadius) = toVC.sourcesInfo[toVC.startIndex] else {
|
||||
toVC.view.frame = finalVCFrame
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
|
@ -29,12 +29,8 @@ class GalleryExpandAnimationController: NSObject, UIViewControllerAnimatedTransi
|
|||
|
||||
let attachment = toVC.attachments[toVC.startIndex]
|
||||
|
||||
guard let data = ImageCache.attachments.get(attachment.url), let image = UIImage(data: data) else {
|
||||
toVC.view.frame = finalVCFrame
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
}
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
|
||||
let ratio = image.size.width / image.size.height
|
||||
var width = finalVCFrame.width
|
||||
var height = width / ratio
|
||||
|
@ -46,11 +42,10 @@ class GalleryExpandAnimationController: NSObject, UIViewControllerAnimatedTransi
|
|||
}
|
||||
let finalFrame = CGRect(x: finalVCFrame.midX - width / 2, y: finalVCFrame.midY - height / 2, width: width, height: height)
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
|
||||
let imageView = GIFImageView(frame: sourceFrame)
|
||||
imageView.image = image
|
||||
if attachment.url.pathExtension == "gif" {
|
||||
if attachment.url.pathExtension == "gif",
|
||||
let data = ImageCache.attachments.get(attachment.url) {
|
||||
imageView.animate(withGIFData: data)
|
||||
}
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
|
|
|
@ -26,20 +26,14 @@ class GalleryShrinkAnimationController: NSObject, UIViewControllerAnimatedTransi
|
|||
return
|
||||
}
|
||||
|
||||
guard let (sourceFrame, sourceCornerRadius) = fromVC.sourcesInfo[fromVC.currentIndex] else {
|
||||
guard let (image, sourceFrame, sourceCornerRadius) = fromVC.sourcesInfo[fromVC.currentIndex] else {
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
}
|
||||
let originalVCFrame = fromVC.view.frame
|
||||
|
||||
let attachment = fromVC.attachments[fromVC.currentIndex]
|
||||
|
||||
guard let data = ImageCache.attachments.get(attachment.url),
|
||||
let image = UIImage(data: data) else {
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let ratio = image.size.width / image.size.height
|
||||
var width = originalVCFrame.width
|
||||
var height = width / ratio
|
||||
|
@ -53,7 +47,8 @@ class GalleryShrinkAnimationController: NSObject, UIViewControllerAnimatedTransi
|
|||
|
||||
let imageView = GIFImageView(frame: originalFrame)
|
||||
imageView.image = image
|
||||
if attachment.url.pathExtension == "gif" {
|
||||
if attachment.url.pathExtension == "gif",
|
||||
let data = ImageCache.attachments.get(attachment.url) {
|
||||
imageView.animate(withGIFData: data)
|
||||
}
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
|
|
|
@ -13,7 +13,7 @@ import Gifu
|
|||
|
||||
class LargeImageViewController: UIViewController, UIScrollViewDelegate {
|
||||
|
||||
typealias SourceInfo = (frame: CGRect, cornerRadius: CGFloat)
|
||||
typealias SourceInfo = (image: UIImage, frame: CGRect, cornerRadius: CGFloat)
|
||||
|
||||
var sourceInfo: SourceInfo?
|
||||
var dismissInteractionController: LargeImageInteractionController?
|
||||
|
|
|
@ -22,14 +22,13 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
|
|||
}
|
||||
|
||||
let finalVCFrame = transitionContext.finalFrame(for: toVC)
|
||||
guard let (originFrame, originCornerRadius) = toVC.sourceInfo else {
|
||||
guard let (image, originFrame, originCornerRadius) = toVC.sourceInfo else {
|
||||
toVC.view.frame = finalVCFrame
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
}
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
let image = toVC.imageView.image!
|
||||
let ratio = image.size.width / image.size.height
|
||||
let width = finalVCFrame.width
|
||||
let height = width / ratio
|
||||
|
|
|
@ -27,7 +27,7 @@ class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTra
|
|||
return
|
||||
}
|
||||
|
||||
guard let (finalFrame, finalCornerRadius) = fromVC.sourceInfo else {
|
||||
guard let (image, finalFrame, finalCornerRadius) = fromVC.sourceInfo else {
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
return
|
||||
}
|
||||
|
@ -35,14 +35,13 @@ class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTra
|
|||
let originalVCFrame = fromVC.view.frame
|
||||
|
||||
let containerView = transitionContext.containerView
|
||||
let image = fromVC.image!
|
||||
let ratio = image.size.width / image.size.height
|
||||
let width = originalVCFrame.width
|
||||
let height = width / ratio
|
||||
let originalFrame = CGRect(x: originalVCFrame.midX - width / 2, y: originalVCFrame.midY - height / 2, width: width, height: height)
|
||||
|
||||
let imageView = GIFImageView(frame: originalFrame)
|
||||
imageView.image = fromVC.image!
|
||||
imageView.image = image
|
||||
if let gifData = fromVC.gifData {
|
||||
imageView.animate(withGIFData: gifData)
|
||||
}
|
||||
|
|
|
@ -28,17 +28,17 @@ protocol TuskerNavigationDelegate {
|
|||
|
||||
func reply(to statusID: String)
|
||||
|
||||
func largeImage(_ image: UIImage, description: String?, sourceView: UIView) -> LargeImageViewController
|
||||
func largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController
|
||||
|
||||
func largeImage(gifData: Data, description: String?, sourceView: UIView) -> LargeImageViewController
|
||||
func largeImage(gifData: Data, description: String?, sourceView: UIImageView) -> LargeImageViewController
|
||||
|
||||
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIView)
|
||||
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIImageView)
|
||||
|
||||
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIView)
|
||||
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIImageView)
|
||||
|
||||
func gallery(attachments: [Attachment], sourceViews: [UIView?], startIndex: Int) -> GalleryViewController
|
||||
func gallery(attachments: [Attachment], sourceViews: [UIImageView?], startIndex: Int) -> GalleryViewController
|
||||
|
||||
func showGallery(attachments: [Attachment], sourceViews: [UIView?], startIndex: Int)
|
||||
func showGallery(attachments: [Attachment], sourceViews: [UIImageView?], startIndex: Int)
|
||||
|
||||
func showMoreOptions(forStatus statusID: String)
|
||||
|
||||
|
@ -109,7 +109,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
present(vc, animated: true)
|
||||
}
|
||||
|
||||
private func sourceViewInfo(_ sourceView: UIView?) -> LargeImageViewController.SourceInfo? {
|
||||
private func sourceViewInfo(_ sourceView: UIImageView?) -> LargeImageViewController.SourceInfo? {
|
||||
guard let sourceView = sourceView else { return nil }
|
||||
|
||||
var sourceFrame = sourceView.convert(sourceView.bounds, to: view)
|
||||
|
@ -121,38 +121,38 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
|||
let y = sourceFrame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY
|
||||
sourceFrame = CGRect(x: x, y: y, width: width, height: height)
|
||||
}
|
||||
return (frame: sourceFrame, cornerRadius: sourceView.layer.cornerRadius)
|
||||
return (image: sourceView.image!, frame: sourceFrame, cornerRadius: sourceView.layer.cornerRadius)
|
||||
}
|
||||
|
||||
func largeImage(_ image: UIImage, description: String?, sourceView: UIView) -> LargeImageViewController {
|
||||
func largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController {
|
||||
let vc = LargeImageViewController(image: image, description: description, sourceInfo: sourceViewInfo(sourceView))
|
||||
vc.transitioningDelegate = self
|
||||
return vc
|
||||
}
|
||||
|
||||
func largeImage(gifData: Data, description: String?, sourceView: UIView) -> LargeImageViewController {
|
||||
func largeImage(gifData: Data, description: String?, sourceView: UIImageView) -> LargeImageViewController {
|
||||
let vc = LargeImageViewController(image: UIImage(data: gifData)!, description: description, sourceInfo: sourceViewInfo(sourceView))
|
||||
vc.transitioningDelegate = self
|
||||
vc.gifData = gifData
|
||||
return vc
|
||||
}
|
||||
|
||||
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIView) {
|
||||
func showLargeImage(_ image: UIImage, description: String?, animatingFrom sourceView: UIImageView) {
|
||||
present(largeImage(image, description: description, sourceView: sourceView), animated: true)
|
||||
}
|
||||
|
||||
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIView) {
|
||||
func showLargeImage(gifData: Data, description: String?, animatingFrom sourceView: UIImageView) {
|
||||
present(largeImage(gifData: gifData, description: description, sourceView: sourceView), animated: true)
|
||||
}
|
||||
|
||||
func gallery(attachments: [Attachment], sourceViews: [UIView?], startIndex: Int) -> GalleryViewController {
|
||||
func gallery(attachments: [Attachment], sourceViews: [UIImageView?], startIndex: Int) -> GalleryViewController {
|
||||
let sourcesInfo = sourceViews.map(sourceViewInfo)
|
||||
let vc = GalleryViewController(attachments: attachments, sourcesInfo: sourcesInfo, startIndex: startIndex)
|
||||
vc.transitioningDelegate = self
|
||||
return vc
|
||||
}
|
||||
|
||||
func showGallery(attachments: [Attachment], sourceViews: [UIView?], startIndex: Int) {
|
||||
func showGallery(attachments: [Attachment], sourceViews: [UIImageView?], startIndex: Int) {
|
||||
present(gallery(attachments: attachments, sourceViews: sourceViews, startIndex: startIndex), animated: true)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import UIKit
|
||||
import Pachyderm
|
||||
import Gifu
|
||||
import AVFoundation
|
||||
|
||||
protocol AttachmentViewDelegate {
|
||||
func showAttachmentsGallery(startingAt index: Int)
|
||||
|
@ -17,6 +18,8 @@ protocol AttachmentViewDelegate {
|
|||
class AttachmentView: UIImageView, GIFAnimatable {
|
||||
|
||||
var delegate: AttachmentViewDelegate?
|
||||
|
||||
var playImageView: UIImageView!
|
||||
|
||||
var attachment: Attachment!
|
||||
var index: Int!
|
||||
|
@ -31,7 +34,7 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
|||
|
||||
self.attachment = attachment
|
||||
self.index = index
|
||||
loadImage()
|
||||
loadAttachment()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
@ -44,6 +47,27 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
|||
layer.masksToBounds = true
|
||||
isUserInteractionEnabled = true
|
||||
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imagePressed)))
|
||||
|
||||
playImageView = UIImageView(image: UIImage(systemName: "play.circle.fill"))
|
||||
playImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(playImageView)
|
||||
NSLayoutConstraint.activate([
|
||||
playImageView.widthAnchor.constraint(equalToConstant: 50),
|
||||
playImageView.heightAnchor.constraint(equalToConstant: 50),
|
||||
playImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
playImageView.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
func loadAttachment() {
|
||||
switch attachment.kind {
|
||||
case .image:
|
||||
loadImage()
|
||||
case .video:
|
||||
loadVideo()
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
func loadImage() {
|
||||
|
@ -58,6 +82,22 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
playImageView.isHidden = true
|
||||
}
|
||||
|
||||
func loadVideo() {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let asset = AVURLAsset(url: self.attachment.url)
|
||||
let generator = AVAssetImageGenerator(asset: asset)
|
||||
generator.appliesPreferredTrackTransform = true
|
||||
guard let image = try? generator.copyCGImage(at: CMTime(seconds: 0, preferredTimescale: 1), actualTime: nil) else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.image = UIImage(cgImage: image)
|
||||
}
|
||||
}
|
||||
|
||||
playImageView.isHidden = false
|
||||
}
|
||||
|
||||
override func display(_ layer: CALayer) {
|
||||
|
@ -65,8 +105,14 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
|||
}
|
||||
|
||||
@objc func imagePressed() {
|
||||
// delegate?.showLargeAttachment(for: self)
|
||||
delegate?.showAttachmentsGallery(startingAt: index)
|
||||
// switch attachment.kind {
|
||||
// case .image:
|
||||
delegate?.showAttachmentsGallery(startingAt: index)
|
||||
// case .video:
|
||||
// delegate?.showVideo(attachment: attachment)
|
||||
// default:
|
||||
// fatalError()
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class AttachmentsContainerView: UIView {
|
|||
|
||||
func updateUI(status: Status) {
|
||||
self.statusID = status.id
|
||||
attachments = status.attachments.filter { $0.kind == .image }
|
||||
attachments = status.attachments.filter { $0.kind == .image || $0.kind == .video }
|
||||
|
||||
attachmentViews.removeAllObjects()
|
||||
subviews.forEach { $0.removeFromSuperview() }
|
||||
|
|
Loading…
Reference in New Issue