Files
OrderScheduling/Pods/SwiftEntryKit/Source/Infra/EKRootViewController.swift
DDIsFriend f0e8a1709d initial
2023-08-18 17:28:57 +08:00

269 lines
8.2 KiB
Swift

//
// EntryViewController.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/19/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import UIKit
protocol EntryPresenterDelegate: AnyObject {
var isResponsiveToTouches: Bool { set get }
func displayPendingEntryOrRollbackWindow(dismissCompletionHandler: SwiftEntryKit.DismissCompletionHandler?)
}
class EKRootViewController: UIViewController {
// MARK: - Props
private unowned let delegate: EntryPresenterDelegate
private var lastAttributes: EKAttributes!
private let backgroundView = EKBackgroundView()
private lazy var wrapperView: EKWrapperView = {
return EKWrapperView()
}()
/*
Count the total amount of currently displaying entries,
meaning, total subviews less one - the backgorund of the entry
*/
fileprivate var displayingEntryCount: Int {
return view.subviews.count - 1
}
fileprivate var isDisplaying: Bool {
return lastEntry != nil
}
private var lastEntry: EKContentView? {
return view.subviews.last as? EKContentView
}
private var isResponsive = false {
didSet {
wrapperView.isAbleToReceiveTouches = isResponsive
delegate.isResponsiveToTouches = isResponsive
}
}
override var shouldAutorotate: Bool {
if lastAttributes == nil {
return true
}
return lastAttributes.positionConstraints.rotation.isEnabled
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
guard let lastAttributes = lastAttributes else {
return super.supportedInterfaceOrientations
}
switch lastAttributes.positionConstraints.rotation.supportedInterfaceOrientations {
case .standard:
return super.supportedInterfaceOrientations
case .all:
return .all
}
}
// Previous status bar style
private let previousStatusBar: EKAttributes.StatusBar
private var statusBar: EKAttributes.StatusBar? = nil {
didSet {
if let statusBar = statusBar, ![statusBar, oldValue].contains(.ignored) {
UIApplication.shared.set(statusBarStyle: statusBar)
}
}
}
override var preferredStatusBarStyle: UIStatusBarStyle {
if [previousStatusBar, statusBar].contains(.ignored) {
return super.preferredStatusBarStyle
}
return statusBar?.appearance.style ?? previousStatusBar.appearance.style
}
override var prefersStatusBarHidden: Bool {
if [previousStatusBar, statusBar].contains(.ignored) {
return super.prefersStatusBarHidden
}
return !(statusBar?.appearance.visible ?? previousStatusBar.appearance.visible)
}
// MARK: - Lifecycle
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public init(with delegate: EntryPresenterDelegate) {
self.delegate = delegate
previousStatusBar = .currentStatusBar
super.init(nibName: nil, bundle: nil)
}
override public func loadView() {
view = wrapperView
view.insertSubview(backgroundView, at: 0)
backgroundView.isUserInteractionEnabled = false
backgroundView.fillSuperview()
}
override public func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
statusBar = previousStatusBar
}
// Set status bar
func setStatusBarStyle(for attributes: EKAttributes) {
statusBar = attributes.statusBar
}
// MARK: - Setup
func configure(entryView: EKEntryView) {
// In case the entry is a view controller, add the entry as child of root
if let viewController = entryView.content.viewController {
addChild(viewController)
}
// Extract the attributes struct
let attributes = entryView.attributes
// Assign attributes
let previousAttributes = lastAttributes
// Remove the last entry
removeLastEntry(lastAttributes: previousAttributes, keepWindow: true)
lastAttributes = attributes
let entryContentView = EKContentView(withEntryDelegate: self)
view.addSubview(entryContentView)
entryContentView.setup(with: entryView)
switch attributes.screenInteraction.defaultAction {
case .forward:
isResponsive = false
default:
isResponsive = true
}
if previousAttributes?.statusBar != attributes.statusBar {
setNeedsStatusBarAppearanceUpdate()
}
if shouldAutorotate {
UIViewController.attemptRotationToDeviceOrientation()
}
}
// Check priority precedence for a given entry
func canDisplay(attributes: EKAttributes) -> Bool {
guard let lastAttributes = lastAttributes else {
return true
}
return attributes.precedence.priority >= lastAttributes.precedence.priority
}
// Removes last entry - can keep the window 'ON' if necessary
private func removeLastEntry(lastAttributes: EKAttributes?, keepWindow: Bool) {
guard let attributes = lastAttributes else {
return
}
if attributes.popBehavior.isOverriden {
lastEntry?.removePromptly()
} else {
popLastEntry()
}
}
// Make last entry exit using exitAnimation - animatedly
func animateOutLastEntry(completionHandler: SwiftEntryKit.DismissCompletionHandler? = nil) {
lastEntry?.dismissHandler = completionHandler
lastEntry?.animateOut(pushOut: false)
}
// Pops last entry (using pop animation) - animatedly
func popLastEntry() {
lastEntry?.animateOut(pushOut: true)
}
}
// MARK: - UIResponder
extension EKRootViewController {
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
switch lastAttributes.screenInteraction.defaultAction {
case .dismissEntry:
lastEntry?.animateOut(pushOut: false)
fallthrough
default:
lastAttributes.screenInteraction.customTapActions.forEach { $0() }
}
}
}
// MARK: - EntryScrollViewDelegate
extension EKRootViewController: EntryContentViewDelegate {
func didFinishDisplaying(entry: EKEntryView, keepWindowActive: Bool, dismissCompletionHandler: SwiftEntryKit.DismissCompletionHandler?) {
guard !isDisplaying else {
return
}
guard !keepWindowActive else {
return
}
delegate.displayPendingEntryOrRollbackWindow(dismissCompletionHandler: dismissCompletionHandler)
}
func changeToInactive(withAttributes attributes: EKAttributes, pushOut: Bool) {
guard displayingEntryCount <= 1 else {
return
}
let clear = {
let style = EKBackgroundView.Style(background: .clear, displayMode: attributes.displayMode)
self.changeBackground(to: style, duration: attributes.exitAnimation.totalDuration)
}
guard pushOut else {
clear()
return
}
guard let lastBackroundStyle = lastAttributes?.screenBackground else {
clear()
return
}
if lastBackroundStyle != attributes.screenBackground {
clear()
}
}
func changeToActive(withAttributes attributes: EKAttributes) {
let style = EKBackgroundView.Style(background: attributes.screenBackground,
displayMode: attributes.displayMode)
changeBackground(to: style, duration: attributes.entranceAnimation.totalDuration)
}
private func changeBackground(to style: EKBackgroundView.Style, duration: TimeInterval) {
DispatchQueue.main.async {
UIView.animate(withDuration: duration, delay: 0, options: [], animations: {
self.backgroundView.style = style
}, completion: nil)
}
}
}