| 
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -239,7 +239,6 @@ protocol MediaGalleryDataSourceDelegate: class {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				class MediaGalleryNavigationController: OWSNavigationController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    var presentationView: UIImageView!
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    var retainUntilDismissed: MediaGallery?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // HACK: Though we don't have an input accessory view, the VC we are presented above (ConversationVC) does.
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -250,16 +249,6 @@ class MediaGalleryNavigationController: OWSNavigationController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    override public func becomeFirstResponder() -> Bool {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        Logger.debug("")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return super.becomeFirstResponder()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    override public func resignFirstResponder() -> Bool {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        Logger.debug("")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return super.resignFirstResponder()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // MARK: View Lifecycle
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    override var preferredStatusBarStyle: UIStatusBarStyle {
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -269,23 +258,6 @@ class MediaGalleryNavigationController: OWSNavigationController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    override func viewDidLoad() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        super.viewDidLoad()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // UIModalPresentationCustom retains the current view context behind our VC, allowing us to manually
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // animate in our view, over the existing context, similar to a cross disolve, but allowing us to have
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // more fine grained control
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.modalPresentationStyle = .custom
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // The presentationView is only used during present/dismiss animations.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // It's a static image of the media content.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let presentationView = UIImageView()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.presentationView = presentationView
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.view.insertSubview(presentationView, at: 0)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        presentationView.isHidden = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        presentationView.clipsToBounds = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        presentationView.layer.allowsEdgeAntialiasing = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        presentationView.layer.minificationFilter = .trilinear
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        presentationView.layer.magnificationFilter = .trilinear
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        presentationView.contentMode = .scaleAspectFit
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        guard let navigationBar = self.navigationBar as? OWSNavigationBar else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            owsFailDebug("navigationBar had unexpected class: \(self.navigationBar)")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -363,14 +335,8 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return self.pageViewController!.currentItem
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private var replacingView: UIView?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private var presentationViewConstraints: [NSLayoutConstraint] = []
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // TODO rename to replacingOriginRect
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private var originRect: CGRect?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @objc
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public func presentDetailView(fromViewController: UIViewController, mediaAttachment: TSAttachment, replacingView: UIView) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public func presentDetailView(fromViewController: UIViewController, mediaAttachment: TSAttachment) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        var galleryItem: MediaGalleryItem?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        uiDatabaseConnection.read { transaction in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            galleryItem = self.buildGalleryItem(attachment: mediaAttachment, transaction: transaction)
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -380,10 +346,10 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        presentDetailView(fromViewController: fromViewController, initialDetailItem: initialDetailItem, replacingView: replacingView)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        presentDetailView(fromViewController: fromViewController, initialDetailItem: initialDetailItem)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public func presentDetailView(fromViewController: UIViewController, initialDetailItem: MediaGalleryItem, replacingView: UIView) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public func presentDetailView(fromViewController: UIViewController, initialDetailItem: MediaGalleryItem) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // For a speedy load, we only fetch a few items on either side of
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // the initial message
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ensureGalleryItemsLoaded(.around, item: initialDetailItem, amount: 10)
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -405,103 +371,10 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.setViewControllers([pageViewController], animated: false)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.replacingView = replacingView
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let convertedRect: CGRect = replacingView.convert(replacingView.bounds, to: UIApplication.shared.keyWindow)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.originRect = convertedRect
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.modalPresentationStyle = .fullScreen
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.modalTransitionStyle = .crossDissolve
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // loadView hasn't necessarily been called yet.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.loadViewIfNeeded()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.presentationView.image = initialDetailItem.attachmentStream.thumbnailImageLarge(success: { [weak self] (image) in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            guard let strongSelf = self else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            strongSelf.navigationController.presentationView.image = image
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }, failure: {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                Logger.warn("Could not load presentation image.")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.applyInitialMediaViewConstraints()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Restore presentationView.alpha in case a previous dismiss left us in a bad state.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.setNavigationBarHidden(false, animated: false)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.presentationView.alpha = 1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // We want to animate the tapped media from it's position in the previous VC
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // to it's resting place in the center of this view controller.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        //
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Rather than animating the actual media view in place, we animate the presentationView, which is a static
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // image of the media content. Animating the actual media view is problematic for a couple reasons:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // 1. The media view ultimately lives in a zoomable scrollView. Getting both original positioning and the final positioning
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        //    correct, involves manipulating the zoomScale and position simultaneously, which results in non-linear movement,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        //    especially noticeable on high resolution images.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // 2. For Video views, the AVPlayerLayer content does not scale with the presentation animation. So you instead get a full scale
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        //    video, wherein only the cropping is animated.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Using a simple image view allows us to address both these problems relatively easily.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.view.alpha = 0.0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        guard let detailView = pageViewController.view else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            owsFailDebug("detailView was unexpectedly nil")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // At this point our media view should be overlayed perfectly
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // by our presentationView. Swapping them out should be imperceptible.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.presentationView.isHidden = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // We don't hide the pageViewController entirely - e.g. we want the toolbars to fade in.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        pageViewController.currentViewController.view.isHidden = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        detailView.backgroundColor = .clear
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.view.backgroundColor = .clear
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.presentationView.layer.cornerRadius = VisibleMessageCell.smallCornerRadius
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        fromViewController.present(navigationController, animated: false) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // 1. Fade in the entire view.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            UIView.animate(withDuration: 0.1) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.replacingView?.alpha = 0.0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                self.navigationController.view.alpha = 1.0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.navigationController.presentationView.superview?.layoutIfNeeded()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.applyFinalMediaViewConstraints()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // 2. Animate imageView from it's initial position, which should match where it was
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // in the presenting view to it's final position, front and center in this view. This
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // animation duration intentionally overlaps the previous
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            UIView.animate(withDuration: 0.2,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                           delay: 0.08,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                           options: .curveEaseOut,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                           animations: {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            self.navigationController.presentationView.layer.cornerRadius = 0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            self.navigationController.presentationView.superview?.layoutIfNeeded()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            // fade out content behind the pageViewController
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            // and behind the presentation view
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            self.navigationController.view.backgroundColor = Theme.darkThemeBackgroundColor
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                           completion: { (_: Bool) in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            // At this point our presentation view should be overlayed perfectly
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            // with our media view. Swapping them out should be imperceptible.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            pageViewController.currentViewController.view.isHidden = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            self.navigationController.presentationView.isHidden = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            self.navigationController.view.isUserInteractionEnabled = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            pageViewController.wasPresented()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            // Since we're presenting *over* the ConversationVC, we need to `becomeFirstResponder`.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            //
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            // Otherwise, the `ConversationVC.inputAccessoryView` will appear over top of us whenever
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            // OWSWindowManager window juggling calls `[rootWindow makeKeyAndVisible]`.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            //
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            // We don't need to do this when pushing VCs onto the SignalsNavigationController - only when
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            // presenting directly from ConversationVC.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            _ = self.navigationController.becomeFirstResponder()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        fromViewController.present(navigationController, animated: true, completion: nil)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // If we're using a navigationController other than self to present the views
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -554,7 +427,7 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // [ConversationView]
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            //
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.presentDetailView(fromViewController: mediaTileViewController, initialDetailItem: mediaGalleryItem, replacingView: tappedView)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.presentDetailView(fromViewController: mediaTileViewController, initialDetailItem: mediaGalleryItem)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // If we got to the gallery via the conversation view, pop the tile view
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // to return to the detail view
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -596,127 +469,7 @@ class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDel
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.view.isUserInteractionEnabled = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        guard let detailView = mediaPageViewController.view else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            owsFailDebug("detailView was unexpectedly nil")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.navigationController.presentingViewController?.dismiss(animated: false, completion: completion)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        mediaPageViewController.currentViewController.view.isHidden = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        navigationController.presentationView.isHidden = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Move the presentationView back to it's initial position, i.e. where
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // it sits on the screen in the conversation view.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let changedItems = currentItem != self.initialDetailItem
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if changedItems {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            navigationController.presentationView.image = currentItem.attachmentStream.thumbnailImageLarge(success: { [weak self] (image) in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                guard let strongSelf = self else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                strongSelf.navigationController.presentationView.image = image
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                }, failure: {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    Logger.warn("Could not load presentation image.")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.applyOffscreenMediaViewConstraints()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.applyInitialMediaViewConstraints()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if isAnimated {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            UIView.animate(withDuration: changedItems ? 0.25 : 0.18,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                           delay: 0.0,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                           options: .curveEaseOut,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                           animations: {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            // Move back over it's original location
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            self.navigationController.presentationView.superview?.layoutIfNeeded()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            detailView.alpha = 0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            if changedItems {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                self.navigationController.presentationView.alpha = 0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                self.navigationController.presentationView.layer.cornerRadius = VisibleMessageCell.smallCornerRadius
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // This intentionally overlaps the previous animation a bit
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            UIView.animate(withDuration: 0.1,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                           delay: 0.15,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                           options: .curveEaseInOut,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                           animations: {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            guard let replacingView = self.replacingView else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                owsFailDebug("replacingView was unexpectedly nil")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                presentingViewController.dismiss(animated: false, completion: completion)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            // fade out content and toolbars
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            self.navigationController.view.alpha = 0.0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            replacingView.alpha = 1.0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                           completion: { (_: Bool) in
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                            presentingViewController.dismiss(animated: false, completion: completion)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            guard let replacingView = self.replacingView else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                owsFailDebug("replacingView was unexpectedly nil")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                presentingViewController.dismiss(animated: false, completion: completion)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            replacingView.alpha = 1.0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            presentingViewController.dismiss(animated: false, completion: completion)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private func applyInitialMediaViewConstraints() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (self.presentationViewConstraints.count > 0) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            NSLayoutConstraint.deactivate(self.presentationViewConstraints)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.presentationViewConstraints = []
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        guard let originRect = self.originRect else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            owsFailDebug("originRect was unexpectedly nil")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        guard let presentationSuperview = navigationController.presentationView.superview else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            owsFailDebug("presentationView.superview was unexpectedly nil")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let convertedRect: CGRect = presentationSuperview.convert(originRect, from: UIApplication.shared.keyWindow)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.presentationViewConstraints += navigationController.presentationView.autoSetDimensions(to: convertedRect.size)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.presentationViewConstraints += [
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            navigationController.presentationView.autoPinEdge(toSuperviewEdge: .top, withInset: convertedRect.origin.y),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            navigationController.presentationView.autoPinEdge(toSuperviewEdge: .left, withInset: convertedRect.origin.x)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ]
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private func applyFinalMediaViewConstraints() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (self.presentationViewConstraints.count > 0) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            NSLayoutConstraint.deactivate(self.presentationViewConstraints)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.presentationViewConstraints = []
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.presentationViewConstraints = [
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            navigationController.presentationView.autoPinEdge(toSuperviewEdge: .leading),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            navigationController.presentationView.autoPinEdge(toSuperviewEdge: .top),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            navigationController.presentationView.autoPinEdge(toSuperviewEdge: .trailing),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            navigationController.presentationView.autoPinEdge(toSuperviewEdge: .bottom)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ]
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private func applyOffscreenMediaViewConstraints() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (self.presentationViewConstraints.count > 0) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            NSLayoutConstraint.deactivate(self.presentationViewConstraints)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.presentationViewConstraints = []
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.presentationViewConstraints += [
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            navigationController.presentationView.autoPinEdge(toSuperviewEdge: .leading),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            navigationController.presentationView.autoPinEdge(toSuperviewEdge: .trailing),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            navigationController.presentationView.autoPinEdge(.top, to: .bottom, of: self.navigationController.view)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ]
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        presentingViewController.dismiss(animated: true, completion: completion)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // MARK: - Database Notifications
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
				
			
			 | 
			 | 
			
				
 
 |