2018-03-12 23:04:16 +01:00
//
2019-01-08 21:07:23 +01:00
// C o p y r i g h t ( c ) 2 0 1 9 O p e n W h i s p e r S y s t e m s . 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
2018-11-14 00:02:48 +01:00
import PromiseKit
2018-03-12 23:04:16 +01:00
2018-03-19 23:00:11 +01:00
// O b j c w r a p p e r f o r t h e M e d i a G a l l e r y I t e m s t r u c t
@objc
public class GalleryItemBox : NSObject {
public let value : MediaGalleryItem
2018-03-20 01:15:47 +01:00
2018-03-19 23:00:11 +01:00
init ( _ value : MediaGalleryItem ) {
self . value = value
2018-03-15 18:46:29 +01:00
}
2018-03-20 01:15:47 +01:00
2018-05-25 23:17:15 +02:00
@objc
2018-03-15 18:46:29 +01:00
public var attachmentStream : TSAttachmentStream {
2018-03-19 23:00:11 +01:00
return value . attachmentStream
2018-03-15 18:46:29 +01:00
}
2018-03-19 23:00:11 +01:00
}
2018-03-15 18:46:29 +01:00
2018-03-26 23:57:46 +02:00
private class Box < A > {
2018-03-19 23:00:11 +01:00
var value : A
init ( _ val : A ) {
self . value = val
2018-03-15 18:46:29 +01:00
}
2018-03-19 23:00:11 +01:00
}
2018-03-17 13:15:44 +01:00
2018-03-19 23:00:11 +01:00
fileprivate extension MediaDetailViewController {
fileprivate var galleryItem : MediaGalleryItem {
return self . galleryItemBox . value
2018-03-12 23:04:16 +01:00
}
2018-03-15 18:46:29 +01:00
}
2018-03-26 23:57:46 +02:00
class MediaPageViewController : UIPageViewController , UIPageViewControllerDataSource , UIPageViewControllerDelegate , MediaDetailViewControllerDelegate , MediaGalleryDataSourceDelegate {
2018-03-15 18:46:29 +01:00
2018-03-19 18:44:59 +01:00
private weak var mediaGalleryDataSource : MediaGalleryDataSource ?
2018-03-12 23:04:16 +01:00
2018-03-19 23:00:11 +01:00
private var cachedPages : [ MediaGalleryItem : MediaDetailViewController ] = [ : ]
private var initialPage : MediaDetailViewController !
2018-03-15 18:46:29 +01:00
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
}
2018-03-15 18:46:29 +01:00
public var currentItem : MediaGalleryItem ! {
2018-11-09 23:26:12 +01:00
return currentViewController . galleryItemBox . value
2018-03-22 23:47:40 +01:00
}
2018-03-15 18:46:29 +01:00
2019-03-30 14:22:31 +01:00
public func setCurrentItem ( _ item : MediaGalleryItem , 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 )
updateMediaRail ( )
2018-03-15 18:46:29 +01:00
}
2018-03-12 23:04:16 +01:00
private let uiDatabaseConnection : YapDatabaseConnection
2018-03-20 14:56:39 +01:00
private let showAllMediaButton : Bool
private let sliderEnabled : Bool
2018-03-12 23:04:16 +01:00
2018-03-20 14:56:39 +01:00
init ( initialItem : MediaGalleryItem , mediaGalleryDataSource : MediaGalleryDataSource , uiDatabaseConnection : YapDatabaseConnection , options : MediaGalleryOption ) {
2018-03-18 16:28:26 +01:00
assert ( uiDatabaseConnection . isInLongLivedReadTransaction ( ) )
2018-03-15 18:46:29 +01:00
self . uiDatabaseConnection = uiDatabaseConnection
2018-03-20 14:56:39 +01:00
self . showAllMediaButton = options . contains ( . showAllMediaButton )
self . sliderEnabled = options . contains ( . sliderEnabled )
2018-03-15 18:46:29 +01:00
self . mediaGalleryDataSource = mediaGalleryDataSource
2018-03-12 23:04:16 +01:00
let kSpacingBetweenItems : CGFloat = 20
2019-03-30 15:05:02 +01:00
let options : [ UIPageViewController . OptionsKey : Any ] = [ . interPageSpacing : kSpacingBetweenItems ]
2018-03-12 23:04:16 +01:00
super . init ( transitionStyle : . scroll ,
navigationOrientation : . horizontal ,
2019-03-30 15:05:02 +01:00
options : options )
2018-03-12 23:04:16 +01:00
self . dataSource = self
self . delegate = self
2018-03-15 18:46:29 +01:00
guard let initialPage = self . buildGalleryPage ( galleryItem : initialItem ) else {
2018-11-26 20:31:03 +01:00
owsFailDebug ( " unexpectedly unable to build initial gallery item " )
2018-03-12 23:04:16 +01:00
return
}
2018-03-15 18:46:29 +01:00
self . initialPage = initialPage
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
// MARK: B o t t o m B a r
2018-11-08 17:28:07 +01:00
var bottomContainer : UIView !
2018-03-12 23:04:16 +01:00
var footerBar : UIToolbar !
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
// N o t e : u s i n g a c u s t o m l e f t B a r B u t t o n I t e m b r e a k s t h e i n t e r a c t i v e p o p g e s t u r e , b u t w e d o n ' t w a n t t o b e a b l e
// t o s w i p e t o g o b a c k i n t h e p a g e r v i e w a n y w a y , i n s t e a d s w i p i n g b a c k s h o u l d s h o w t h e n e x t p a g e .
let backButton = OWSViewController . createOWSBackButton ( withTarget : self , selector : #selector ( didPressDismissButton ) )
self . navigationItem . leftBarButtonItem = backButton
2018-03-12 23:04:16 +01:00
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
// 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 {
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
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
2018-11-14 00:02:48 +01:00
galleryRailView . delegate = self
2018-11-14 17:09:24 +01:00
galleryRailView . autoSetDimension ( . height , toSize : 72 )
2018-11-14 00:02:48 +01:00
let footerBar = self . makeClearToolbar ( )
self . footerBar = footerBar
2019-12-12 06:49:48 +01:00
footerBar . tintColor = Colors . text
footerBar . setBackgroundImage ( UIImage ( ) , forToolbarPosition : . any , barMetrics : UIBarMetrics . default )
footerBar . setShadowImage ( UIImage ( ) , forToolbarPosition : . any )
footerBar . isTranslucent = false
footerBar . barTintColor = Colors . navigationBarBackground
2018-11-14 00:02:48 +01:00
2018-11-08 17:28:07 +01:00
let bottomContainer = UIView ( )
self . bottomContainer = bottomContainer
2019-12-12 06:49:48 +01:00
bottomContainer . backgroundColor = Colors . navigationBarBackground
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
bottomContainer . addSubview ( bottomStack )
bottomStack . autoPinEdgesToSuperviewEdges ( )
self . view . addSubview ( bottomContainer )
bottomContainer . autoPinWidthToSuperview ( )
2020-06-20 02:21:44 +02:00
bottomContainer . autoPinEdge ( . bottom , to : . bottom , of : view )
footerBar . bottomAnchor . constraint ( equalTo : view . safeAreaLayoutGuide . bottomAnchor ) . isActive = true
2018-11-14 00:02:48 +01:00
footerBar . autoSetDimension ( . height , toSize : 44 )
updateTitle ( )
2018-11-14 17:09:24 +01:00
updateCaption ( item : currentItem )
2018-11-14 00:02:48 +01:00
updateMediaRail ( )
updateFooterBarButtonItems ( isPlayingVideo : true )
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 )
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
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-11-14 17:09:24 +01:00
private func makeClearToolbar ( ) -> UIToolbar {
let toolbar = UIToolbar ( )
2019-12-12 06:49:48 +01:00
toolbar . backgroundColor = Colors . navigationBarBackground
2018-11-14 17:09:24 +01:00
// h i d e 1 p x t o p - b o r d e r
toolbar . clipsToBounds = true
return toolbar
}
2018-03-12 23:04:16 +01:00
private var shouldHideToolbars : Bool = false {
didSet {
if ( oldValue = = shouldHideToolbars ) {
return
}
// 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 = {
2018-11-14 17:09:24 +01:00
let shareBarButton = UIBarButtonItem ( barButtonSystemItem : . action , target : self , action : #selector ( didPressShare ) )
2018-11-15 17:23:32 +01:00
shareBarButton . tintColor = Theme . darkThemePrimaryColor
2018-11-14 17:09:24 +01:00
return shareBarButton
} ( )
2018-11-15 21:30:46 +01:00
lazy var deleteBarButton : UIBarButtonItem = {
2018-11-14 17:09:24 +01:00
let deleteBarButton = UIBarButtonItem ( barButtonSystemItem : . trash ,
target : self ,
action : #selector ( didPressDelete ) )
2018-11-15 17:23:32 +01:00
deleteBarButton . tintColor = Theme . darkThemePrimaryColor
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 = {
2018-11-14 17:09:24 +01:00
let videoPlayBarButton = UIBarButtonItem ( barButtonSystemItem : . play , target : self , action : #selector ( didPressPlayBarButton ) )
2018-11-15 17:23:32 +01:00
videoPlayBarButton . tintColor = Theme . darkThemePrimaryColor
2018-11-14 17:09:24 +01:00
return videoPlayBarButton
} ( )
2018-11-22 05:20:04 +01:00
lazy var videoPauseBarButton : UIBarButtonItem = {
2018-11-14 17:09:24 +01:00
let videoPauseBarButton = UIBarButtonItem ( barButtonSystemItem : . pause , target : self , action :
#selector ( didPressPauseBarButton ) )
2018-11-15 17:23:32 +01:00
videoPauseBarButton . tintColor = Theme . darkThemePrimaryColor
2018-11-14 17:09:24 +01:00
return videoPauseBarButton
} ( )
2018-03-12 23:04:16 +01:00
private func updateFooterBarButtonItems ( isPlayingVideo : Bool ) {
// T O D O d o w e s t i l l n e e d t h i s ? s e e m s l i k e a v e s t i g e
// f r o m w h e n m e d i a d e t a i l v i e w w a s u s e d f o r a t t a c h m e n t a p p r o v a l
2018-03-21 17:37:42 +01:00
if self . footerBar = = nil {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " No footer bar visible. " )
2018-03-12 23:04:16 +01:00
return
}
var toolbarItems : [ UIBarButtonItem ] = [
2018-11-14 17:09:24 +01:00
shareBarButton ,
buildFlexibleSpace ( )
2018-03-12 23:04:16 +01:00
]
2018-03-19 23:00:11 +01:00
if ( self . currentItem . isVideo ) {
2018-03-12 23:04:16 +01:00
toolbarItems += [
isPlayingVideo ? self . videoPauseBarButton : self . videoPlayBarButton ,
2018-11-14 17:09:24 +01:00
buildFlexibleSpace ( )
2018-03-12 23:04:16 +01:00
]
}
2018-11-14 17:09:24 +01:00
toolbarItems . append ( deleteBarButton )
2018-03-12 23:04:16 +01:00
self . footerBar . setItems ( toolbarItems , animated : false )
}
2018-11-14 00:02:48 +01:00
func updateMediaRail ( ) {
guard let currentItem = self . currentItem else {
owsFailDebug ( " currentItem was unexpectedly nil " )
return
}
2019-01-08 21:07:23 +01:00
galleryRailView . configureCellViews ( itemProvider : currentItem . album ,
focusedItem : currentItem ,
2019-04-20 00:21:00 +02:00
cellViewBuilder : { _ in return GalleryRailCellView ( ) } )
2018-11-14 00:02:48 +01:00
}
2018-03-12 23:04:16 +01:00
// MARK: A c t i o n s
2018-11-08 17:28:07 +01:00
@objc
public func didPressAllMediaButton ( sender : Any ) {
Logger . debug ( " " )
currentViewController . stopAnyVideo ( )
guard let mediaGalleryDataSource = self . mediaGalleryDataSource else {
owsFailDebug ( " mediaGalleryDataSource was unexpectedly nil " )
return
}
mediaGalleryDataSource . showAllMedia ( focusedItem : currentItem )
}
@objc
public func didSwipeView ( sender : Any ) {
Logger . debug ( " " )
self . dismissSelf ( animated : true )
}
2018-03-12 23:04:16 +01:00
@objc
public func didPressDismissButton ( _ sender : Any ) {
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
}
2018-11-08 04:12:28 +01:00
let attachmentStream = currentViewController . galleryItem . attachmentStream
AttachmentSharing . showShareUI ( forAttachment : attachmentStream )
2018-03-12 23:04:16 +01:00
}
@objc
public func didPressDelete ( _ 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
}
2018-03-22 23:47:40 +01:00
guard let mediaGalleryDataSource = self . mediaGalleryDataSource else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " mediaGalleryDataSource was unexpectedly nil " )
2018-03-22 23:47:40 +01:00
return
}
let actionSheet = UIAlertController ( title : nil , message : nil , preferredStyle : . actionSheet )
let deleteAction = UIAlertAction ( title : NSLocalizedString ( " TXT_DELETE_TITLE " , comment : " " ) ,
style : . destructive ) { _ in
let deletedItem = currentViewController . galleryItem
2018-03-26 23:57:46 +02:00
mediaGalleryDataSource . delete ( items : [ deletedItem ] , initiatedBy : self )
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
}
2018-03-26 23:57:46 +02:00
// MARK: M e d i a G a l l e r y D a t a S o u r c e D e l e g a t e
2019-02-20 20:44:30 +01:00
func mediaGalleryDataSource ( _ mediaGalleryDataSource : MediaGalleryDataSource , willDelete items : [ MediaGalleryItem ] , initiatedBy : AnyObject ) {
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2018-03-26 23:57:46 +02:00
guard let currentItem = self . currentItem else {
2019-02-20 20:44:30 +01:00
owsFailDebug ( " currentItem was unexpectedly nil " )
2018-03-26 23:57:46 +02:00
return
}
guard items . contains ( currentItem ) else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " irrelevant item " )
2018-03-26 23:57:46 +02:00
return
}
// I f w e s e t C u r r e n t I t e m w i t h ( a n i m a t e d : t r u e ) w h i l e t h i s V C i s i n t h e b a c k g r o u n d , t h e n
// t h e n e x t / p r e v i o u s c a c h e i s n ' t e x p i r e d , a n d w e ' r e a b l e t o s w i p e b a c k t o t h e j u s t - d e l e t e d v c .
// S o t o g e t t h e c o r r e c t b e h a v i o r , w e s h o u l d o n l y a n i m a t e t h e s e t r a n s i t i o n s w h e n t h i s
// v c i s i n t h e f o r e g r o u n d
let isAnimated = initiatedBy = = = self
if ! self . sliderEnabled {
// I n m e s s a g e d e t a i l s , w h i c h d o e s n ' t u s e t h e s l i d e r , s o d o n ' t s w a p p a g e s .
} else if let nextItem = mediaGalleryDataSource . galleryItem ( after : currentItem ) {
self . setCurrentItem ( nextItem , direction : . forward , animated : isAnimated )
} else if let previousItem = mediaGalleryDataSource . galleryItem ( before : currentItem ) {
self . setCurrentItem ( previousItem , direction : . reverse , animated : isAnimated )
} else {
// e l s e w e d e l e t e d t h e l a s t p i e c e o f m e d i a , r e t u r n t o t h e c o n v e r s a t i o n v i e w
self . dismissSelf ( animated : true )
}
}
func mediaGalleryDataSource ( _ mediaGalleryDataSource : MediaGalleryDataSource , deletedSections : IndexSet , deletedItems : [ IndexPath ] ) {
// n o - o p
}
2018-03-12 23:04:16 +01:00
@objc
public func didPressPlayBarButton ( _ 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
}
currentViewController . didPressPlayBarButton ( sender )
}
@objc
public func didPressPauseBarButton ( _ 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
}
currentViewController . didPressPauseBarButton ( sender )
}
// 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
}
2018-03-20 19:37:30 +01:00
updateTitle ( )
2018-11-14 00:02:48 +01:00
updateMediaRail ( )
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 ? {
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2018-03-15 18:46:29 +01:00
2018-03-19 23:00:11 +01:00
guard let previousDetailViewController = viewController as ? MediaDetailViewController else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " unexpected viewController: \( viewController ) " )
2018-03-12 23:04:16 +01:00
return nil
}
2018-03-19 18:44:59 +01:00
guard let mediaGalleryDataSource = self . mediaGalleryDataSource else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " mediaGalleryDataSource was unexpectedly nil " )
2018-03-19 18:44:59 +01:00
return nil
}
2018-03-19 23:00:11 +01:00
let previousItem = previousDetailViewController . galleryItem
guard let nextItem : MediaGalleryItem = mediaGalleryDataSource . galleryItem ( before : previousItem ) else {
2018-03-12 23:04:16 +01:00
return nil
}
2018-03-19 23:00:11 +01:00
guard let nextPage : MediaDetailViewController = buildGalleryPage ( galleryItem : nextItem ) else {
2018-03-12 23:04:16 +01:00
return nil
}
2018-03-19 23:00:11 +01:00
return nextPage
2018-03-12 23:04:16 +01:00
}
public func pageViewController ( _ pageViewController : UIPageViewController , viewControllerAfter viewController : UIViewController ) -> UIViewController ? {
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2018-03-12 23:04:16 +01:00
2018-03-19 23:00:11 +01:00
guard let previousDetailViewController = viewController as ? MediaDetailViewController else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " unexpected viewController: \( viewController ) " )
2018-03-12 23:04:16 +01:00
return nil
}
2018-03-19 18:44:59 +01:00
guard let mediaGalleryDataSource = self . mediaGalleryDataSource else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " mediaGalleryDataSource was unexpectedly nil " )
2018-03-19 18:44:59 +01:00
return nil
}
2018-03-19 23:00:11 +01:00
let previousItem = previousDetailViewController . galleryItem
guard let nextItem = mediaGalleryDataSource . galleryItem ( after : previousItem ) else {
// n o m o r e p a g e s
2018-03-12 23:04:16 +01:00
return nil
}
2018-03-19 23:00:11 +01:00
guard let nextPage : MediaDetailViewController = buildGalleryPage ( galleryItem : nextItem ) else {
2018-03-12 23:04:16 +01:00
return nil
}
2018-03-19 23:00:11 +01:00
return nextPage
2018-03-12 23:04:16 +01:00
}
2018-03-19 23:00:11 +01:00
private func buildGalleryPage ( galleryItem : MediaGalleryItem ) -> MediaDetailViewController ? {
if let cachedPage = cachedPages [ galleryItem ] {
2018-08-23 16:37:34 +02:00
Logger . debug ( " cache hit. " )
2018-03-19 23:00:11 +01:00
return cachedPage
}
2018-08-23 16:37:34 +02:00
Logger . debug ( " cache miss. " )
2018-08-08 15:29:23 +02:00
var fetchedItem : ConversationViewItem ?
2018-03-12 23:04:16 +01:00
self . uiDatabaseConnection . read { transaction in
2018-03-15 18:46:29 +01:00
let message = galleryItem . message
let thread = message . thread ( with : transaction )
2018-06-25 21:20:17 +02:00
let conversationStyle = ConversationStyle ( thread : thread )
2018-09-28 00:49:01 +02:00
fetchedItem = ConversationInteractionViewItem ( interaction : message ,
isGroupThread : thread . isGroupThread ( ) ,
2019-09-11 06:07:51 +02:00
isRSSFeed : false ,
2018-09-28 00:49:01 +02:00
transaction : transaction ,
conversationStyle : conversationStyle )
2018-03-12 23:04:16 +01:00
}
guard let viewItem = fetchedItem else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " viewItem was unexpectedly nil " )
2018-03-12 23:04:16 +01:00
return nil
}
2018-03-19 23:00:11 +01:00
let viewController = MediaDetailViewController ( galleryItemBox : GalleryItemBox ( galleryItem ) , viewItem : viewItem )
2018-03-12 23:04:16 +01:00
viewController . delegate = self
2018-03-19 23:00:11 +01:00
cachedPages [ galleryItem ] = viewController
return viewController
2018-03-12 23:04:16 +01:00
}
public func dismissSelf ( animated isAnimated : Bool , completion : ( ( ) -> Void ) ? = nil ) {
// 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
guard let mediaGalleryDataSource = self . mediaGalleryDataSource else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " mediaGalleryDataSource was unexpectedly nil " )
2018-03-19 18:44:59 +01:00
self . presentingViewController ? . dismiss ( animated : true )
return
}
2019-01-16 22:49:25 +01:00
if IsLandscapeOrientationEnabled ( ) {
mediaGalleryDataSource . dismissMediaDetailViewController ( self ,
animated : isAnimated ,
completion : completion )
} else {
mediaGalleryDataSource . dismissMediaDetailViewController ( self , animated : isAnimated ) {
UIDevice . current . ows_setOrientation ( . portrait )
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
@objc
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-11-08 04:12:28 +01:00
public func mediaDetailViewController ( _ mediaDetailViewController : MediaDetailViewController , requestDelete attachment : TSAttachment ) {
2018-03-22 19:10:41 +01:00
guard let mediaGalleryDataSource = self . mediaGalleryDataSource else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " mediaGalleryDataSource was unexpectedly nil " )
2018-03-22 19:10:41 +01:00
self . presentingViewController ? . dismiss ( animated : true )
return
}
2018-11-08 04:12:28 +01:00
guard let galleryItem = self . mediaGalleryDataSource ? . galleryItems . first ( where : { $0 . attachmentStream = = attachment } ) else {
owsFailDebug ( " galleryItem was unexpectedly nil " )
2018-03-23 15:00:07 +01:00
self . presentingViewController ? . dismiss ( animated : true )
return
}
2018-03-22 19:10:41 +01:00
dismissSelf ( animated : true ) {
2018-03-26 23:57:46 +02:00
mediaGalleryDataSource . delete ( items : [ galleryItem ] , initiatedBy : self )
2018-03-22 19:10:41 +01:00
}
}
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
// MARK: D y n a m i c H e a d e r
private var contactsManager : OWSContactsManager {
2018-08-31 19:22:19 +02:00
return Environment . shared . contactsManager
2018-03-20 19:37:30 +01:00
}
private func senderName ( message : TSMessage ) -> String {
switch message {
case let incomingMessage as TSIncomingMessage :
2019-10-17 05:04:13 +02:00
let hexEncodedPublicKey = incomingMessage . authorId
2019-10-21 01:12:39 +02:00
if incomingMessage . thread . isGroupThread ( ) {
2020-07-20 03:02:58 +02:00
var publicChat : PublicChat ?
2019-10-21 01:12:39 +02:00
OWSPrimaryStorage . shared ( ) . dbReadConnection . read { transaction in
publicChat = LokiDatabaseUtilities . getPublicChat ( for : incomingMessage . thread . uniqueId ! , in : transaction )
}
if let publicChat = publicChat {
2020-01-30 23:42:36 +01:00
return UserDisplayNameUtilities . getPublicChatDisplayName ( for : hexEncodedPublicKey , in : publicChat . channel , on : publicChat . server ) ? ? hexEncodedPublicKey
2019-10-21 01:12:39 +02:00
} else {
return hexEncodedPublicKey
}
} else {
2020-01-30 23:42:36 +01:00
return UserDisplayNameUtilities . getPrivateChatDisplayName ( for : hexEncodedPublicKey ) ? ? hexEncodedPublicKey
2019-10-21 01:12:39 +02:00
}
2018-03-20 19:37:30 +01:00
case is TSOutgoingMessage :
return NSLocalizedString ( " MEDIA_GALLERY_SENDER_NAME_YOU " , comment : " Short sender label for media sent by you " )
default :
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Unknown message type: \( type ( of : message ) ) " )
2018-03-20 19:37:30 +01:00
return " "
}
}
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
} ( )
2018-03-20 19:37:30 +01:00
private func updateTitle ( ) {
guard let currentItem = self . currentItem else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " currentItem was unexpectedly nil " )
2018-03-20 19:37:30 +01:00
return
}
updateTitle ( item : currentItem )
}
2018-11-09 23:26:12 +01:00
private func updateCaption ( item : MediaGalleryItem ) {
2018-11-14 17:09:24 +01:00
captionContainerView . currentText = item . captionForDisplay
2018-11-09 23:26:12 +01:00
}
2018-03-20 19:37:30 +01:00
private func updateTitle ( item : MediaGalleryItem ) {
let name = senderName ( message : item . message )
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
let date = Date ( timeIntervalSince1970 : Double ( item . message . timestamp ) / 1000 )
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
// 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
}
2018-03-12 23:04:16 +01:00
}
2018-11-08 17:28:07 +01:00
2018-11-15 03:01:21 +01:00
extension MediaGalleryItem : 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
if let image = self . thumbnailImage ( async : { fulfill ( $0 ) } ) {
fulfill ( image )
}
2018-11-15 03:01:21 +01:00
}
}
}
extension MediaGalleryAlbum : GalleryRailItemProvider {
var railItems : [ GalleryRailItem ] {
return self . items
}
}
2018-11-14 00:02:48 +01:00
extension MediaPageViewController : GalleryRailViewDelegate {
func galleryRailView ( _ galleryRailView : GalleryRailView , didTapItem imageRailItem : GalleryRailItem ) {
guard let targetItem = imageRailItem as ? MediaGalleryItem else {
owsFailDebug ( " unexpected imageRailItem: \( imageRailItem ) " )
return
}
2019-03-30 14:22:31 +01:00
let direction : UIPageViewController . NavigationDirection
2018-11-16 00:16:38 +01:00
direction = currentItem . albumIndex < targetItem . albumIndex ? . forward : . reverse
2018-11-14 00:02:48 +01:00
self . setCurrentItem ( targetItem , direction : direction , animated : true )
}
}
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
}