// // AlertOperation.swift // MVMCoreUI // // Created by Scott Pfeil on 4/11/23. // Copyright © 2023 Verizon Wireless. All rights reserved. // import MVMCore import Dispatch import Combine public class AlertOperation: MVMCoreOperation { private actor Properties { private var isDisplayed: Bool = false func set(displayed: Bool) { isDisplayed = displayed } func getIsDisplayed() -> Bool { return isDisplayed } } private var properties = Properties() public let alertController: AlertController public let alertObject: AlertObject /// For tracking isVisible changes of the alert controller. private var cancellable: Cancellable? /// Blocks the navigation queue to ensure no other navigation happens while an alert is displayed. private var blockingOperation: MVMCoreOperation? public init(with alert: AlertController, alertObject: AlertObject) { self.alertController = alert self.alertObject = alertObject super.init() } deinit { stopObservingAlertView() } public override func main() { guard !checkAndHandleForCancellation() else { return } // Observe for when it is removed. observeForCurrentAlertViewDismissal() Task(priority: .high) { guard let viewControllerToPresentOn = await NavigationHandler.shared().getViewControllerToPresentOn() else { markAsFinished() return } // Presents the alert. let presentationOperation = await NavigationOperation(with: .present(viewController: alertController, onController: viewControllerToPresentOn), tryToReplace: false) let blockingOperation = MVMCoreOperation() blockingOperation.addDependency(presentationOperation) self.blockingOperation = blockingOperation // Block other navigation until this alert is removed. NavigationHandler.shared().navigationQueue.addOperation(blockingOperation) await NavigationHandler.shared().navigate(with: presentationOperation) // We finished but it was not displayed yet. It's possible that it was cancelled. Finish this task if await !self.properties.getIsDisplayed() { self.markAsFinished() } else { (MVMCoreObject.sharedInstance()?.loggingDelegate as? MVMCoreUILoggingDelegateProtocol)?.logAlert(with: self.alertObject) if self.isCancelled { await self.dismissAlertView() } } } } public override func cancel() { super.cancel() Task { @MainActor in self.alertObject.alertDelegate?.alertCancelled(self.alertController) await self.dismissAlertView() } } private func dismissAlertView() async { guard await properties.getIsDisplayed() else { return } await withCheckedContinuation { continuation in Task { let dismissOperation = await NavigationOperation(with: .dismiss(viewController: alertController)) dismissOperation.queuePriority = .veryHigh let task = Task(priority: .high) { await NavigationHandler.shared().navigate(with: dismissOperation) } blockingOperation?.markAsFinished() _ = await task.result continuation.resume() } } } public override func markAsFinished() { blockingOperation?.markAsFinished() super.markAsFinished() } // MARK: Observer Functions private func observeForCurrentAlertViewDismissal() { stopObservingAlertView() cancellable = alertController.publisher(for: \AlertController.visible).sink() { [weak self] visible in guard let self = self else { return } Task { guard await self.properties.getIsDisplayed() != visible else { return } await self.properties.set(displayed: visible) Task { @MainActor in if visible { self.alertObject.alertDelegate?.alertShown(self.alertController) } else { self.alertObject.alertDelegate?.alertDismissed(self.alertController) // Is visible was set to NO, meaning that the alertview is no longer visible. self.stopObservingAlertView() self.markAsFinished() } } } } } private func stopObservingAlertView() { cancellable?.cancel() } }