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.
		
		
		
		
		
			
		
			
				
	
	
		
			556 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			556 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Swift
		
	
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | 
						|
 | 
						|
import Foundation
 | 
						|
import UIKit
 | 
						|
import AVFoundation
 | 
						|
import SessionUIKit
 | 
						|
import SignalCoreKit
 | 
						|
 | 
						|
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)
 | 
						|
    )
 | 
						|
    
 | 
						|
    public 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()
 | 
						|
        
 | 
						|
        navigationItem.backButtonTitle = ""
 | 
						|
        view.themeBackgroundColor = .newConversation_background
 | 
						|
 | 
						|
        view.addSubview(contentContainerView)
 | 
						|
        
 | 
						|
        contentContainerView.addSubview(scrollView)
 | 
						|
        scrollView.addSubview(mediaMessageView)
 | 
						|
        
 | 
						|
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(screenTapped))
 | 
						|
        mediaMessageView.addGestureRecognizer(tapGesture)
 | 
						|
        
 | 
						|
        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 func screenTapped() {
 | 
						|
        self.view.window?.endEditing(true)
 | 
						|
    }
 | 
						|
 | 
						|
    @objc public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) {
 | 
						|
        self.view.window?.endEditing(true)
 | 
						|
        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
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    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 = StyledNavigationController(rootViewController: viewController)
 | 
						|
        navigationController.modalPresentationStyle = (isTransparent ?
 | 
						|
            .overFullScreen :
 | 
						|
            .fullScreen
 | 
						|
        )
 | 
						|
        
 | 
						|
        self.present(navigationController, animated: false, completion: nil)
 | 
						|
    }
 | 
						|
 | 
						|
    public func imageEditorUpdateNavigationBar() {
 | 
						|
        prepDelegate?.prepViewControllerUpdateNavigationBar()
 | 
						|
    }
 | 
						|
 | 
						|
    public func imageEditorUpdateControls() {
 | 
						|
        prepDelegate?.prepViewControllerUpdateControls()
 | 
						|
    }
 | 
						|
}
 |