mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			559 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			559 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Swift
		
	
| //
 | |
| //  Copyright (c) 2019 Open Whisper Systems. All rights reserved.
 | |
| //
 | |
| 
 | |
| import Foundation
 | |
| import UIKit
 | |
| import AVFoundation
 | |
| import SessionUIKit
 | |
| 
 | |
| protocol AttachmentPrepViewControllerDelegate: AnyObject {
 | |
|     func prepViewControllerUpdateNavigationBar()
 | |
| 
 | |
|     func prepViewControllerUpdateControls()
 | |
| }
 | |
| 
 | |
| // MARK: -
 | |
| 
 | |
| public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarDelegate, OWSVideoPlayerDelegate, MediaMessageViewAudioDelegate {
 | |
|     // We sometimes shrink the attachment view so that it remains somewhat visible
 | |
|     // when the keyboard is presented.
 | |
|     public enum AttachmentViewScale {
 | |
|         case fullsize, compact
 | |
|     }
 | |
| 
 | |
|     // MARK: - Properties
 | |
| 
 | |
|     weak var prepDelegate: AttachmentPrepViewControllerDelegate?
 | |
| 
 | |
|     let attachmentItem: SignalAttachmentItem
 | |
|     var attachment: SignalAttachment {
 | |
|         return attachmentItem.attachment
 | |
|     }
 | |
| 
 | |
|     private lazy var videoPlayer: OWSVideoPlayer? = {
 | |
|         guard let videoURL = attachment.dataUrl else {
 | |
|             owsFailDebug("Missing videoURL")
 | |
|             return nil
 | |
|         }
 | |
| 
 | |
|         let player: OWSVideoPlayer = OWSVideoPlayer(url: videoURL)
 | |
|         player.delegate = self
 | |
|         
 | |
|         return player
 | |
|     }()
 | |
|     
 | |
|     // MARK: - UI
 | |
|     
 | |
|     fileprivate static let verticalCenterOffset: CGFloat = (
 | |
|         AttachmentTextToolbar.kMinTextViewHeight + (AttachmentTextToolbar.kToolbarMargin * 2)
 | |
|     )
 | |
|     
 | |
|     private lazy var scrollView: UIScrollView = {
 | |
|         // Scroll View - used to zoom/pan on images and video
 | |
|         let scrollView: UIScrollView = UIScrollView()
 | |
|         scrollView.translatesAutoresizingMaskIntoConstraints = false
 | |
|         scrollView.delegate = self
 | |
|         scrollView.showsHorizontalScrollIndicator = false
 | |
|         scrollView.showsVerticalScrollIndicator = false
 | |
| 
 | |
|         // Panning should stop pretty soon after the user stops scrolling
 | |
|         scrollView.decelerationRate = UIScrollView.DecelerationRate.fast
 | |
|         
 | |
|         return scrollView
 | |
|     }()
 | |
|     
 | |
|     private lazy var contentContainerView: UIView = {
 | |
|         // Anything that should be shrunk when user pops keyboard lives in the contentContainer.
 | |
|         let view: UIView = UIView()
 | |
|         view.translatesAutoresizingMaskIntoConstraints = false
 | |
| 
 | |
|         return view
 | |
|     }()
 | |
|     
 | |
|     private lazy var mediaMessageView: MediaMessageView = {
 | |
|         let view: MediaMessageView = MediaMessageView(attachment: attachment, mode: .attachmentApproval)
 | |
|         view.translatesAutoresizingMaskIntoConstraints = false
 | |
|         view.audioDelegate = self
 | |
|         view.isHidden = (imageEditorView != nil)
 | |
|         
 | |
|         return view
 | |
|     }()
 | |
|     
 | |
|     private lazy var imageEditorView: ImageEditorView? = {
 | |
|         guard let imageEditorModel = attachmentItem.imageEditorModel else { return nil }
 | |
|         
 | |
|         let view: ImageEditorView = ImageEditorView(model: imageEditorModel, delegate: self)
 | |
|         view.translatesAutoresizingMaskIntoConstraints = false
 | |
|         
 | |
|         guard view.configureSubviews() else { return nil }
 | |
|         
 | |
|         return view
 | |
|     }()
 | |
|     
 | |
|     private lazy var videoPlayerView: VideoPlayerView? = {
 | |
|         guard let videoPlayer: OWSVideoPlayer = videoPlayer else { return nil }
 | |
| 
 | |
|         let view: VideoPlayerView = VideoPlayerView()
 | |
|         view.translatesAutoresizingMaskIntoConstraints = false
 | |
|         view.player = videoPlayer.avPlayer
 | |
| 
 | |
|         let pauseGesture = UITapGestureRecognizer(target: self, action: #selector(didTapPlayerView(_:)))
 | |
|         view.addGestureRecognizer(pauseGesture)
 | |
|         
 | |
|         return view
 | |
|     }()
 | |
|     
 | |
|     private lazy var progressBar: PlayerProgressBar = {
 | |
|         let progressBar: PlayerProgressBar = PlayerProgressBar()
 | |
|         progressBar.translatesAutoresizingMaskIntoConstraints = false
 | |
|         progressBar.player = videoPlayer?.avPlayer
 | |
|         progressBar.delegate = self
 | |
|         
 | |
|         return progressBar
 | |
|     }()
 | |
|     
 | |
|     private lazy var playVideoButton: UIButton = {
 | |
|         let button: UIButton = UIButton()
 | |
|         button.translatesAutoresizingMaskIntoConstraints = false
 | |
|         button.contentMode = .scaleAspectFit
 | |
|         button.setBackgroundImage(#imageLiteral(resourceName: "CirclePlay"), for: .normal)
 | |
|         button.addTarget(self, action: #selector(playButtonTapped), for: .touchUpInside)
 | |
|         
 | |
|         return button
 | |
|     }()
 | |
| 
 | |
|     public var shouldHideControls: Bool {
 | |
|         guard let imageEditorView = imageEditorView else {
 | |
|             return false
 | |
|         }
 | |
|         return imageEditorView.shouldHideControls
 | |
|     }
 | |
| 
 | |
|     // MARK: - Initializers
 | |
| 
 | |
|     init(attachmentItem: SignalAttachmentItem) {
 | |
|         self.attachmentItem = attachmentItem
 | |
|         super.init(nibName: nil, bundle: nil)
 | |
|         if attachment.hasError {
 | |
|             owsFailDebug(attachment.error.debugDescription)
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public required init?(coder aDecoder: NSCoder) {
 | |
|         fatalError("init(coder:) has not been implemented")
 | |
|     }
 | |
| 
 | |
|     // MARK: - View Lifecycle
 | |
|     
 | |
|     public override func viewDidLoad() {
 | |
|         super.viewDidLoad()
 | |
|         
 | |
|         view.backgroundColor = Colors.navigationBarBackground
 | |
| 
 | |
|         view.addSubview(contentContainerView)
 | |
|         
 | |
|         contentContainerView.addSubview(scrollView)
 | |
|         scrollView.addSubview(mediaMessageView)
 | |
|         
 | |
|         if attachment.isImage, let editorView: ImageEditorView = imageEditorView {
 | |
|             view.addSubview(editorView)
 | |
|             
 | |
|             imageEditorUpdateNavigationBar()
 | |
|         }
 | |
| 
 | |
|         // Hide the play button embedded in the MediaView and replace it with our own.
 | |
|         // This allows us to zoom in on the media view without zooming in on the button
 | |
|         // TODO: This for both Audio and Video?
 | |
|         if attachment.isVideo, let playerView: VideoPlayerView = videoPlayerView {
 | |
|             mediaMessageView.videoPlayButton.isHidden = true
 | |
|             mediaMessageView.addSubview(playerView)
 | |
|             
 | |
|             // We don't want the progress bar to zoom during "pinch-to-zoom"
 | |
|             // but we do want it to shrink with the media content when the user
 | |
|             // pops the keyboard.
 | |
|             contentContainerView.addSubview(progressBar)
 | |
|             contentContainerView.addSubview(playVideoButton)
 | |
|         }
 | |
|         else if attachment.isAudio, mediaMessageView.audioPlayer != nil {
 | |
|             contentContainerView.addSubview(progressBar)
 | |
|         }
 | |
|         
 | |
|         setupLayout()
 | |
|     }
 | |
| 
 | |
|     override public func viewWillAppear(_ animated: Bool) {
 | |
|         super.viewWillAppear(animated)
 | |
|         
 | |
|         prepDelegate?.prepViewControllerUpdateNavigationBar()
 | |
|         prepDelegate?.prepViewControllerUpdateControls()
 | |
|     }
 | |
| 
 | |
|     override public func viewDidAppear(_ animated: Bool) {
 | |
|         super.viewDidAppear(animated)
 | |
| 
 | |
|         prepDelegate?.prepViewControllerUpdateNavigationBar()
 | |
|         prepDelegate?.prepViewControllerUpdateControls()
 | |
|     }
 | |
|     
 | |
|     override public func viewWillLayoutSubviews() {
 | |
|         super.viewWillLayoutSubviews()
 | |
|         
 | |
|         setupZoomScale()
 | |
|         ensureAttachmentViewScale(animated: false)
 | |
|     }
 | |
|     
 | |
|     public override func viewDidLayoutSubviews() {
 | |
|         super.viewDidLayoutSubviews()
 | |
|         
 | |
|         // Note: Need to do this here to ensure it's based on the final sizing
 | |
|         // otherwise the offsets will be slightly off
 | |
|         resetContentInset()
 | |
|     }
 | |
|     
 | |
|     // MARK: - Layout
 | |
|     
 | |
|     private func setupLayout() {
 | |
|         NSLayoutConstraint.activate([
 | |
|             contentContainerView.topAnchor.constraint(equalTo: view.topAnchor),
 | |
|             contentContainerView.leftAnchor.constraint(equalTo: view.leftAnchor),
 | |
|             contentContainerView.rightAnchor.constraint(equalTo: view.rightAnchor),
 | |
|             contentContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
 | |
|             
 | |
|             scrollView.topAnchor.constraint(equalTo: contentContainerView.topAnchor),
 | |
|             scrollView.leftAnchor.constraint(equalTo: contentContainerView.leftAnchor),
 | |
|             scrollView.rightAnchor.constraint(equalTo: contentContainerView.rightAnchor),
 | |
|             scrollView.bottomAnchor.constraint(equalTo: contentContainerView.bottomAnchor),
 | |
|             
 | |
|             mediaMessageView.topAnchor.constraint(equalTo: scrollView.topAnchor),
 | |
|             mediaMessageView.leftAnchor.constraint(equalTo: scrollView.leftAnchor),
 | |
|             mediaMessageView.rightAnchor.constraint(equalTo: scrollView.rightAnchor),
 | |
|             mediaMessageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
 | |
|             mediaMessageView.widthAnchor.constraint(equalTo: view.widthAnchor),
 | |
|             mediaMessageView.heightAnchor.constraint(equalTo: view.heightAnchor)
 | |
|         ])
 | |
|         
 | |
|         if attachment.isImage, let editorView: ImageEditorView = imageEditorView {
 | |
|             let size: CGSize = (attachment.image()?.size ?? CGSize.zero)
 | |
|             let isPortrait: Bool = (size.height > size.width)
 | |
|             
 | |
|             NSLayoutConstraint.activate([
 | |
|                 editorView.topAnchor.constraint(equalTo: view.topAnchor),
 | |
|                 editorView.leftAnchor.constraint(equalTo: view.leftAnchor),
 | |
|                 editorView.rightAnchor.constraint(equalTo: view.rightAnchor),
 | |
|                 editorView.bottomAnchor.constraint(
 | |
|                     equalTo: view.bottomAnchor,
 | |
|                     // Don't offset portrait images as they look fine vertically aligned, horizontal
 | |
|                     // ones need to be pushed up a bit though
 | |
|                     constant: (isPortrait ? 0 : -AttachmentPrepViewController.verticalCenterOffset)
 | |
|                 )
 | |
|             ])
 | |
|         }
 | |
|          
 | |
|         if attachment.isVideo, let playerView: VideoPlayerView = videoPlayerView {
 | |
|             let playButtonSize: CGFloat = ScaleFromIPhone5(70)
 | |
|             
 | |
|             NSLayoutConstraint.activate([
 | |
|                 playerView.topAnchor.constraint(equalTo: mediaMessageView.topAnchor),
 | |
|                 playerView.leftAnchor.constraint(equalTo: mediaMessageView.leftAnchor),
 | |
|                 playerView.rightAnchor.constraint(equalTo: mediaMessageView.rightAnchor),
 | |
|                 playerView.bottomAnchor.constraint(equalTo: mediaMessageView.bottomAnchor),
 | |
|                 
 | |
|                 progressBar.topAnchor.constraint(equalTo: view.topAnchor),
 | |
|                 progressBar.widthAnchor.constraint(equalTo: contentContainerView.widthAnchor),
 | |
|                 progressBar.heightAnchor.constraint(equalToConstant: 44),
 | |
|                 
 | |
|                 playVideoButton.centerXAnchor.constraint(equalTo: contentContainerView.centerXAnchor),
 | |
|                 playVideoButton.centerYAnchor.constraint(
 | |
|                     equalTo: contentContainerView.centerYAnchor,
 | |
|                     constant: -AttachmentPrepViewController.verticalCenterOffset
 | |
|                 ),
 | |
|                 playVideoButton.widthAnchor.constraint(equalToConstant: playButtonSize),
 | |
|                 playVideoButton.heightAnchor.constraint(equalToConstant: playButtonSize),
 | |
|             ])
 | |
|         }
 | |
|         else if attachment.isAudio, mediaMessageView.audioPlayer != nil {
 | |
|             NSLayoutConstraint.activate([
 | |
|                 progressBar.topAnchor.constraint(equalTo: view.topAnchor),
 | |
|                 progressBar.widthAnchor.constraint(equalTo: contentContainerView.widthAnchor),
 | |
|                 progressBar.heightAnchor.constraint(equalToConstant: 44)
 | |
|             ])
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // MARK: - Navigation Bar
 | |
| 
 | |
|     public func navigationBarItems() -> [UIView] {
 | |
|         guard let imageEditorView = imageEditorView else {
 | |
|             return []
 | |
|         }
 | |
|         
 | |
|         return imageEditorView.navigationBarItems()
 | |
|     }
 | |
| 
 | |
|     // MARK: - Event Handlers
 | |
| 
 | |
|     @objc public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) {
 | |
|         assert(self.videoPlayer != nil)
 | |
|         self.pauseVideo()
 | |
|     }
 | |
| 
 | |
|     @objc public func playButtonTapped() {
 | |
|         self.playVideo()
 | |
|     }
 | |
| 
 | |
|     // MARK: - Video
 | |
| 
 | |
|     private func playVideo() {
 | |
|         guard let videoPlayer = self.videoPlayer else {
 | |
|             owsFailDebug("video player was unexpectedly nil")
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         UIView.animate(withDuration: 0.1) { [weak self] in
 | |
|             self?.playVideoButton.alpha = 0.0
 | |
|         }
 | |
|         
 | |
|         videoPlayer.play()
 | |
|     }
 | |
| 
 | |
|     private func pauseVideo() {
 | |
|         guard let videoPlayer = self.videoPlayer else {
 | |
|             owsFailDebug("video player was unexpectedly nil")
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         videoPlayer.pause()
 | |
|         
 | |
|         UIView.animate(withDuration: 0.1) { [weak self] in
 | |
|             self?.playVideoButton.alpha = 1.0
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @objc public func videoPlayerDidPlayToCompletion(_ videoPlayer: OWSVideoPlayer) {
 | |
|         UIView.animate(withDuration: 0.1) { [weak self] in
 | |
|             self?.playVideoButton.alpha = 1.0
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public func playerProgressBarDidStartScrubbing(_ playerProgressBar: PlayerProgressBar) {
 | |
|         if attachment.isAudio {
 | |
|             mediaMessageView.pauseAudio()
 | |
|             return
 | |
|         }
 | |
|         
 | |
|         guard let videoPlayer = self.videoPlayer else {
 | |
|             owsFailDebug("video player was unexpectedly nil")
 | |
|             return
 | |
|         }
 | |
|         
 | |
|         videoPlayer.pause()
 | |
|     }
 | |
| 
 | |
|     public func playerProgressBar(_ playerProgressBar: PlayerProgressBar, scrubbedToTime time: CMTime) {
 | |
|         if attachment.isAudio {
 | |
|             mediaMessageView.setAudioTime(currentTime: CMTimeGetSeconds(time))
 | |
|             progressBar.manuallySetValue(CMTimeGetSeconds(time), durationSeconds: mediaMessageView.audioDurationSeconds)
 | |
|             return
 | |
|         }
 | |
|         
 | |
|         guard let videoPlayer = self.videoPlayer else {
 | |
|             owsFailDebug("video player was unexpectedly nil")
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         videoPlayer.seek(to: time)
 | |
|         progressBar.updateState()
 | |
|     }
 | |
| 
 | |
|     public func playerProgressBar(_ playerProgressBar: PlayerProgressBar, didFinishScrubbingAtTime time: CMTime, shouldResumePlayback: Bool) {
 | |
|         if attachment.isAudio {
 | |
|             mediaMessageView.setAudioTime(currentTime: CMTimeGetSeconds(time))
 | |
|             progressBar.manuallySetValue(CMTimeGetSeconds(time), durationSeconds: mediaMessageView.audioDurationSeconds)
 | |
|             
 | |
|             if mediaMessageView.wasPlayingAudio {
 | |
|                 mediaMessageView.playAudio()
 | |
|             }
 | |
|             return
 | |
|         }
 | |
|         
 | |
|         guard let videoPlayer = self.videoPlayer else {
 | |
|             owsFailDebug("video player was unexpectedly nil")
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         videoPlayer.seek(to: time)
 | |
|         progressBar.updateState()
 | |
|         
 | |
|         if (shouldResumePlayback) {
 | |
|             videoPlayer.play()
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     // MARK: - MediaMessageViewAudioDelegate
 | |
|     
 | |
|     public func progressChanged(_ progressSeconds: CGFloat, durationSeconds: CGFloat) {
 | |
|         progressBar.manuallySetValue(progressSeconds, durationSeconds: durationSeconds)
 | |
|     }
 | |
| 
 | |
|     // MARK: - Helpers
 | |
| 
 | |
|     var isZoomable: Bool {
 | |
|         return attachment.isImage || attachment.isVideo
 | |
|     }
 | |
| 
 | |
|     func zoomOut(animated: Bool) {
 | |
|         if self.scrollView.zoomScale != self.scrollView.minimumZoomScale {
 | |
|             self.scrollView.setZoomScale(self.scrollView.minimumZoomScale, animated: animated)
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // When the keyboard is popped, it can obscure the attachment view.
 | |
|     // so we sometimes allow resizing the attachment.
 | |
|     var shouldAllowAttachmentViewResizing: Bool = true
 | |
| 
 | |
|     var attachmentViewScale: AttachmentViewScale = .fullsize
 | |
|     
 | |
|     public func setAttachmentViewScale(_ attachmentViewScale: AttachmentViewScale, animated: Bool) {
 | |
|         self.attachmentViewScale = attachmentViewScale
 | |
|         ensureAttachmentViewScale(animated: animated)
 | |
|     }
 | |
| 
 | |
|     func ensureAttachmentViewScale(animated: Bool) {
 | |
|         let animationDuration = animated ? 0.2 : 0
 | |
|         guard shouldAllowAttachmentViewResizing else {
 | |
|             if self.contentContainerView.transform != CGAffineTransform.identity {
 | |
|                 UIView.animate(withDuration: animationDuration) {
 | |
|                     self.contentContainerView.transform = CGAffineTransform.identity
 | |
|                 }
 | |
|             }
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         switch attachmentViewScale {
 | |
|         case .fullsize:
 | |
|             guard self.contentContainerView.transform != .identity else {
 | |
|                 return
 | |
|             }
 | |
|             UIView.animate(withDuration: animationDuration) {
 | |
|                 self.contentContainerView.transform = CGAffineTransform.identity
 | |
|             }
 | |
|         case .compact:
 | |
|             guard self.contentContainerView.transform == .identity else {
 | |
|                 return
 | |
|             }
 | |
|             UIView.animate(withDuration: animationDuration) {
 | |
|                 let kScaleFactor: CGFloat = 0.7
 | |
|                 let scale = CGAffineTransform(scaleX: kScaleFactor, y: kScaleFactor)
 | |
| 
 | |
|                 let originalHeight = self.scrollView.bounds.size.height
 | |
| 
 | |
|                 // Position the new scaled item to be centered with respect
 | |
|                 // to it's new size.
 | |
|                 let heightDelta = originalHeight * (1 - kScaleFactor)
 | |
|                 let translate = CGAffineTransform(translationX: 0, y: -heightDelta / 2)
 | |
| 
 | |
|                 self.contentContainerView.transform = scale.concatenating(translate)
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: -
 | |
| 
 | |
| extension AttachmentPrepViewController: UIScrollViewDelegate {
 | |
| 
 | |
|     public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
 | |
|         if isZoomable {
 | |
|             return mediaMessageView
 | |
|         }
 | |
|         
 | |
|         // Don't zoom for audio or generic attachments.
 | |
|         return nil
 | |
|     }
 | |
|     
 | |
|     public func scrollViewDidZoom(_ scrollView: UIScrollView) {
 | |
|         resetContentInset()
 | |
|     }
 | |
| 
 | |
|     fileprivate func setupZoomScale() {
 | |
|         // We only want to setup the zoom scale once (otherwise we get glitchy behaviour
 | |
|         // when anything forces a re-layout)
 | |
|         guard abs(scrollView.maximumZoomScale - 1.0) <= CGFloat.leastNormalMagnitude else {
 | |
|             return
 | |
|         }
 | |
|         
 | |
|         // Ensure bounds have been computed
 | |
|         guard mediaMessageView.bounds.width > 0, mediaMessageView.bounds.height > 0 else {
 | |
|             Logger.warn("bad bounds")
 | |
|             return
 | |
|         }
 | |
| 
 | |
|         let widthScale: CGFloat = (view.bounds.size.width / mediaMessageView.bounds.width)
 | |
|         let heightScale: CGFloat = (view.bounds.size.height / mediaMessageView.bounds.height)
 | |
|         let minScale: CGFloat = min(widthScale, heightScale)
 | |
| 
 | |
|         scrollView.minimumZoomScale = minScale
 | |
|         scrollView.maximumZoomScale = (minScale * 5)
 | |
|         scrollView.zoomScale = minScale
 | |
|     }
 | |
|     
 | |
|     // Allow the user to zoom out to 100% of the attachment size if it's smaller
 | |
|     // than the screen
 | |
|     fileprivate func resetContentInset() {
 | |
|         // If the content isn't zoomable then inset the content so it appears centered
 | |
|         guard isZoomable else {
 | |
|             scrollView.contentInset = UIEdgeInsets(
 | |
|                 top: -AttachmentPrepViewController.verticalCenterOffset,
 | |
|                 leading: 0,
 | |
|                 bottom: 0,
 | |
|                 trailing: 0
 | |
|             )
 | |
|             return
 | |
|         }
 | |
|         
 | |
|         let offsetX: CGFloat = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
 | |
|         let offsetY: CGFloat = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
 | |
|         
 | |
|         scrollView.contentInset = UIEdgeInsets(
 | |
|             top: offsetY - AttachmentPrepViewController.verticalCenterOffset,
 | |
|             left: offsetX,
 | |
|             bottom: 0,
 | |
|             right: 0
 | |
|         )
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: -
 | |
| 
 | |
| extension AttachmentPrepViewController: ImageEditorViewDelegate {
 | |
|     public func imageEditor(presentFullScreenView viewController: UIViewController,
 | |
|                             isTransparent: Bool) {
 | |
| 
 | |
|         let navigationController = OWSNavigationController(rootViewController: viewController)
 | |
|         navigationController.modalPresentationStyle = (isTransparent
 | |
|             ? .overFullScreen
 | |
|             : .fullScreen)
 | |
|         navigationController.ows_prefersStatusBarHidden = true
 | |
|         navigationController.view.backgroundColor = Colors.navigationBarBackground
 | |
| 
 | |
|         if let navigationBar = navigationController.navigationBar as? OWSNavigationBar {
 | |
|             navigationBar.overrideTheme(type: .clear)
 | |
|         } else {
 | |
|             owsFailDebug("navigationBar was nil or unexpected class")
 | |
|         }
 | |
| 
 | |
|         self.present(navigationController, animated: false) {
 | |
|             // Do nothing.
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public func imageEditorUpdateNavigationBar() {
 | |
|         prepDelegate?.prepViewControllerUpdateNavigationBar()
 | |
|     }
 | |
| 
 | |
|     public func imageEditorUpdateControls() {
 | |
|         prepDelegate?.prepViewControllerUpdateControls()
 | |
|     }
 | |
| }
 |