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

232 lines
8.9 KiB
Swift

//
// EKWindowProvider.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/19/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import UIKit
final class EKWindowProvider: EntryPresenterDelegate {
/** The artificial safe area insets */
static var safeAreaInsets: UIEdgeInsets {
if #available(iOS 11.0, *) {
return EKWindowProvider.shared.entryWindow?.rootViewController?.view?.safeAreaInsets ?? UIApplication.shared.keyWindow?.rootViewController?.view.safeAreaInsets ?? .zero
} else {
let statusBarMaxY = UIApplication.shared.statusBarFrame.maxY
return UIEdgeInsets(top: statusBarMaxY, left: 0, bottom: 10, right: 0)
}
}
/** Single access point */
static let shared = EKWindowProvider()
/** Current entry window */
var entryWindow: EKWindow!
/** Returns the root view controller if it is instantiated */
var rootVC: EKRootViewController? {
return entryWindow?.rootViewController as? EKRootViewController
}
/** A window to go back to when the last entry has been dismissed */
private var rollbackWindow: SwiftEntryKit.RollbackWindow!
/** The main rollback window to be used internally in case `rollbackWindow`'s value is `.main` */
private weak var mainRollbackWindow: UIWindow?
/** Entry queueing heuristic */
private let entryQueue = EKAttributes.Precedence.QueueingHeuristic.value.heuristic
private weak var entryView: EKEntryView!
/** Cannot be instantiated, customized, inherited */
private init() {}
var isResponsiveToTouches: Bool {
set {
entryWindow.isAbleToReceiveTouches = newValue
}
get {
return entryWindow.isAbleToReceiveTouches
}
}
// MARK: - Setup and Teardown methods
// Prepare the window and the host view controller
private func prepare(for attributes: EKAttributes, presentInsideKeyWindow: Bool) -> EKRootViewController? {
let entryVC = setupWindowAndRootVC()
guard entryVC.canDisplay(attributes: attributes) || attributes.precedence.isEnqueue else {
return nil
}
entryVC.setStatusBarStyle(for: attributes)
entryWindow.windowLevel = attributes.windowLevel.value
if presentInsideKeyWindow {
entryWindow.makeKeyAndVisible()
} else {
entryWindow.isHidden = false
}
return entryVC
}
/** Boilerplate generic setup for entry-window and root-view-controller */
private func setupWindowAndRootVC() -> EKRootViewController {
let entryVC: EKRootViewController
if entryWindow == nil {
entryVC = EKRootViewController(with: self)
entryWindow = EKWindow(with: entryVC)
mainRollbackWindow = UIApplication.shared.keyWindow
} else {
entryVC = rootVC!
}
return entryVC
}
/**
Privately used to display an entry
*/
private func display(entryView: EKEntryView, using attributes: EKAttributes, presentInsideKeyWindow: Bool, rollbackWindow: SwiftEntryKit.RollbackWindow) {
switch entryView.attributes.precedence {
case .override(priority: _, dropEnqueuedEntries: let dropEnqueuedEntries):
if dropEnqueuedEntries {
entryQueue.removeAll()
}
show(entryView: entryView, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
case .enqueue where isCurrentlyDisplaying():
entryQueue.enqueue(entry: .init(view: entryView, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow))
case .enqueue:
show(entryView: entryView, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
}
}
// MARK: - Exposed Actions
func queueContains(entryNamed name: String? = nil) -> Bool {
if name == nil && !entryQueue.isEmpty {
return true
}
if let name = name {
return entryQueue.contains(entryNamed: name)
} else {
return false
}
}
/**
Returns *true* if the currently displayed entry has the given name.
In case *name* has the value of *nil*, the result is *true* if any entry is currently displayed.
*/
func isCurrentlyDisplaying(entryNamed name: String? = nil) -> Bool {
guard let entryView = entryView else {
return false
}
if let name = name { // Test for names equality
return entryView.content.attributes.name == name
} else { // Return true by default if the name is *nil*
return true
}
}
/** Transform current entry to view */
func transform(to view: UIView) {
entryView?.transform(to: view)
}
/** Display a view using attributes */
func display(view: UIView, using attributes: EKAttributes, presentInsideKeyWindow: Bool, rollbackWindow: SwiftEntryKit.RollbackWindow) {
let entryView = EKEntryView(newEntry: .init(view: view, attributes: attributes))
display(entryView: entryView, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
}
/** Display a view controller using attributes */
func display(viewController: UIViewController, using attributes: EKAttributes, presentInsideKeyWindow: Bool, rollbackWindow: SwiftEntryKit.RollbackWindow) {
let entryView = EKEntryView(newEntry: .init(viewController: viewController, attributes: attributes))
display(entryView: entryView, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
}
/** Clear all entries immediately and display to the rollback window */
func displayRollbackWindow() {
if #available(iOS 13.0, *) {
entryWindow.windowScene = nil
}
entryWindow = nil
entryView = nil
switch rollbackWindow! {
case .main:
if let mainRollbackWindow = mainRollbackWindow {
mainRollbackWindow.makeKeyAndVisible()
} else {
UIApplication.shared.keyWindow?.makeKeyAndVisible()
}
case .custom(window: let window):
window.makeKeyAndVisible()
}
}
/** Display a pending entry if there is any inside the queue */
func displayPendingEntryOrRollbackWindow(dismissCompletionHandler: SwiftEntryKit.DismissCompletionHandler?) {
if let next = entryQueue.dequeue() {
// Execute dismiss handler if needed before dequeuing (potentially) another entry
dismissCompletionHandler?()
// Show the next entry in queue
show(entryView: next.view, presentInsideKeyWindow: next.presentInsideKeyWindow, rollbackWindow: next.rollbackWindow)
} else {
// Display the rollback window
displayRollbackWindow()
// As a last step, invoke the dismissal method
dismissCompletionHandler?()
}
}
/** Dismiss entries according to a given descriptor */
func dismiss(_ descriptor: SwiftEntryKit.EntryDismissalDescriptor, with completion: SwiftEntryKit.DismissCompletionHandler? = nil) {
guard let rootVC = rootVC else {
return
}
switch descriptor {
case .displayed:
rootVC.animateOutLastEntry(completionHandler: completion)
case .specific(entryName: let name):
entryQueue.removeEntries(by: name)
if entryView?.attributes.name == name {
rootVC.animateOutLastEntry(completionHandler: completion)
}
case .prioritizedLowerOrEqualTo(priority: let priorityThreshold):
entryQueue.removeEntries(withPriorityLowerOrEqualTo: priorityThreshold)
if let currentPriority = entryView?.attributes.precedence.priority, currentPriority <= priorityThreshold {
rootVC.animateOutLastEntry(completionHandler: completion)
}
case .enqueued:
entryQueue.removeAll()
case .all:
entryQueue.removeAll()
rootVC.animateOutLastEntry(completionHandler: completion)
}
}
/** Layout the view-hierarchy rooted in the window */
func layoutIfNeeded() {
entryWindow?.layoutIfNeeded()
}
/** Privately used to prepare the root view controller and show the entry immediately */
private func show(entryView: EKEntryView, presentInsideKeyWindow: Bool, rollbackWindow: SwiftEntryKit.RollbackWindow) {
guard let entryVC = prepare(for: entryView.attributes, presentInsideKeyWindow: presentInsideKeyWindow) else {
return
}
entryVC.configure(entryView: entryView)
self.entryView = entryView
self.rollbackWindow = rollbackWindow
}
}