diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 1a59212b..85b5fea1 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -563,7 +563,7 @@ D2ED27EE254B0CE700A1C293 /* ActionAlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27E9254B0CE600A1C293 /* ActionAlertModel.swift */; }; D2ED27EF254B0CE700A1C293 /* AlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27EA254B0CE700A1C293 /* AlertModel.swift */; }; D2ED27FB254B0E0300A1C293 /* MVMCoreAlertDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED27F2254B0E0200A1C293 /* MVMCoreAlertDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D2ED27FC254B0E0300A1C293 /* MVMCoreAlertObject+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27F3254B0E0200A1C293 /* MVMCoreAlertObject+Swift.swift */; }; + D2ED27FC254B0E0300A1C293 /* AlertObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27F3254B0E0200A1C293 /* AlertObject.swift */; }; D2ED280C254B0EB800A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED2805254B0EB700A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2ED280D254B0EB800A1C293 /* MVMCoreTopAlertOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED2806254B0EB700A1C293 /* MVMCoreTopAlertOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2ED280E254B0EB800A1C293 /* MVMCoreTopAlertOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = D2ED2807254B0EB700A1C293 /* MVMCoreTopAlertOperation.m */; }; @@ -1169,7 +1169,7 @@ D2ED27E9254B0CE600A1C293 /* ActionAlertModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionAlertModel.swift; sourceTree = ""; }; D2ED27EA254B0CE700A1C293 /* AlertModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertModel.swift; sourceTree = ""; }; D2ED27F2254B0E0200A1C293 /* MVMCoreAlertDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreAlertDelegateProtocol.h; sourceTree = ""; }; - D2ED27F3254B0E0200A1C293 /* MVMCoreAlertObject+Swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MVMCoreAlertObject+Swift.swift"; sourceTree = ""; }; + D2ED27F3254B0E0200A1C293 /* AlertObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertObject.swift; sourceTree = ""; }; D2ED27F7254B0E0200A1C293 /* MVMCoreAlertHandler+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MVMCoreAlertHandler+Extension.swift"; sourceTree = ""; }; D2ED2805254B0EB700A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreTopAlertAnimationDelegateProtocol.h; sourceTree = ""; }; D2ED2806254B0EB700A1C293 /* MVMCoreTopAlertOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreTopAlertOperation.h; sourceTree = ""; }; @@ -2523,7 +2523,7 @@ children = ( D2ED27F2254B0E0200A1C293 /* MVMCoreAlertDelegateProtocol.h */, D2ED27F7254B0E0200A1C293 /* MVMCoreAlertHandler+Extension.swift */, - D2ED27F3254B0E0200A1C293 /* MVMCoreAlertObject+Swift.swift */, + D2ED27F3254B0E0200A1C293 /* AlertObject.swift */, AF7E509729E477C0009DC2AD /* AlertController.swift */, AF7E509629E477C0009DC2AD /* AlertHandler.swift */, AFA4931F29E5CA73001A9663 /* AlertOperation.swift */, @@ -2703,7 +2703,7 @@ AAB9C10824346F4B00151545 /* RadioSwatches.swift in Sources */, 94C2D9A923872E5E0006CF46 /* LabelAttributeImageModel.swift in Sources */, DBC4391922442197001AB423 /* DashLine.swift in Sources */, - D2ED27FC254B0E0300A1C293 /* MVMCoreAlertObject+Swift.swift in Sources */, + D2ED27FC254B0E0300A1C293 /* AlertObject.swift in Sources */, D264FAAA2440F97600D98315 /* CollectionView.swift in Sources */, AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */, AAC23FAD24D92A0D009208DF /* ListThreeColumnSpeedTestModel.swift in Sources */, diff --git a/MVMCoreUI/Alerts/AlertHandler.swift b/MVMCoreUI/Alerts/AlertHandler.swift index 5a7b843a..d2240cb7 100644 --- a/MVMCoreUI/Alerts/AlertHandler.swift +++ b/MVMCoreUI/Alerts/AlertHandler.swift @@ -29,11 +29,6 @@ public class AlertHandler { }) } - /// Cancels all current alerts - public func removeAllAlertViews() { - queue.cancelAllOperations() - } - /// Returns if a greedy alert is currently showing in the hierarchy, even if it is not the top presented view. public func isGreedyAlertShowing() -> Bool { return queue.operations.contains(where: { operation in @@ -86,4 +81,29 @@ public class AlertHandler { queue.addOperation(alertOperation) return alertController } + + /// Cancel Alert with ID. + public func cancelAlert(with id: String) { + queue.operations.first { operation in + guard let operation = operation as? AlertOperation, + operation.alertObject.alertModel.id == id else { return false } + return true + }?.cancel() + } + + /** Iterates through all scheduled alerts and cancels any that match the provided predicate. + * @param predicate The predicate block to decide whether to cancel an alert. + */ + public func cancelAlert(using predicate: ((AlertObject) -> Bool)) { + for case let operation as AlertOperation in queue.operations { + if predicate(operation.alertObject) { + operation.cancel() + } + } + } + + /// Cancels all current alerts + public func removeAllAlertViews() { + queue.cancelAllOperations() + } } diff --git a/MVMCoreUI/Alerts/AlertObject.swift b/MVMCoreUI/Alerts/AlertObject.swift new file mode 100644 index 00000000..398fc7c3 --- /dev/null +++ b/MVMCoreUI/Alerts/AlertObject.swift @@ -0,0 +1,27 @@ +// +// AlertObject.swift +// MVMCore +// +// Created by Suresh, Kamlesh on 7/10/20. +// Copyright © 2020 myverizon. All rights reserved. +// + +import MVMCore + +/// An object with properties for managing the alert. +public struct AlertObject { + + /// Greedy alerts dismiss any other alerts and do not allow any other alerts to show until finished. + public var isGreedy = false + + /// The alert model for the alert to show. + public var alertModel: AlertModel + + public weak var alertDelegate: MVMCoreAlertDelegateProtocol? + + public init(alertModel: AlertModel, isGreedy: Bool = false, alertDelegate: MVMCoreAlertDelegateProtocol? = nil) { + self.alertModel = alertModel + self.isGreedy = isGreedy + self.alertDelegate = alertDelegate + } +} diff --git a/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift b/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift deleted file mode 100644 index 69305385..00000000 --- a/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// MVMCoreAlertObject+Swift.swift -// MVMCore -// -// Created by Suresh, Kamlesh on 7/10/20. -// Copyright © 2020 myverizon. All rights reserved. -// - -import MVMCore - -/// An object with properties for managing the alert. -public struct AlertObject { - - /// Greedy alerts dismiss any other alerts and do not allow any other alerts to show until finished. - public var isGreedy = false - - /// The alert model for the alert to show. - public var alertModel: AlertModel - - public weak var alertDelegate: MVMCoreAlertDelegateProtocol? - - public init(alertModel: AlertModel, isGreedy: Bool = false, alertDelegate: MVMCoreAlertDelegateProtocol? = nil) { - self.alertModel = alertModel - self.isGreedy = isGreedy - self.alertDelegate = alertDelegate - } -} - -//public extension MVMCoreAlertObject { -// -// static func alertObject(from alertModel: AlertModel, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> MVMCoreAlertObject? { -// -// let actionsForAlert = actions ?? generateActions(from: alertModel.alertActions, additionalData: additionalData, delegateObject: delegateObject) -// -// let alertObject = MVMCoreAlertObject(popupAlertWithTitle: alertModel.title, -// message: alertModel.message, -// actions: actionsForAlert, -// isGreedy: false) -// -// alertObject?.alertStyle = alertModel.style -// alertObject?.pageJson = alertModel.analyticsData -// -// return alertObject -// } -// -// static func generateActions(from buttonModels: [AlertButtonModel], additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?, additionalHandling: ((AlertButtonModel, UIAlertAction)->())? = nil) -> [UIAlertAction] { -// return buttonModels.map { alertButtonModel in -// let alertAction = UIAlertAction(title: alertButtonModel.title, style: alertButtonModel.style) { action in -// Task(priority: .userInitiated) { -// do { -// try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction( -// with: alertButtonModel.action, -// additionalData: additionalData, -// delegateObject: delegateObject -// ) -// } catch { -// -// } -// additionalHandling?(alertButtonModel, action) -// } -// } -// return alertAction -// } -// } -// -// @objc static func alertObjectWith(action actionJson: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> MVMCoreAlertObject? { -// -// guard let alertJson = actionJson?.optionalDictionaryForKey("alert"), -// (alertJson.optionalStringForKey(KeyTitle) != nil || alertJson.optionalStringForKey(KeyMessage) != nil), -// let actionsList = alertJson.optionalArrayForKey("alertActions") as? [[AnyHashable: Any]] -// else { -// error?.pointee = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), code: ErrorCode.popupFailed.rawValue, domain: ErrorDomainNative, location: String(describing: self)) -// return nil -// } -// -// var actionsForAlert: [UIAlertAction] = [] -// -// for actionJson in actionsList { -// let style = UIAlertAction.Style(rawValue: actionJson.stringForkey("style")) -// let alertAction = UIAlertAction(title: actionJson.optionalStringForKey(KeyTitle), style: style) { action in -// MVMCoreActionHandler.shared()?.handleAction(with: actionJson.optionalDictionaryForKey("action"), -// additionalData: additionalData, -// delegateObject: delegateObject) -// } -// actionsForAlert.append(alertAction) -// } -// -// let alertObject = MVMCoreAlertObject(popupAlertWithTitle: alertJson.optionalStringForKey(KeyTitle), -// message: alertJson.optionalStringForKey(KeyMessage), -// actions: actionsForAlert, -// isGreedy: false) -// -// if let alertStyle = alertJson.optionalStringForKey("style") { -// alertObject?.alertStyle = UIAlertController.Style(rawValue: alertStyle) -// } -// -// if let analyticsData = alertJson.optionalDictionaryForKey("analyticsData") { -// alertObject?.pageJson = ["analyticsData": analyticsData] -// } -// -// return alertObject -// } -//} diff --git a/MVMCoreUI/Alerts/TopNotificationHandler.swift b/MVMCoreUI/Alerts/TopNotificationHandler.swift index ce6a34a0..19970997 100644 --- a/MVMCoreUI/Alerts/TopNotificationHandler.swift +++ b/MVMCoreUI/Alerts/TopNotificationHandler.swift @@ -137,6 +137,20 @@ public class TopNotificationHandler { // 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 } @@ -176,14 +190,6 @@ public class TopNotificationHandler { operation.cancel() } } - - 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 - } /// Cancel all persistent operations of this type. public func hidePersistentTopAlertView(of type: String) { @@ -196,6 +202,16 @@ public class TopNotificationHandler { } } + /// 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() } diff --git a/MVMCoreUI/Atomic/Actions/AlertModel.swift b/MVMCoreUI/Atomic/Actions/AlertModel.swift index e4b82018..4f22cfc7 100644 --- a/MVMCoreUI/Atomic/Actions/AlertModel.swift +++ b/MVMCoreUI/Atomic/Actions/AlertModel.swift @@ -60,7 +60,7 @@ public struct AlertButtonModel: Codable { } } -public struct AlertModel: Codable { +public struct AlertModel: Codable, Identifiable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -71,25 +71,28 @@ public struct AlertModel: Codable { public var actions: [UIAlertAction] public var alertActions: [AlertButtonModel]? public var analyticsData: JSONValueDictionary? - + public var id: String + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public init(title: String, message: String, actions: [UIAlertAction], style: UIAlertController.Style = .alert) { + public init(title: String, message: String, actions: [UIAlertAction], style: UIAlertController.Style = .alert, id: String = UUID().uuidString) { self.title = title self.message = message self.actions = actions self.style = style + self.id = id } - public init(title: String, message: String, buttonModels: [AlertButtonModel], style: UIAlertController.Style = .alert, delegateObject: DelegateObject?) { + public init(title: String, message: String, buttonModels: [AlertButtonModel], style: UIAlertController.Style = .alert, delegateObject: DelegateObject?, id: String = UUID().uuidString) { self.title = title self.message = message actions = buttonModels.map({ alertButtonModel in return alertButtonModel.generateAction(delegateObject: delegateObject) }) self.style = style + self.id = id } //-------------------------------------------------- @@ -102,6 +105,7 @@ public struct AlertModel: Codable { case alertActions case style case analyticsData + case id } //-------------------------------------------------- @@ -122,6 +126,7 @@ public struct AlertModel: Codable { if let style = try typeContainer.decodeIfPresent(String.self, forKey: .style) { self.style = UIAlertController.Style(rawValue: style) } + id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString } public func encode(to encoder: Encoder) throws { @@ -131,6 +136,7 @@ public struct AlertModel: Codable { try container.encodeIfPresent(alertActions, forKey: .alertActions) try container.encode(style.rawValueString, forKey: .style) try container.encodeIfPresent(analyticsData, forKey: .analyticsData) + try container.encode(id, forKey: .id) } }