mvm_core_ui/MVMCoreUI/Alerts/AlertOperation.swift
2023-04-19 16:36:16 -04:00

152 lines
5.5 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
private var alDescription: String = ""
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()
MVMCoreDispatchUtility.performSyncBlock(onMainThread: {
self.alDescription = alert.description
})
}
deinit {
stopObservingAlertView()
}
public override func main() {
guard !checkAndHandleForCancellation() else { return }
// Observe for when it is removed.
observeForCurrentAlertViewDismissal()
print("---\nTTTTTT alertOperation present: \(self.description)\ncontroller:\(self.alDescription)\nobject: \(alertObject.alertModel.id)\n---")
// 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() {
print("---\nTTTTTT alertOperation cancelled: \(self.description)\ncontroller: \(self.alDescription)\nobject: \(self.alertObject.alertModel.id)\n---")
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
print("---\nTTTTTT alertOperation beginDismiss: \(self.description)\ncontroller: \(self.alDescription)\nobject: \(self.alertObject.alertModel.id)\n---")
MVMCoreNavigationHandler.shared()?.dismiss(alertController, animated: true, delegate: nil) {
print("---\nTTTTTT alertOperation endDismiss: \(self.description)\ncontroller: \(self.alDescription)\nobject: \(self.alertObject.alertModel.id)\n---")
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 {
print("---\nTTTTTT alertOperation visible true: \(self.description)\ncontroller:\(self.alDescription)\nobject: \(self.alertObject.alertModel.id)\n---")
self.alertObject.alertDelegate?.alertShown(self.alertController)
} else {
print("---\nTTTTTT alertOperation visible false: \(self.description)\ncontroller:\(self.alDescription)\nobject: \(self.alertObject.alertModel.id)\n---")
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()
}
}