mvm_core_ui/MVMCoreUI/Alerts/AlertOperation.swift
2023-10-04 10:17:49 -04:00

139 lines
4.8 KiB
Swift

//
// 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()
}
}