237 lines
10 KiB
Swift
237 lines
10 KiB
Swift
//
|
|
// TopNotificationHandler.swift
|
|
// MVMCoreUI
|
|
//
|
|
// Created by Scott Pfeil on 4/11/23.
|
|
// Copyright © 2023 Verizon Wireless. All rights reserved.
|
|
//
|
|
|
|
import MVMCore
|
|
|
|
public class TopNotificationHandler {
|
|
|
|
/// The operation queue of alert operations.
|
|
private var queue = OperationQueue()
|
|
|
|
/// Returns the action handler stored in the CoreUIObject
|
|
public static func shared() -> Self {
|
|
return MVMCoreActionUtility.fatalClassCheck(object: CoreUIObject.sharedInstance()?.topNotificationHandler)
|
|
}
|
|
|
|
init() {
|
|
registerWithNotificationCenter()
|
|
registerForPageChanges()
|
|
}
|
|
|
|
// MARK: - JSON Handling
|
|
|
|
/// Registers with the notification center to know when json is updated.
|
|
private func registerWithNotificationCenter() {
|
|
NotificationCenter.default.addObserver(self, selector: #selector(responseJSONUpdated(notification:)), name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil)
|
|
}
|
|
|
|
/// Registers to know when pages change.
|
|
private func registerForPageChanges() {
|
|
MVMCoreNavigationHandler.shared()?.addDelegate(self)
|
|
}
|
|
|
|
private func getDelegateObject() -> MVMCoreUIDelegateObject? {
|
|
// TODO: Top alert view is current delegate. Should move to current view controller eventually?
|
|
guard let alertView = MVMCoreUISplitViewController.main()?.topAlertView else { return nil }
|
|
return MVMCoreUIDelegateObject.create(withDelegateForAll: alertView)
|
|
}
|
|
|
|
/// Checks for new top alert json
|
|
@objc private func responseJSONUpdated(notification: Notification) {
|
|
guard let loadObject = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject) else { return }
|
|
|
|
// Dismiss any top alerts that server wants us to dismiss/
|
|
if let disableType = loadObject.responseInfoMap?.optionalStringForKey("disableType") {
|
|
TopNotificationHandler.shared().hideTopAlertView(of: disableType)
|
|
}
|
|
|
|
// Show any new top alert.
|
|
guard let responseJSON = loadObject.responseJSON,
|
|
let json = responseJSON.optionalDictionaryForKey(KeyTopAlert) else { return }
|
|
showTopNotification(with: json)
|
|
}
|
|
|
|
/// Decodes the json into a TopNotificationModel
|
|
public func decodeTopNotification(with json: [AnyHashable: Any], delegateObject: MVMCoreUIDelegateObject?) -> TopNotificationModel? {
|
|
do {
|
|
return try TopNotificationModel.decode(json: json, delegateObject: delegateObject)
|
|
} catch {
|
|
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "\(self)") {
|
|
MVMCoreUILoggingHandler.addError(toLog: errorObject)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// MARK: - Operation Handling
|
|
|
|
private func add(operation: MVMCoreTopAlertOperation) {
|
|
operation.completionBlock = { [weak self] in
|
|
// If the alert was cancelled to show another with higher priority, re-add to the operation when cancelled to the queue.
|
|
if operation.reAddAfterCancel {
|
|
let newOperation: MVMCoreTopAlertOperation = operation.copy() as! MVMCoreTopAlertOperation
|
|
newOperation.reAddAfterCancel = false
|
|
self?.add(operation: newOperation)
|
|
}
|
|
operation.completionBlock = nil
|
|
}
|
|
|
|
let currentPageType = (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType
|
|
operation.updateDisplayable(byPageType: currentPageType)
|
|
|
|
queue.addOperation(operation)
|
|
reevaluteQueue()
|
|
}
|
|
|
|
/// Checks for existing top alert object of same type and updates it. Only happens for molecular top alerts. Returns true if we updated.
|
|
private func checkAndUpdateExisting(with topAlertObject: MVMCoreTopAlertObject) -> Bool {
|
|
for case let operation as MVMCoreTopAlertOperation in queue.operations {
|
|
guard topAlertObject.json != nil,
|
|
operation.topAlertObject.type == topAlertObject.type else { continue }
|
|
operation.update(with: topAlertObject)
|
|
let pageType = (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType
|
|
operation.updateDisplayable(byPageType: pageType)
|
|
reevaluteQueue()
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// Re-evaluates the queue operations
|
|
private func reevaluteQueue() {
|
|
var highestReadyOperation: MVMCoreTopAlertOperation?
|
|
var executingOperation: MVMCoreTopAlertOperation?
|
|
for case let operation as MVMCoreTopAlertOperation in queue.operations {
|
|
guard !operation.isCancelled,
|
|
!operation.isFinished else { continue }
|
|
if operation.isReady,
|
|
highestReadyOperation == nil || operation.queuePriority.rawValue > highestReadyOperation!.queuePriority.rawValue {
|
|
highestReadyOperation = operation
|
|
}
|
|
if operation.isExecuting {
|
|
executingOperation = operation
|
|
}
|
|
}
|
|
guard let currentOperation = executingOperation else { return }
|
|
|
|
// Cancel the executing operation if it is no longer ready to run. Re-add for later if it is persistent.
|
|
guard currentOperation.isReady else {
|
|
currentOperation.reAddAfterCancel = currentOperation.topAlertObject.persistent
|
|
currentOperation.cancel()
|
|
return
|
|
}
|
|
|
|
// If the highest priority operation is not executing, and the executing operation is persistent, cancel it.
|
|
if let newOperation = highestReadyOperation,
|
|
currentOperation != newOperation,
|
|
currentOperation.topAlertObject.persistent {
|
|
currentOperation.reAddAfterCancel = true
|
|
currentOperation.cancel()
|
|
}
|
|
}
|
|
|
|
// MARK: - Show and hide
|
|
|
|
public func isTopAlertShowing() -> Bool {
|
|
return queue.operations.first(where: { operation in
|
|
return operation.isExecuting
|
|
}) != nil
|
|
}
|
|
|
|
public func hasPersistentTopAlert(of type: String) -> Bool {
|
|
return queue.operations.first(where: { operation in
|
|
guard operation.isExecuting,
|
|
let operation = operation as? MVMCoreTopAlertOperation else { return false }
|
|
return operation.topAlertObject.persistent && operation.topAlertObject.type == type
|
|
}) as? MVMCoreTopAlertOperation == nil
|
|
}
|
|
|
|
/// Shows the top alert with the json.
|
|
func showTopNotification(with json: [AnyHashable: Any]) {
|
|
guard let model = decodeTopNotification(with: json, delegateObject: getDelegateObject()) else { return }
|
|
showTopNotification(with: model)
|
|
}
|
|
|
|
/// Shows the top notification with the model.
|
|
func showTopNotification(with model: TopNotificationModel) {
|
|
let object = model.createTopAlertObject()
|
|
guard !checkAndUpdateExisting(with: object),
|
|
let operation = MVMCoreTopAlertOperation(topAlertObject: object) else { return }
|
|
TopNotificationHandler.shared().add(operation: operation)
|
|
}
|
|
|
|
/// Show the top alert with the legacy object.
|
|
public func showTopAlert(with topAlertObject: MVMCoreTopAlertObject) {
|
|
let alertOperation = MVMCoreTopAlertOperation(topAlertObject: topAlertObject)!
|
|
add(operation: alertOperation)
|
|
}
|
|
|
|
/// Cancel the current top alert view.
|
|
public func hideTopAlertView() {
|
|
guard let currentOperation = queue.operations.first(where: { operation in
|
|
return operation.isExecuting
|
|
}) as? MVMCoreTopAlertOperation else { return }
|
|
currentOperation.topAlertObject.persistent = false
|
|
currentOperation.reAddAfterCancel = false
|
|
currentOperation.cancel()
|
|
}
|
|
|
|
/// Cancel all operations of this type.
|
|
public func hideTopAlertView(of type: String) {
|
|
for operation in queue.operations {
|
|
guard let operation = operation as? MVMCoreTopAlertOperation,
|
|
operation.topAlertObject.type == type else { continue }
|
|
operation.reAddAfterCancel = false
|
|
operation.cancel()
|
|
}
|
|
}
|
|
|
|
/// Cancel all persistent operations of this type.
|
|
public func hidePersistentTopAlertView(of type: String) {
|
|
for operation in queue.operations {
|
|
guard let operation = operation as? MVMCoreTopAlertOperation,
|
|
operation.topAlertObject.persistent,
|
|
operation.topAlertObject.type == type else { continue }
|
|
operation.reAddAfterCancel = false
|
|
operation.cancel()
|
|
}
|
|
}
|
|
|
|
/// Finds an cancels top alerts associated with the object.
|
|
public func removeTopAlert(for object: MVMCoreTopAlertObject) {
|
|
for operation in queue.operations {
|
|
guard let operation = operation as? MVMCoreTopAlertOperation,
|
|
operation.topAlertObject === object else { return }
|
|
operation.reAddAfterCancel = false
|
|
operation.cancel()
|
|
}
|
|
}
|
|
|
|
public func removeAllTopAlerts() {
|
|
queue.cancelAllOperations()
|
|
}
|
|
}
|
|
|
|
extension TopNotificationHandler: MVMCorePresentationDelegateProtocol {
|
|
// Update displayable for each top alert operation when page type changes, in top queue priority order.
|
|
public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) {
|
|
guard queue.operations.count > 0 else { return }
|
|
let viewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController)
|
|
guard viewController == MVMCoreUISplitViewController.main()?.getCurrentViewController() else { return }
|
|
let pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType
|
|
queue.operations.compactMap {
|
|
$0 as? MVMCoreTopAlertOperation
|
|
}.sorted {
|
|
$0.queuePriority.rawValue > $1.queuePriority.rawValue
|
|
}.forEach {
|
|
$0.updateDisplayable(byPageType: pageType)
|
|
}
|
|
reevaluteQueue()
|
|
}
|
|
}
|