Gemini/Gemini-iOS/BrowserNavigationController...

194 lines
7.7 KiB
Swift

//
// BrowserNavigationController.swift
// Gemini-iOS
//
// Created by Shadowfacts on 12/17/20.
//
import UIKit
import BrowserCore
import Combine
class BrowserNavigationController: UIViewController {
let navigator: NavigationManager
private var backBrowserVCs = [BrowserWebViewController]()
private var forwardBrowserVCs = [BrowserWebViewController]()
private var currentBrowserVC: BrowserWebViewController!
private var gestureState: GestureState?
private var cancellables = [AnyCancellable]()
init(navigator: NavigationManager) {
self.navigator = navigator
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
currentBrowserVC = BrowserWebViewController(navigator: navigator, url: navigator.currentURL)
embedChild(currentBrowserVC)
navigator.navigationOperation
.sink(receiveValue: self.onNavigate)
.store(in: &cancellables)
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized)))
}
private func onNavigate(_ operation: NavigationManager.Operation) {
let newVC: BrowserWebViewController
switch operation {
case .go:
backBrowserVCs.append(currentBrowserVC)
newVC = BrowserWebViewController(navigator: navigator, url: navigator.currentURL)
case let .backward(count: count):
var removed = backBrowserVCs.suffix(count)
backBrowserVCs.removeLast(count)
forwardBrowserVCs.insert(currentBrowserVC, at: 0)
newVC = removed.removeFirst()
forwardBrowserVCs.insert(contentsOf: removed, at: 0)
case let .forward(count: count):
var removed = forwardBrowserVCs.prefix(count)
forwardBrowserVCs.removeFirst(count)
backBrowserVCs.append(currentBrowserVC)
newVC = removed.removeFirst()
backBrowserVCs.append(contentsOf: removed)
}
currentBrowserVC.removeViewAndController()
currentBrowserVC = newVC
embedChild(newVC)
}
private let startEdgeNavigationSwipeDistance: CGFloat = 75
private let finishEdgeNavigationVelocityThreshold: CGFloat = 500
private let edgeNavigationMaxDimmingAlpha: CGFloat = 0.35
private let edgeNavigationParallaxFactor: CGFloat = 0.25
private let totalEdgeNavigationTime: TimeInterval = 0.4
@objc private func panGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
let location = recognizer.location(in: view)
let velocity = recognizer.velocity(in: view)
switch recognizer.state {
case .began:
if location.x < startEdgeNavigationSwipeDistance && velocity.x > 0 && navigator.backStack.count > 0 {
let older = backBrowserVCs.last ?? BrowserWebViewController(navigator: navigator, url: navigator.backStack.last!)
embedChild(older)
older.view.layer.zPosition = -2
older.view.transform = CGAffineTransform(translationX: -1 * edgeNavigationParallaxFactor * view.bounds.width, y: 0)
let dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = .black
dimmingView.layer.zPosition = -1
dimmingView.alpha = edgeNavigationMaxDimmingAlpha
view.embedSubview(dimmingView)
let animator = UIViewPropertyAnimator(duration: totalEdgeNavigationTime, curve: .easeInOut) {
dimmingView.alpha = 0
older.view.transform = .identity
self.currentBrowserVC.view.transform = CGAffineTransform(translationX: self.view.bounds.width, y: 0)
}
animator.addCompletion { (position) in
dimmingView.removeFromSuperview()
older.view.transform = .identity
older.view.layer.zPosition = 0
older.removeViewAndController()
self.currentBrowserVC.view.transform = .identity
if position == .end {
self.navigator.back()
}
}
gestureState = .backwards(animator)
} else if location.x > view.bounds.width - startEdgeNavigationSwipeDistance && velocity.x < 0 && navigator.forwardStack.count > 0 {
let newer = forwardBrowserVCs.first ?? BrowserWebViewController(navigator: navigator, url: navigator.backStack.first!)
embedChild(newer)
newer.view.transform = CGAffineTransform(translationX: view.bounds.width, y: 0)
newer.view.layer.zPosition = 2
let dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = .black
dimmingView.layer.zPosition = 1
dimmingView.alpha = 0
view.embedSubview(dimmingView)
let animator = UIViewPropertyAnimator(duration: totalEdgeNavigationTime, curve: .easeInOut) {
dimmingView.alpha = self.edgeNavigationMaxDimmingAlpha
newer.view.transform = .identity
self.currentBrowserVC.view.transform = CGAffineTransform(translationX: -1 * self.edgeNavigationParallaxFactor * self.view.bounds.width, y: 0)
}
animator.addCompletion { (position) in
dimmingView.removeFromSuperview()
newer.removeViewAndController()
newer.view.layer.zPosition = 0
newer.view.transform = .identity
self.currentBrowserVC.view.transform = .identity
if position == .end {
self.navigator.forward()
}
}
gestureState = .forwards(animator)
}
case .changed:
let translation = recognizer.translation(in: view)
switch gestureState {
case let .backwards(animator):
animator.fractionComplete = translation.x / view.bounds.width
case let .forwards(animator):
animator.fractionComplete = abs(translation.x) / view.bounds.width
case nil:
break
}
case .ended, .cancelled:
switch gestureState {
case let .backwards(animator):
let shouldComplete = location.x > view.bounds.width / 2 || velocity.x > finishEdgeNavigationVelocityThreshold
animator.isReversed = !shouldComplete
animator.startAnimation()
case let .forwards(animator):
let shouldComplete = location.x < view.bounds.width / 2 || velocity.x < -finishEdgeNavigationVelocityThreshold
animator.isReversed = !shouldComplete
animator.startAnimation()
case nil:
break
}
gestureState = nil
default:
return
}
}
}
extension BrowserNavigationController {
enum GestureState {
case backwards(UIViewPropertyAnimator)
case forwards(UIViewPropertyAnimator)
}
}