Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/src/ViewControllers/Photos/PhotoCaptureDismiss.swift
1 views
//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import SignalServiceKit
import SignalUI

class PhotoCaptureInteractiveDismiss: UIPercentDrivenInteractiveTransition {
    var interactionInProgress = false

    weak var interactiveDismissDelegate: InteractiveDismissDelegate?
    let handlesAnimation: Bool

    init(viewController: UIViewController, handlesAnimation: Bool = true) {
        self.viewController = viewController
        self.handlesAnimation = handlesAnimation
        super.init()
    }

    func addGestureRecognizer(to view: UIView) {
        let gesture = DirectionalPanGestureRecognizer(
            direction: .vertical,
            target: self,
            action: #selector(handleGesture(_:)),
        )
        view.addGestureRecognizer(gesture)
    }

    // MARK: - Private

    private var initialDimissFrame: CGRect?
    private weak var viewController: UIViewController?
    private var farEnoughToCompleteTransition = false

    private var shouldCompleteTransition: Bool {
        if farEnoughToCompleteTransition {
            return true
        }

        return false
    }

    @objc
    private func handleGesture(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
        guard let coordinateSpace = gestureRecognizer.view?.superview else {
            owsFailDebug("coordinateSpace was unexpectedly nil")
            return
        }

        if case .began = gestureRecognizer.state {
            initialDimissFrame = self.viewController?.view.frame
            gestureRecognizer.setTranslation(.zero, in: coordinateSpace)
        }

        let distanceToTriggerDismiss: CGFloat = 200

        switch gestureRecognizer.state {
        case .began:
            interactionInProgress = true
            interactiveDismissDelegate?.interactiveDismissDidBegin(self)

        case .changed:
            let offset = gestureRecognizer.translation(in: coordinateSpace)
            let progress = offset.y / distanceToTriggerDismiss
            // `farEnoughToCompleteTransition` is cancelable if the user reverses direction
            farEnoughToCompleteTransition = progress >= 1
            update(progress)

            interactiveDismissDelegate?.interactiveDismiss(self, didChangeProgress: progress, touchOffset: offset)
            if handlesAnimation {
                guard let frame = initialDimissFrame else { return }
                let delta = self.constainSwipe(offset: offset)
                viewController?.view.center = frame.offsetBy(dx: delta.x, dy: delta.y).center
            }

        case .cancelled:
            cancel()
            interactiveDismissDelegate?.interactiveDismissDidCancel(self)

            initialDimissFrame = nil
            interactionInProgress = false
            farEnoughToCompleteTransition = false

        case .ended:
            if shouldCompleteTransition {
                finish()
                interactiveDismissDelegate?.interactiveDismissDidFinish(self)
            } else {
                cancel()
                interactiveDismissDelegate?.interactiveDismissDidCancel(self)
                if handlesAnimation {
                    guard let frame = initialDimissFrame else { return }
                    UIView.animate(withDuration: 0.1, animations: {
                        self.viewController?.view.frame = frame
                    })
                }
            }

            initialDimissFrame = nil
            interactionInProgress = false
            farEnoughToCompleteTransition = false

        default:
            break
        }
    }

    private func constainSwipe(offset: CGPoint) -> CGPoint {
        // Don't allow the swipe to move the view upwards off the screen
        var y = offset.y
        if y < 0 {
            y = 0
        }
        return CGPoint(x: 0, y: y)
    }
}