2022-05-20 09:58:39 +02:00
// C o p y r i g h t © 2 0 2 2 R a n g e p r o o f P t y L t d . A l l r i g h t s r e s e r v e d .
2018-03-12 23:04:16 +01:00
import UIKit
2022-05-20 09:58:39 +02:00
import GRDB
2018-11-14 00:02:48 +01:00
import PromiseKit
2022-03-30 00:46:08 +02:00
import SessionUIKit
2022-05-20 09:58:39 +02:00
import SessionMessagingKit
import SignalUtilitiesKit
class MediaPageViewController : UIPageViewController , UIPageViewControllerDataSource , UIPageViewControllerDelegate , MediaDetailViewControllerDelegate , InteractivelyDismissableViewController {
class DynamicallySizedView : UIView {
override var intrinsicContentSize : CGSize { CGSize . zero }
}
fileprivate var mediaInteractiveDismiss : MediaInteractiveDismiss ?
public let viewModel : MediaGalleryViewModel
private var dataChangeObservable : DatabaseCancellable ?
private var initialPage : MediaDetailViewController
private var cachedPages : [ Int64 : [ MediaGalleryViewModel . Item : MediaDetailViewController ] ] = [ : ]
2018-03-23 23:21:44 +01:00
public var currentViewController : MediaDetailViewController {
2018-03-19 23:00:11 +01:00
return viewControllers ! . first as ! MediaDetailViewController
2018-03-12 23:04:16 +01:00
}
2022-05-20 09:58:39 +02:00
public var currentItem : MediaGalleryViewModel . Item {
return currentViewController . galleryItem
2018-03-22 23:47:40 +01:00
}
2018-03-15 18:46:29 +01:00
2022-05-20 09:58:39 +02:00
public func setCurrentItem ( _ item : MediaGalleryViewModel . Item , direction : UIPageViewController . NavigationDirection , animated isAnimated : Bool ) {
2018-03-22 23:47:40 +01:00
guard let galleryPage = self . buildGalleryPage ( galleryItem : item ) else {
2018-11-26 20:31:03 +01:00
owsFailDebug ( " unexpectedly unable to build new gallery page " )
2018-03-22 23:47:40 +01:00
return
2018-03-15 18:46:29 +01:00
}
2018-03-22 23:47:40 +01:00
2018-11-14 00:02:48 +01:00
updateTitle ( item : item )
updateCaption ( item : item )
setViewControllers ( [ galleryPage ] , direction : direction , animated : isAnimated )
updateFooterBarButtonItems ( isPlayingVideo : false )
2022-05-20 09:58:39 +02:00
updateMediaRail ( item : item )
2018-03-15 18:46:29 +01:00
}
2018-03-12 23:04:16 +01:00
2018-03-20 14:56:39 +01:00
private let showAllMediaButton : Bool
private let sliderEnabled : Bool
2018-03-12 23:04:16 +01:00
2022-05-20 09:58:39 +02:00
init (
viewModel : MediaGalleryViewModel ,
initialItem : MediaGalleryViewModel . Item ,
options : [ MediaGalleryOption ]
) {
self . viewModel = viewModel
2018-03-20 14:56:39 +01:00
self . showAllMediaButton = options . contains ( . showAllMediaButton )
self . sliderEnabled = options . contains ( . sliderEnabled )
2022-05-20 09:58:39 +02:00
self . initialPage = MediaDetailViewController ( galleryItem : initialItem )
2018-03-12 23:04:16 +01:00
2022-05-20 09:58:39 +02:00
super . init (
transitionStyle : . scroll ,
navigationOrientation : . horizontal ,
options : [ . interPageSpacing : 20 ]
)
self . cachedPages [ initialItem . interactionId ] = [ initialItem : self . initialPage ]
self . initialPage . delegate = self
2018-03-12 23:04:16 +01:00
self . dataSource = self
self . delegate = self
2022-05-20 09:58:39 +02:00
self . modalPresentationStyle = . overFullScreen
self . transitioningDelegate = self
2018-03-19 23:00:11 +01:00
self . setViewControllers ( [ initialPage ] , direction : . forward , animated : false , completion : nil )
2018-03-12 23:04:16 +01:00
}
@ available ( * , unavailable , message : " Unimplemented " )
required init ? ( coder : NSCoder ) {
2018-08-27 16:21:03 +02:00
notImplemented ( )
2018-03-12 23:04:16 +01:00
}
deinit {
2018-08-23 16:37:34 +02:00
Logger . debug ( " deinit " )
2018-03-12 23:04:16 +01:00
}
2018-11-14 17:09:24 +01:00
// MARK: - S u b v i e w
2022-05-20 09:58:39 +02:00
private var hasAppeared : Bool = false
override var canBecomeFirstResponder : Bool { hasAppeared }
2018-11-14 17:09:24 +01:00
2022-05-20 09:58:39 +02:00
override var inputAccessoryView : UIView ? {
return bottomContainer
}
// MARK: - B o t t o m B a r
2018-11-08 17:28:07 +01:00
var bottomContainer : UIView !
2022-05-20 09:58:39 +02:00
var footerBar : UIToolbar = {
let result : UIToolbar = UIToolbar ( )
result . clipsToBounds = true // h i d e 1 p x t o p - b o r d e r
result . tintColor = Colors . text
result . barTintColor = Colors . navigationBarBackground
result . setBackgroundImage ( UIImage ( ) , forToolbarPosition : . any , barMetrics : UIBarMetrics . default )
result . setShadowImage ( UIImage ( ) , forToolbarPosition : . any )
result . isTranslucent = false
result . backgroundColor = Colors . navigationBarBackground
return result
} ( )
2018-11-14 17:09:24 +01:00
let captionContainerView : CaptionContainerView = CaptionContainerView ( )
var galleryRailView : GalleryRailView = GalleryRailView ( )
2018-11-14 00:02:48 +01:00
2018-11-14 17:09:24 +01:00
var pagerScrollView : UIScrollView !
2018-11-14 00:02:48 +01:00
2018-11-14 17:09:24 +01:00
// MARK: U I V i e w C o n t r o l l e r o v e r r i d e s
2018-11-14 00:02:48 +01:00
2018-03-12 23:04:16 +01:00
override func viewDidLoad ( ) {
super . viewDidLoad ( )
// N a v i g a t i o n
2018-03-20 15:03:49 +01:00
let backButton = OWSViewController . createOWSBackButton ( withTarget : self , selector : #selector ( didPressDismissButton ) )
self . navigationItem . leftBarButtonItem = backButton
2018-10-31 02:20:53 +01:00
self . navigationItem . titleView = portraitHeaderView
2018-03-20 14:56:39 +01:00
if showAllMediaButton {
2018-03-15 18:46:29 +01:00
self . navigationItem . rightBarButtonItem = UIBarButtonItem ( title : MediaStrings . allMedia , style : . plain , target : self , action : #selector ( didPressAllMediaButton ) )
}
2018-03-12 23:04:16 +01:00
// E v e n t h o u g h b a r s a r e o p a q u e , w e w a n t c o n t e n t t o b e l a y e d o u t b e h i n d t h e m .
// T h e b a r s m i g h t o b s c u r e p a r t o f t h e c o n t e n t , b u t t h e y c a n e a s i l y b e h i d d e n b y t a p p i n g
// T h e a l t e r n a t i v e w o u l d b e t h a t c o n t e n t w o u l d s h i f t w h e n t h e n a v b a r s h i d e .
self . extendedLayoutIncludesOpaqueBars = true
self . automaticallyAdjustsScrollViewInsets = false
2022-05-20 09:58:39 +02:00
// D i s a b l e t h e i n t e r a c t i v e P o p G e s t u r e R e c o g n i z e r a s w e w a n t t o b e a b l e t o s w i p e b e t w e e n
// d i f f e r e n t p a g e s
self . navigationController ? . interactivePopGestureRecognizer ? . isEnabled = false
self . mediaInteractiveDismiss = MediaInteractiveDismiss ( targetViewController : self )
self . mediaInteractiveDismiss ? . addGestureRecognizer ( to : view )
2018-03-12 23:04:16 +01:00
// G e t r e f e r e n c e t o p a g e d c o n t e n t w h i c h l i v e s i n a s c r o l l V i e w c r e a t e d b y t h e s u p e r c l a s s
// W e s h o w / h i d e t h i s c o n t e n t d u r i n g p r e s e n t a t i o n
for view in self . view . subviews {
if let pagerScrollView = view as ? UIScrollView {
2022-05-20 09:58:39 +02:00
pagerScrollView . contentInsetAdjustmentBehavior = . never
2018-03-12 23:04:16 +01:00
self . pagerScrollView = pagerScrollView
}
}
// H a c k t o a v o i d " p a g e " b o u n c i n g w h e n n o t i n g a l l e r y v i e w .
// e . g . w h e n g e t t i n g t o m e d i a d e t a i l s v i a m e s s a g e d e t a i l s s c r e e n , t h e r e ' s o n l y
// o n e " P a g e " s o t h e b o u n c e d o e s n ' t m a k e s e n s e .
2018-03-20 14:56:39 +01:00
pagerScrollView . isScrollEnabled = sliderEnabled
2019-01-16 22:49:25 +01:00
pagerScrollViewContentOffsetObservation = pagerScrollView . observe ( \ . contentOffset , options : [ . new ] ) { [ weak self ] _ , change in
2018-11-08 17:28:07 +01:00
guard let strongSelf = self else { return }
strongSelf . pagerScrollView ( strongSelf . pagerScrollView , contentOffsetDidChange : change )
}
2018-03-12 23:04:16 +01:00
// V i e w s
2020-01-20 04:44:51 +01:00
pagerScrollView . backgroundColor = Colors . navigationBarBackground
2022-05-20 09:58:39 +02:00
2020-01-20 04:44:51 +01:00
view . backgroundColor = Colors . navigationBarBackground
2018-03-12 23:04:16 +01:00
2018-11-14 17:09:24 +01:00
captionContainerView . delegate = self
updateCaptionContainerVisibility ( )
2018-11-08 17:28:07 +01:00
2022-05-20 09:58:39 +02:00
galleryRailView . isHidden = true
2018-11-14 00:02:48 +01:00
galleryRailView . delegate = self
2018-11-14 17:09:24 +01:00
galleryRailView . autoSetDimension ( . height , toSize : 72 )
2022-05-20 09:58:39 +02:00
footerBar . autoSetDimension ( . height , toSize : 44 )
2018-11-14 00:02:48 +01:00
2022-05-20 09:58:39 +02:00
let bottomContainer : DynamicallySizedView = DynamicallySizedView ( )
bottomContainer . clipsToBounds = true
bottomContainer . autoresizingMask = . flexibleHeight
2019-12-12 06:49:48 +01:00
bottomContainer . backgroundColor = Colors . navigationBarBackground
2022-05-20 09:58:39 +02:00
self . bottomContainer = bottomContainer
2018-11-14 00:02:48 +01:00
2018-11-14 17:09:24 +01:00
let bottomStack = UIStackView ( arrangedSubviews : [ captionContainerView , galleryRailView , footerBar ] )
2018-11-08 17:28:07 +01:00
bottomStack . axis = . vertical
2022-05-20 09:58:39 +02:00
bottomStack . isLayoutMarginsRelativeArrangement = true
2018-11-08 17:28:07 +01:00
bottomContainer . addSubview ( bottomStack )
bottomStack . autoPinEdgesToSuperviewEdges ( )
2022-05-20 09:58:39 +02:00
let galleryRailBlockingView : UIView = UIView ( )
galleryRailBlockingView . backgroundColor = Colors . navigationBarBackground
bottomStack . addSubview ( galleryRailBlockingView )
galleryRailBlockingView . pin ( . top , to : . bottom , of : footerBar )
galleryRailBlockingView . pin ( . left , to : . left , of : bottomStack )
galleryRailBlockingView . pin ( . right , to : . right , of : bottomStack )
galleryRailBlockingView . pin ( . bottom , to : . bottom , of : bottomStack )
updateTitle ( item : currentItem )
2018-11-14 17:09:24 +01:00
updateCaption ( item : currentItem )
2022-05-20 09:58:39 +02:00
updateMediaRail ( item : currentItem )
updateFooterBarButtonItems ( isPlayingVideo : false )
2018-03-12 23:04:16 +01:00
// G e s t u r e s
let verticalSwipe = UISwipeGestureRecognizer ( target : self , action : #selector ( didSwipeView ) )
verticalSwipe . direction = [ . up , . down ]
view . addGestureRecognizer ( verticalSwipe )
2022-05-20 09:58:39 +02:00
2019-12-12 06:49:48 +01:00
let navigationBar = navigationController ! . navigationBar
navigationBar . setBackgroundImage ( UIImage ( ) , for : UIBarMetrics . default )
navigationBar . shadowImage = UIImage ( )
navigationBar . isTranslucent = false
navigationBar . barTintColor = Colors . navigationBarBackground
2022-05-20 09:58:39 +02:00
// N o t i f i c a t i o n s
NotificationCenter . default . addObserver (
self ,
selector : #selector ( applicationDidBecomeActive ( _ : ) ) ,
name : UIApplication . didBecomeActiveNotification ,
object : nil
)
NotificationCenter . default . addObserver (
self ,
selector : #selector ( applicationDidResignActive ( _ : ) ) ,
name : UIApplication . didEnterBackgroundNotification , object : nil
)
}
public override func viewWillAppear ( _ animated : Bool ) {
super . viewWillAppear ( animated )
startObservingChanges ( )
}
override func viewDidAppear ( _ animated : Bool ) {
super . viewDidAppear ( animated )
hasAppeared = true
becomeFirstResponder ( )
}
public override func viewWillDisappear ( _ animated : Bool ) {
super . viewWillDisappear ( animated )
// S t o p o b s e r v i n g d a t a b a s e c h a n g e s
dataChangeObservable ? . cancel ( )
resignFirstResponder ( )
}
@objc func applicationDidBecomeActive ( _ notification : Notification ) {
startObservingChanges ( )
}
@objc func applicationDidResignActive ( _ notification : Notification ) {
// S t o p o b s e r v i n g d a t a b a s e c h a n g e s
dataChangeObservable ? . cancel ( )
2018-03-12 23:04:16 +01:00
}
2018-11-14 17:09:24 +01:00
override func viewWillTransition ( to size : CGSize , with coordinator : UIViewControllerTransitionCoordinator ) {
super . viewWillTransition ( to : size , with : coordinator )
let isLandscape = size . width > size . height
self . navigationItem . titleView = isLandscape ? nil : self . portraitHeaderView
}
override func didReceiveMemoryWarning ( ) {
Logger . info ( " " )
super . didReceiveMemoryWarning ( )
self . cachedPages = [ : ]
}
2018-11-08 17:28:07 +01:00
// MARK: K V O
var pagerScrollViewContentOffsetObservation : NSKeyValueObservation ?
func pagerScrollView ( _ pagerScrollView : UIScrollView , contentOffsetDidChange change : NSKeyValueObservedChange < CGPoint > ) {
guard let newValue = change . newValue else {
owsFailDebug ( " newValue was unexpectedly nil " )
return
}
let width = pagerScrollView . frame . size . width
guard width > 0 else {
return
}
let ratioComplete = abs ( ( newValue . x - width ) / width )
2018-11-14 17:09:24 +01:00
captionContainerView . updatePagerTransition ( ratioComplete : ratioComplete )
2018-03-19 23:00:11 +01:00
}
2018-03-12 23:04:16 +01:00
// MARK: V i e w H e l p e r s
2018-03-20 01:46:35 +01:00
public func willBePresentedAgain ( ) {
updateFooterBarButtonItems ( isPlayingVideo : false )
}
2018-03-19 23:00:11 +01:00
public func wasPresented ( ) {
let currentViewController = self . currentViewController
if currentViewController . galleryItem . isVideo {
currentViewController . playVideo ( )
}
}
2018-03-21 17:37:42 +01:00
2018-03-12 23:04:16 +01:00
private var shouldHideToolbars : Bool = false {
didSet {
2022-05-20 09:58:39 +02:00
guard oldValue != shouldHideToolbars else { return }
2018-03-12 23:04:16 +01:00
2022-05-20 09:58:39 +02:00
// H i d i n g t h e s t a t u s b a r a f f e c t s t h e p o s i t i o n i n g o f t h e n a v b a r . W e d o n ' t w a n t t o s h o w
// t h a t i n a n a n i m a t i o n , i t ' s b e t t e r t o j u s t h a v e e v e r y t h i g n " f l i t " i n / o u t
2018-03-26 23:57:46 +02:00
UIApplication . shared . setStatusBarHidden ( shouldHideToolbars , with : . none )
2018-03-12 23:04:16 +01:00
self . navigationController ? . setNavigationBarHidden ( shouldHideToolbars , animated : false )
UIView . animate ( withDuration : 0.1 ) {
2018-03-19 23:00:11 +01:00
self . currentViewController . setShouldHideToolbars ( self . shouldHideToolbars )
2018-11-08 17:28:07 +01:00
self . bottomContainer . isHidden = self . shouldHideToolbars
2018-03-12 23:04:16 +01:00
}
}
}
2018-11-14 17:09:24 +01:00
// MARK: B a r B u t t o n s
2018-11-15 21:30:46 +01:00
lazy var shareBarButton : UIBarButtonItem = {
2022-05-20 09:58:39 +02:00
let shareBarButton = UIBarButtonItem (
barButtonSystemItem : . action ,
target : self ,
action : #selector ( didPressShare )
)
2022-03-30 00:46:08 +02:00
shareBarButton . tintColor = Colors . text
2022-05-20 09:58:39 +02:00
2018-11-14 17:09:24 +01:00
return shareBarButton
} ( )
2018-11-15 21:30:46 +01:00
lazy var deleteBarButton : UIBarButtonItem = {
2022-05-20 09:58:39 +02:00
let deleteBarButton = UIBarButtonItem (
barButtonSystemItem : . trash ,
target : self ,
action : #selector ( didPressDelete )
)
2022-03-30 00:46:08 +02:00
deleteBarButton . tintColor = Colors . text
2022-05-20 09:58:39 +02:00
2018-11-14 17:09:24 +01:00
return deleteBarButton
} ( )
func buildFlexibleSpace ( ) -> UIBarButtonItem {
return UIBarButtonItem ( barButtonSystemItem : . flexibleSpace , target : nil , action : nil )
}
2018-11-22 05:20:04 +01:00
lazy var videoPlayBarButton : UIBarButtonItem = {
2022-05-20 09:58:39 +02:00
let videoPlayBarButton = UIBarButtonItem (
barButtonSystemItem : . play ,
target : self ,
action : #selector ( didPressPlayBarButton )
)
2022-03-30 00:46:08 +02:00
videoPlayBarButton . tintColor = Colors . text
2022-05-20 09:58:39 +02:00
2018-11-14 17:09:24 +01:00
return videoPlayBarButton
} ( )
2018-11-22 05:20:04 +01:00
lazy var videoPauseBarButton : UIBarButtonItem = {
2022-05-20 09:58:39 +02:00
let videoPauseBarButton = UIBarButtonItem (
barButtonSystemItem : . pause ,
target : self ,
action : #selector ( didPressPauseBarButton )
)
2022-03-30 00:46:08 +02:00
videoPauseBarButton . tintColor = Colors . text
2022-05-20 09:58:39 +02:00
2018-11-14 17:09:24 +01:00
return videoPauseBarButton
} ( )
2018-03-12 23:04:16 +01:00
private func updateFooterBarButtonItems ( isPlayingVideo : Bool ) {
2022-05-20 09:58:39 +02:00
self . footerBar . setItems (
[
shareBarButton ,
buildFlexibleSpace ( ) ,
( self . currentItem . isVideo && isPlayingVideo ? self . videoPauseBarButton : nil ) ,
( self . currentItem . isVideo && ! isPlayingVideo ? self . videoPlayBarButton : nil ) ,
( self . currentItem . isVideo ? buildFlexibleSpace ( ) : nil ) ,
deleteBarButton
] . compactMap { $0 } ,
animated : false
)
}
func updateMediaRail ( item : MediaGalleryViewModel . Item ) {
galleryRailView . configureCellViews (
album : ( self . viewModel . albumData [ item . interactionId ] ? ? [ ] ) ,
focusedItem : currentItem ,
cellViewBuilder : { _ in return GalleryRailCellView ( ) }
)
}
// MARK: - U p d a t i n g
private func startObservingChanges ( ) {
// S t a r t o b s e r v i n g f o r d a t a c h a n g e s
dataChangeObservable = GRDBStorage . shared . start (
viewModel . observableAlbumData ,
2022-05-29 11:26:06 +02:00
onError : { _ in } ,
2022-05-20 09:58:39 +02:00
onChange : { [ weak self ] albumData in
// T h e d e f a u l s c h e d u l e r e m i t s c h a n g e s o n t h e m a i n t h r e a d
self ? . handleUpdates ( albumData )
}
)
}
private func handleUpdates ( _ updatedViewData : [ MediaGalleryViewModel . Item ] ) {
// D e t e r m i n e i f w e s w a p p e d a l b u m s ( i f s o w e d o n ' t n e e d t o d o a n y t h i n g e l s e )
guard updatedViewData . contains ( where : { $0 . interactionId = = currentItem . interactionId } ) else {
if let updatedInteractionId : Int64 = updatedViewData . first ? . interactionId {
self . viewModel . updateAlbumData ( updatedViewData , for : updatedInteractionId )
}
2018-03-12 23:04:16 +01:00
return
}
2022-05-20 09:58:39 +02:00
// C l e a r t h e c a c h e d p a g e s t h a t n o l o n g e r m a t c h
let interactionId : Int64 = currentItem . interactionId
let updatedCachedPages : [ MediaGalleryViewModel . Item : MediaDetailViewController ] = cachedPages [ interactionId ]
. defaulting ( to : [ : ] )
. filter { key , _ -> Bool in updatedViewData . contains ( key ) }
// I f t h e r e a r e n o m o r e i t e m s i n t h e a l b u m t h e n d i s m i s s t h e s c r e e n
guard
! updatedViewData . isEmpty ,
let oldIndex : Int = self . viewModel . albumData [ interactionId ] ? . firstIndex ( of : currentItem )
else {
self . dismissSelf ( animated : true )
return
2018-03-12 23:04:16 +01:00
}
2022-05-20 09:58:39 +02:00
// U p d a t e t h e c a c h e s
self . viewModel . updateAlbumData ( updatedViewData , for : interactionId )
self . cachedPages [ interactionId ] = updatedCachedPages
// I f t h e c u r r e n t i t e m i s s t i l l a v a i l a b l e t h e n d o n o t h i n g e l s e
guard updatedCachedPages [ currentItem ] = = nil else { return }
// I f t h e c u r r e n t i t e m w a s m o d i f i e d w i t h i n t h e c u r r e n t u p d a t e t h e n r e l o a d i t ( j u s t i n c a s e )
if let updatedCurrentItem : MediaGalleryViewModel . Item = updatedViewData . first ( where : { item in item . attachment . id = = currentItem . attachment . id } ) {
setCurrentItem ( updatedCurrentItem , direction : . forward , animated : false )
2018-11-14 00:02:48 +01:00
return
}
2022-05-20 09:58:39 +02:00
// D e t e r m i n e t h e n e x t i n d e x ( i f i t ' s l e s s t h a n 0 t h e n p o p t h e s c r e e n )
let nextIndex : Int = min ( oldIndex , ( updatedViewData . count - 1 ) )
guard nextIndex >= 0 else {
self . dismissSelf ( animated : true )
return
}
self . setCurrentItem (
updatedViewData [ nextIndex ] ,
direction : ( nextIndex < oldIndex ?
. reverse :
. forward
) ,
animated : true
)
2018-11-14 00:02:48 +01:00
}
2022-05-20 09:58:39 +02:00
// MARK: - A c t i o n s
2018-11-08 17:28:07 +01:00
2022-05-20 09:58:39 +02:00
@objc public func didPressAllMediaButton ( sender : Any ) {
2018-11-08 17:28:07 +01:00
currentViewController . stopAnyVideo ( )
2022-05-20 09:58:39 +02:00
// I f t h e s c r e e n w a s n ' t p r e s e n t e d o r i t w a s p r e s e n t e d f r o m a l o c a t i o n w h i c h i s n ' t t h e
// M e d i a T i l e V i e w C o n t r o l l e r t h e n j u s t p o p / d i s m i s s t h e s c r e e n
guard
let presentingNavController : UINavigationController = ( self . presentingViewController as ? UINavigationController ) ,
! ( presentingNavController . viewControllers . last is MediaTileViewController )
else {
guard self . navigationController ? . viewControllers . count = = 1 else {
self . navigationController ? . popViewController ( animated : true )
return
}
self . dismiss ( animated : true )
2018-11-08 17:28:07 +01:00
return
}
2022-05-20 09:58:39 +02:00
// O t h e r w i s e i f w e c a m e v i a t h e c o n v e r s a t i o n s c r e e n w e n e e d t o p u s h a n e w
// i n s t a n c e o f M e d i a T i l e V i e w C o n t r o l l e r
let tileViewController : MediaTileViewController = MediaGalleryViewModel . createTileViewController (
threadId : self . viewModel . threadId ,
threadVariant : self . viewModel . threadVariant ,
2022-06-24 10:29:45 +02:00
focusedAttachmentId : currentItem . attachment . id ,
performInitialQuerySync : true
2022-05-20 09:58:39 +02:00
)
let navController : MediaGalleryNavigationController = MediaGalleryNavigationController ( )
navController . viewControllers = [ tileViewController ]
navController . modalPresentationStyle = . overFullScreen
navController . transitioningDelegate = tileViewController
self . navigationController ? . present ( navController , animated : true )
2018-11-08 17:28:07 +01:00
}
2022-05-20 09:58:39 +02:00
@objc public func didSwipeView ( sender : Any ) {
2018-11-08 17:28:07 +01:00
self . dismissSelf ( animated : true )
}
2022-05-20 09:58:39 +02:00
@objc public func didPressDismissButton ( _ sender : Any ) {
2018-03-12 23:04:16 +01:00
dismissSelf ( animated : true )
}
@objc
public func didPressShare ( _ sender : Any ) {
guard let currentViewController = self . viewControllers ? [ 0 ] as ? MediaDetailViewController else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " currentViewController was unexpectedly nil " )
2018-03-12 23:04:16 +01:00
return
}
2022-05-20 09:58:39 +02:00
guard let originalFilePath : String = currentViewController . galleryItem . attachment . originalFilePath else {
2018-03-12 23:04:16 +01:00
return
}
2022-05-20 09:58:39 +02:00
2022-05-23 09:16:14 +02:00
let shareVC = UIActivityViewController ( activityItems : [ URL ( fileURLWithPath : originalFilePath ) ] , applicationActivities : nil )
2022-02-24 05:35:35 +01:00
2022-05-23 09:16:14 +02:00
if UIDevice . current . isIPad {
shareVC . excludedActivityTypes = [ ]
shareVC . popoverPresentationController ? . permittedArrowDirections = [ ]
shareVC . popoverPresentationController ? . sourceView = self . view
shareVC . popoverPresentationController ? . sourceRect = self . view . bounds
}
2022-06-08 06:29:51 +02:00
2022-05-23 09:16:14 +02:00
shareVC . completionWithItemsHandler = { activityType , completed , returnedItems , activityError in
if let activityError = activityError {
SNLog ( " Failed to share with activityError: \( activityError ) " )
2022-06-08 06:29:51 +02:00
}
else if completed {
2022-05-23 09:16:14 +02:00
SNLog ( " Did share with activityType: \( activityType . debugDescription ) " )
}
2022-06-08 06:29:51 +02:00
2022-05-20 09:58:39 +02:00
guard
let activityType = activityType ,
activityType = = . saveToCameraRoll ,
currentViewController . galleryItem . interactionVariant = = . standardIncoming ,
self . viewModel . threadVariant = = . contact
else { return }
GRDBStorage . shared . write { db in
guard let thread : SessionThread = try SessionThread . fetchOne ( db , id : self . viewModel . threadId ) else {
return
}
try MessageSender . send (
db ,
message : DataExtractionNotification (
kind : . mediaSaved (
timestamp : UInt64 ( currentViewController . galleryItem . interactionTimestampMs )
)
) ,
interactionId : nil , // S h o w n o i n t e r a c t i o n f o r t h e c u r r e n t u s e r
in : thread
)
}
2018-03-22 23:47:40 +01:00
}
2022-05-23 09:16:14 +02:00
self . present ( shareVC , animated : true , completion : nil )
2022-05-20 09:58:39 +02:00
}
2018-03-22 23:47:40 +01:00
2022-05-20 09:58:39 +02:00
@objc public func didPressDelete ( _ sender : Any ) {
let itemToDelete : MediaGalleryViewModel . Item = self . currentItem
let actionSheet : UIAlertController = UIAlertController ( title : nil , message : nil , preferredStyle : . actionSheet )
let deleteAction = UIAlertAction (
title : " delete_message_for_me " . localized ( ) ,
style : . destructive
) { _ in
GRDBStorage . shared . writeAsync { db in
_ = try Attachment
. filter ( id : itemToDelete . attachment . id )
. deleteAll ( db )
// A d d t h e g a r b a g e c o l l e c t i o n j o b t o d e l e t e o r p h a n e d a t t a c h m e n t f i l e s
JobRunner . add (
db ,
job : Job (
variant : . garbageCollection ,
behaviour : . runOnce ,
details : GarbageCollectionJob . Details (
typesToCollect : [ . orphanedAttachmentFiles ]
)
)
)
// D e l e t e a n y i n t e r a c t i o n s w h i c h h a d a l l o f t h e i r a t t a c h m e n t s r e m o v e d
_ = try Interaction
. filter ( id : itemToDelete . interactionId )
. having ( Interaction . interactionAttachments . isEmpty )
. deleteAll ( db )
}
2018-03-22 23:47:40 +01:00
}
actionSheet . addAction ( OWSAlerts . cancelAction )
actionSheet . addAction ( deleteAction )
2019-03-21 15:55:04 +01:00
self . presentAlert ( actionSheet )
2018-03-12 23:04:16 +01:00
}
2022-05-20 09:58:39 +02:00
// MARK: - V i d e o i n t e r a c t i o n
2018-03-26 23:57:46 +02:00
2022-05-20 09:58:39 +02:00
@objc public func didPressPlayBarButton ( ) {
guard let currentViewController = self . viewControllers ? . first as ? MediaDetailViewController else {
SNLog ( " currentViewController was unexpectedly nil " )
2018-03-26 23:57:46 +02:00
return
}
2022-05-20 09:58:39 +02:00
currentViewController . didPressPlayBarButton ( )
2018-03-26 23:57:46 +02:00
}
2022-05-20 09:58:39 +02:00
@objc public func didPressPauseBarButton ( ) {
guard let currentViewController = self . viewControllers ? . first as ? MediaDetailViewController else {
SNLog ( " currentViewController was unexpectedly nil " )
2018-03-12 23:04:16 +01:00
return
}
2022-05-20 09:58:39 +02:00
currentViewController . didPressPauseBarButton ( )
2018-03-12 23:04:16 +01:00
}
// MARK: U I P a g e V i e w C o n t r o l l e r D e l e g a t e
2018-11-08 17:28:07 +01:00
var pendingViewController : MediaDetailViewController ?
2018-03-12 23:04:16 +01:00
public func pageViewController ( _ pageViewController : UIPageViewController , willTransitionTo pendingViewControllers : [ UIViewController ] ) {
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2018-03-12 23:04:16 +01:00
assert ( pendingViewControllers . count = = 1 )
pendingViewControllers . forEach { viewController in
2018-11-08 17:28:07 +01:00
guard let pendingViewController = viewController as ? MediaDetailViewController else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " unexpected mediaDetailViewController: \( viewController ) " )
2018-03-12 23:04:16 +01:00
return
}
2018-11-08 17:28:07 +01:00
self . pendingViewController = pendingViewController
2018-11-09 16:18:11 +01:00
if let pendingCaptionText = pendingViewController . galleryItem . captionForDisplay , pendingCaptionText . count > 0 {
2018-11-14 17:09:24 +01:00
self . captionContainerView . pendingText = pendingCaptionText
2018-11-08 17:28:07 +01:00
} else {
2018-11-14 17:09:24 +01:00
self . captionContainerView . pendingText = nil
2018-11-08 17:28:07 +01:00
}
2018-03-12 23:04:16 +01:00
// E n s u r e u p c o m i n g p a g e r e s p e c t s c u r r e n t t o o l b a r s t a t u s
2018-11-08 17:28:07 +01:00
pendingViewController . setShouldHideToolbars ( self . shouldHideToolbars )
2018-03-12 23:04:16 +01:00
}
}
public func pageViewController ( _ pageViewController : UIPageViewController , didFinishAnimating finished : Bool , previousViewControllers : [ UIViewController ] , transitionCompleted : Bool ) {
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2018-03-12 23:04:16 +01:00
assert ( previousViewControllers . count = = 1 )
previousViewControllers . forEach { viewController in
2018-03-19 23:00:11 +01:00
guard let previousPage = viewController as ? MediaDetailViewController else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " unexpected mediaDetailViewController: \( viewController ) " )
2018-03-12 23:04:16 +01:00
return
}
// D o a n y c l e a n u p f o r t h e n o - l o n g e r v i s i b l e v i e w c o n t r o l l e r
if transitionCompleted {
2018-11-08 17:28:07 +01:00
pendingViewController = nil
// T h i s c a n h a p p e n w h e n t r y i n g t o p a g e p a s t t h e l a s t ( o r f i r s t ) v i e w c o n t r o l l e r
// I n t h a t c a s e , w e d o n ' t w a n t t o c h a n g e t h e c a p t i o n V i e w .
if ( previousPage != currentViewController ) {
2018-11-14 17:09:24 +01:00
captionContainerView . completePagerTransition ( )
2018-11-08 17:28:07 +01:00
}
2022-05-20 09:58:39 +02:00
updateTitle ( item : currentItem )
updateMediaRail ( item : currentItem )
2018-03-19 23:00:11 +01:00
previousPage . zoomOut ( animated : false )
2018-03-20 01:28:43 +01:00
previousPage . stopAnyVideo ( )
2018-03-12 23:04:16 +01:00
updateFooterBarButtonItems ( isPlayingVideo : false )
2018-11-14 17:09:24 +01:00
} else {
captionContainerView . pendingText = nil
2018-03-12 23:04:16 +01:00
}
}
}
// MARK: U I P a g e V i e w C o n t r o l l e r D a t a S o u r c e
public func pageViewController ( _ pageViewController : UIPageViewController , viewControllerBefore viewController : UIViewController ) -> UIViewController ? {
2022-05-20 09:58:39 +02:00
guard let mediaViewController : MediaDetailViewController = viewController as ? MediaDetailViewController else {
2018-03-12 23:04:16 +01:00
return nil
}
2022-05-20 09:58:39 +02:00
// F i r s t c h e c k i f t h e r e i s a n o t h e r i t e m i n t h e c u r r e n t a l b u m
let interactionId : Int64 = mediaViewController . galleryItem . interactionId
if
let currentAlbum : [ MediaGalleryViewModel . Item ] = self . viewModel . albumData [ interactionId ] ,
let index : Int = currentAlbum . firstIndex ( of : mediaViewController . galleryItem ) ,
index > 0 ,
let previousPage : MediaDetailViewController = buildGalleryPage ( galleryItem : currentAlbum [ index - 1 ] )
{
return previousPage
2018-03-12 23:04:16 +01:00
}
2022-05-20 09:58:39 +02:00
// T h e n c h e c k i f t h e r e i s a n i n t e r a c t i o n b e f o r e t h e c u r r e n t a l b u m i n t e r a c t i o n
guard let interactionIdAfter : Int64 = self . viewModel . interactionIdAfter [ interactionId ] else { return nil }
// C a c h e a n d r e t r i e v e t h e n e w a l b u m i t e m s
let newAlbumItems : [ MediaGalleryViewModel . Item ] = viewModel . loadAndCacheAlbumData ( for : interactionIdAfter )
guard
! newAlbumItems . isEmpty ,
let previousPage : MediaDetailViewController = buildGalleryPage (
galleryItem : newAlbumItems [ newAlbumItems . count - 1 ]
)
else {
// I n v a l i d s t a t e , r e s t a r t t h e o b s e r v e r
startObservingChanges ( )
2018-03-12 23:04:16 +01:00
return nil
}
2022-05-20 09:58:39 +02:00
// S w a p o u t t h e d a t a b a s e o b s e r v e r
dataChangeObservable ? . cancel ( )
viewModel . replaceAlbumObservation ( toObservationFor : interactionIdAfter )
startObservingChanges ( )
return previousPage
2018-03-12 23:04:16 +01:00
}
public func pageViewController ( _ pageViewController : UIPageViewController , viewControllerAfter viewController : UIViewController ) -> UIViewController ? {
2022-05-20 09:58:39 +02:00
guard let mediaViewController : MediaDetailViewController = viewController as ? MediaDetailViewController else {
2018-03-12 23:04:16 +01:00
return nil
}
2022-05-20 09:58:39 +02:00
// F i r s t c h e c k i f t h e r e i s a n o t h e r i t e m i n t h e c u r r e n t a l b u m
let interactionId : Int64 = mediaViewController . galleryItem . interactionId
if
let currentAlbum : [ MediaGalleryViewModel . Item ] = self . viewModel . albumData [ interactionId ] ,
let index : Int = currentAlbum . firstIndex ( of : mediaViewController . galleryItem ) ,
index < ( currentAlbum . count - 1 ) ,
let nextPage : MediaDetailViewController = buildGalleryPage ( galleryItem : currentAlbum [ index + 1 ] )
{
return nextPage
2018-03-12 23:04:16 +01:00
}
2022-05-20 09:58:39 +02:00
// T h e n c h e c k i f t h e r e i s a n i n t e r a c t i o n b e f o r e t h e c u r r e n t a l b u m i n t e r a c t i o n
guard let interactionIdBefore : Int64 = self . viewModel . interactionIdBefore [ interactionId ] else { return nil }
2018-03-12 23:04:16 +01:00
2022-05-20 09:58:39 +02:00
// C a c h e a n d r e t r i e v e t h e n e w a l b u m i t e m s
let newAlbumItems : [ MediaGalleryViewModel . Item ] = viewModel . loadAndCacheAlbumData ( for : interactionIdBefore )
guard
! newAlbumItems . isEmpty ,
let nextPage : MediaDetailViewController = buildGalleryPage ( galleryItem : newAlbumItems [ 0 ] )
else {
// I n v a l i d s t a t e , r e s t a r t t h e o b s e r v e r
startObservingChanges ( )
2018-03-12 23:04:16 +01:00
return nil
}
2022-05-20 09:58:39 +02:00
// S w a p o u t t h e d a t a b a s e o b s e r v e r
dataChangeObservable ? . cancel ( )
viewModel . replaceAlbumObservation ( toObservationFor : interactionIdBefore )
startObservingChanges ( )
2018-03-19 23:00:11 +01:00
return nextPage
2018-03-12 23:04:16 +01:00
}
2022-05-20 09:58:39 +02:00
private func buildGalleryPage ( galleryItem : MediaGalleryViewModel . Item ) -> MediaDetailViewController ? {
if let cachedPage : MediaDetailViewController = cachedPages [ galleryItem . interactionId ] ? [ galleryItem ] {
2018-03-19 23:00:11 +01:00
return cachedPage
}
2022-05-20 09:58:39 +02:00
cachedPages [ galleryItem . interactionId ] = ( cachedPages [ galleryItem . interactionId ] ? ? [ : ] )
. setting ( galleryItem , MediaDetailViewController ( galleryItem : galleryItem , delegate : self ) )
return cachedPages [ galleryItem . interactionId ] ? [ galleryItem ]
2018-03-12 23:04:16 +01:00
}
public func dismissSelf ( animated isAnimated : Bool , completion : ( ( ) -> Void ) ? = nil ) {
2022-05-20 09:58:39 +02:00
// I f w e h a v e p r e s e n t e d a M e d i a T i l e V i e w C o n t r o l l e r f r o m t h i s s c r e e n t h e n i t w i l l c o n t i n u e
// t o o b s e r v e m e d i a c h a n g e s a n d i f a l l t h e i t e m s i n t h e a l b u m t h i s s c r e e n i s s h o w i n g a r e
// d e l e t e d i t w i l l a t t e m p t t o a u t o - d i s m i s s
guard self . presentedViewController = = nil else { return }
2018-03-12 23:04:16 +01:00
// S w a p p i n g m e d i a V i e w f o r p r e s e n t a t i o n V i e w w i l l b e p e r c e p t i b l e i f w e ' r e n o t z o o m e d o u t a l l t h e w a y .
2018-03-19 23:00:11 +01:00
// c u r r e n t V C
currentViewController . zoomOut ( animated : true )
2018-03-23 22:14:37 +01:00
currentViewController . stopAnyVideo ( )
2018-03-19 18:44:59 +01:00
2022-05-20 09:58:39 +02:00
self . navigationController ? . view . isUserInteractionEnabled = false
self . navigationController ? . dismiss ( animated : true , completion : { [ weak self ] in
if ! IsLandscapeOrientationEnabled ( ) {
2019-01-16 22:49:25 +01:00
UIDevice . current . ows_setOrientation ( . portrait )
}
2022-05-20 09:58:39 +02:00
UIApplication . shared . isStatusBarHidden = false
self ? . navigationController ? . presentingViewController ? . setNeedsStatusBarAppearanceUpdate ( )
completion ? ( )
} )
2018-03-12 23:04:16 +01:00
}
2018-03-22 19:10:41 +01:00
// MARK: M e d i a D e t a i l V i e w C o n t r o l l e r D e l e g a t e
2018-03-27 16:16:18 +02:00
public func mediaDetailViewControllerDidTapMedia ( _ mediaDetailViewController : MediaDetailViewController ) {
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2018-03-27 16:16:18 +02:00
self . shouldHideToolbars = ! self . shouldHideToolbars
}
2018-03-12 23:04:16 +01:00
public func mediaDetailViewController ( _ mediaDetailViewController : MediaDetailViewController , isPlayingVideo : Bool ) {
2018-03-19 23:00:11 +01:00
guard mediaDetailViewController = = currentViewController else {
2018-08-23 16:37:34 +02:00
Logger . verbose ( " ignoring stale delegate. " )
2018-03-12 23:04:16 +01:00
return
}
self . shouldHideToolbars = isPlayingVideo
self . updateFooterBarButtonItems ( isPlayingVideo : isPlayingVideo )
}
2018-03-20 19:37:30 +01:00
2022-05-20 09:58:39 +02:00
// MARK: - D y n a m i c H e a d e r
2018-03-20 19:37:30 +01:00
private lazy var dateFormatter : DateFormatter = {
let formatter = DateFormatter ( )
formatter . dateStyle = . short
formatter . timeStyle = . short
return formatter
} ( )
2018-10-31 02:20:53 +01:00
lazy private var portraitHeaderNameLabel : UILabel = {
2018-03-20 19:37:30 +01:00
let label = UILabel ( )
2019-12-12 06:49:48 +01:00
label . textColor = Colors . text
label . font = . systemFont ( ofSize : Values . mediumFontSize )
2018-03-20 19:37:30 +01:00
label . textAlignment = . center
label . adjustsFontSizeToFitWidth = true
label . minimumScaleFactor = 0.8
return label
} ( )
2018-10-31 02:20:53 +01:00
lazy private var portraitHeaderDateLabel : UILabel = {
2018-03-20 19:37:30 +01:00
let label = UILabel ( )
2019-12-12 06:49:48 +01:00
label . textColor = Colors . text
label . font = . systemFont ( ofSize : Values . verySmallFontSize )
2018-03-20 19:37:30 +01:00
label . textAlignment = . center
label . adjustsFontSizeToFitWidth = true
2018-05-03 19:53:40 +02:00
label . minimumScaleFactor = 0.8
2018-03-20 19:37:30 +01:00
return label
} ( )
2018-10-31 02:20:53 +01:00
private lazy var portraitHeaderView : UIView = {
let stackView = UIStackView ( )
stackView . axis = . vertical
stackView . alignment = . center
stackView . spacing = 0
stackView . distribution = . fillProportionally
stackView . addArrangedSubview ( portraitHeaderNameLabel )
stackView . addArrangedSubview ( portraitHeaderDateLabel )
let containerView = UIView ( )
containerView . layoutMargins = UIEdgeInsets ( top : 2 , left : 8 , bottom : 4 , right : 8 )
containerView . addSubview ( stackView )
stackView . autoPinEdge ( toSuperviewMargin : . top , relation : . greaterThanOrEqual )
stackView . autoPinEdge ( toSuperviewMargin : . trailing , relation : . greaterThanOrEqual )
stackView . autoPinEdge ( toSuperviewMargin : . bottom , relation : . greaterThanOrEqual )
stackView . autoPinEdge ( toSuperviewMargin : . leading , relation : . greaterThanOrEqual )
stackView . setContentHuggingHigh ( )
stackView . autoCenterInSuperview ( )
return containerView
} ( )
2022-05-20 09:58:39 +02:00
private func updateCaption ( item : MediaGalleryViewModel . Item ) {
2018-11-14 17:09:24 +01:00
captionContainerView . currentText = item . captionForDisplay
2018-11-09 23:26:12 +01:00
}
2022-05-20 09:58:39 +02:00
private func updateTitle ( item : MediaGalleryViewModel . Item ) {
let targetItem : MediaGalleryViewModel . Item = item
let threadVariant : SessionThread . Variant = self . viewModel . threadVariant
let name : String = {
switch targetItem . interactionVariant {
case . standardIncoming :
return GRDBStorage . shared
. read { db in
Profile . displayName (
db ,
id : targetItem . interactionAuthorId ,
threadVariant : threadVariant
)
}
. defaulting ( to : Profile . truncated ( id : targetItem . interactionAuthorId , truncating : . middle ) )
case . standardOutgoing :
return " MEDIA_GALLERY_SENDER_NAME_YOU " . localized ( ) // " S h o r t s e n d e r l a b e l f o r m e d i a s e n t b y y o u "
default :
owsFailDebug ( " Unsupported message variant: \( targetItem . interactionVariant ) " )
return " "
}
} ( )
2018-10-31 02:20:53 +01:00
portraitHeaderNameLabel . text = name
2018-03-20 19:37:30 +01:00
// u s e s e n t d a t e
2022-05-20 09:58:39 +02:00
let date = Date ( timeIntervalSince1970 : ( Double ( targetItem . interactionTimestampMs ) / 1000 ) )
2018-03-20 19:37:30 +01:00
let formattedDate = dateFormatter . string ( from : date )
2018-10-31 02:20:53 +01:00
portraitHeaderDateLabel . text = formattedDate
let landscapeHeaderFormat = NSLocalizedString ( " MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT " , comment : " embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' " )
let landscapeHeaderText = String ( format : landscapeHeaderFormat , name , formattedDate )
self . title = landscapeHeaderText
self . navigationItem . title = landscapeHeaderText
2018-05-03 19:53:40 +02:00
if #available ( iOS 11 , * ) {
2018-10-31 02:20:53 +01:00
// D o n o t h i n g , o n i O S 1 1 + , a u t o l a y o u t g r o w s t h e s t a c k v i e w a s n e c e s s a r y .
2018-05-03 19:53:40 +02:00
} else {
// S i z e t h e t i t l e V i e w t o b e l a r g e e n o u g h t o f i t t h e w i d e s t l a b e l ,
// b u t n o l a r g e r . I f w e g o f o r a " f u l l w i d t h " l a b e l , o u r t i t l e v i e w
2022-05-20 09:58:39 +02:00
// w i l l n o t b e c e n t e r e d ( s i n c e t h e l e f t a n d r i g h t b a r b u t t o n s h a v e d i f f e r e n t w i d t h s )
2018-10-31 02:20:53 +01:00
portraitHeaderNameLabel . sizeToFit ( )
portraitHeaderDateLabel . sizeToFit ( )
let width = max ( portraitHeaderNameLabel . frame . width , portraitHeaderDateLabel . frame . width )
2018-05-03 19:53:40 +02:00
2018-10-31 02:20:53 +01:00
let headerFrame : CGRect = CGRect ( x : 0 , y : 0 , width : width , height : 44 )
portraitHeaderView . frame = headerFrame
2018-05-03 19:53:40 +02:00
}
2018-03-20 19:37:30 +01:00
}
2022-05-20 09:58:39 +02:00
// MARK: - I n t e r a c t i v e l y D i s m i s s a b l e V i e w C o n t r o l l e r
func performInteractiveDismissal ( animated : Bool ) {
dismissSelf ( animated : true )
}
2018-03-12 23:04:16 +01:00
}
2018-11-08 17:28:07 +01:00
2022-05-20 09:58:39 +02:00
extension MediaGalleryViewModel . Item : GalleryRailItem {
2019-04-20 00:21:00 +02:00
public func buildRailItemView ( ) -> UIView {
let imageView = UIImageView ( )
imageView . contentMode = . scaleAspectFill
getRailImage ( ) . map { [ weak imageView ] image in
guard let imageView = imageView else { return }
imageView . image = image
} . retainUntilComplete ( )
return imageView
2018-11-15 03:01:21 +01:00
}
2019-04-20 00:21:00 +02:00
public func getRailImage ( ) -> Guarantee < UIImage > {
return Guarantee < UIImage > { fulfill in
2022-05-20 09:58:39 +02:00
self . thumbnailImage ( async : { image in fulfill ( image ) } )
2018-11-15 03:01:21 +01:00
}
}
2022-05-20 09:58:39 +02:00
public func isEqual ( to other : GalleryRailItem ? ) -> Bool {
guard let otherItem : MediaGalleryViewModel . Item = other as ? MediaGalleryViewModel . Item else { return false }
return ( self = = otherItem )
2018-11-15 03:01:21 +01:00
}
}
2018-11-14 00:02:48 +01:00
extension MediaPageViewController : GalleryRailViewDelegate {
func galleryRailView ( _ galleryRailView : GalleryRailView , didTapItem imageRailItem : GalleryRailItem ) {
2022-05-20 09:58:39 +02:00
guard let targetItem = imageRailItem as ? MediaGalleryViewModel . Item else {
2018-11-14 00:02:48 +01:00
owsFailDebug ( " unexpected imageRailItem: \( imageRailItem ) " )
return
}
2022-05-20 09:58:39 +02:00
self . setCurrentItem (
targetItem ,
direction : ( currentItem . attachmentAlbumIndex < targetItem . attachmentAlbumIndex ?
. forward :
. reverse
) ,
animated : true
)
2018-11-14 00:02:48 +01:00
}
}
2018-11-14 17:09:24 +01:00
extension MediaPageViewController : CaptionContainerViewDelegate {
2018-11-09 16:17:13 +01:00
2018-11-14 17:09:24 +01:00
func captionContainerViewDidUpdateText ( _ captionContainerView : CaptionContainerView ) {
updateCaptionContainerVisibility ( )
2018-11-08 17:28:07 +01:00
}
2018-11-14 17:09:24 +01:00
// MARK: H e l p e r s
2018-11-09 16:17:13 +01:00
2018-11-14 17:09:24 +01:00
func updateCaptionContainerVisibility ( ) {
if let currentText = captionContainerView . currentText , currentText . count > 0 {
captionContainerView . isHidden = false
return
2018-11-09 16:17:13 +01:00
}
2018-11-14 17:09:24 +01:00
if let pendingText = captionContainerView . pendingText , pendingText . count > 0 {
captionContainerView . isHidden = false
return
2018-11-09 16:17:13 +01:00
}
2018-11-14 17:09:24 +01:00
captionContainerView . isHidden = true
2018-11-09 16:17:13 +01:00
}
2018-11-08 17:28:07 +01:00
}
2022-05-20 09:58:39 +02:00
// MARK: - U I V i e w C o n t r o l l e r T r a n s i t i o n i n g D e l e g a t e
extension MediaPageViewController : UIViewControllerTransitioningDelegate {
public func animationController ( forPresented presented : UIViewController , presenting : UIViewController , source : UIViewController ) -> UIViewControllerAnimatedTransitioning ? {
guard self = = presented || self . navigationController = = presented else { return nil }
return MediaZoomAnimationController ( galleryItem : currentItem )
}
public func animationController ( forDismissed dismissed : UIViewController ) -> UIViewControllerAnimatedTransitioning ? {
guard self = = dismissed || self . navigationController = = dismissed else { return nil }
guard ! self . viewModel . albumData . isEmpty else { return nil }
let animationController = MediaDismissAnimationController ( galleryItem : currentItem , interactionController : mediaInteractiveDismiss )
mediaInteractiveDismiss ? . interactiveDismissDelegate = animationController
return animationController
}
public func interactionControllerForDismissal ( using animator : UIViewControllerAnimatedTransitioning ) -> UIViewControllerInteractiveTransitioning ? {
guard let animator = animator as ? MediaDismissAnimationController ,
let interactionController = animator . interactionController ,
interactionController . interactionInProgress
else {
return nil
}
return interactionController
}
}
// MARK: - M e d i a P r e s e n t a t i o n C o n t e x t P r o v i d e r
extension MediaPageViewController : MediaPresentationContextProvider {
func mediaPresentationContext ( mediaItem : Media , in coordinateSpace : UICoordinateSpace ) -> MediaPresentationContext ? {
let mediaView = currentViewController . mediaView
guard let mediaSuperview : UIView = mediaView . superview else { return nil }
let presentationFrame = coordinateSpace . convert ( mediaView . frame , from : mediaSuperview )
return MediaPresentationContext (
mediaView : mediaView ,
presentationFrame : presentationFrame ,
cornerRadius : 0 ,
cornerMask : CACornerMask ( )
)
}
func snapshotOverlayView ( in coordinateSpace : UICoordinateSpace ) -> ( UIView , CGRect ) ? {
return self . navigationController ? . navigationBar . generateSnapshot ( in : coordinateSpace )
}
}