// // 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() // Adds the presentation to the animation queue. let blockingOperation = MVMCoreOperation() self.blockingOperation = blockingOperation Task { @MainActor in MVMCoreNavigationHandler.shared()?.present(alertController, animated: true, delegate: nil) { [weak self] in guard let self = self else { blockingOperation.markAsFinished() return } Task { // 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 { (CoreUIObject.sharedInstance()?.loggingDelegate as? MVMCoreUILoggingDelegateProtocol)?.logAlert(with: self.alertObject) if self.isCancelled { await self.dismissAlertView() } } } } // Block navigations until this alert is removed. MVMCoreNavigationHandler.shared()?.addNavigationOperation(blockingOperation) } } 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 { @MainActor in MVMCoreNavigationHandler.shared()?.dismiss(alertController, animated: true, delegate: nil) { 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() } }