From f231ea26d7ad66c1d042b436742bf6c18600980f Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 13 Apr 2023 11:54:51 -0400 Subject: [PATCH 01/12] Migrate AlertHandler files to swift. --- MVMCoreUI.xcodeproj/project.pbxproj | 42 +-- MVMCoreUI/Alerts/AlertController.swift | 38 +++ MVMCoreUI/Alerts/AlertHandler.swift | 89 ++++++ MVMCoreUI/Alerts/AlertOperation.swift | 114 ++++++++ MVMCoreUI/Alerts/MVMCoreAlertHandler.h | 119 -------- MVMCoreUI/Alerts/MVMCoreAlertHandler.m | 274 ------------------ .../Alerts/MVMCoreAlertObject+Swift.swift | 159 +++++----- MVMCoreUI/Alerts/MVMCoreAlertObject.h | 65 ----- MVMCoreUI/Alerts/MVMCoreAlertObject.m | 197 ------------- MVMCoreUI/Alerts/MVMCoreAlertOperation.h | 39 --- MVMCoreUI/Alerts/MVMCoreAlertOperation.m | 242 ---------------- MVMCoreUI/Alerts/TopNotificationHandler.swift | 230 +++++++++++++++ .../Atomic/Actions/ActionAlertHandler.swift | 25 +- .../Atomic/Actions/ActionPopupHandler.swift | 104 ++++++- .../Actions/ActionTopAlertHandler.swift | 10 +- .../ActionTopNotificationHandler.swift | 2 +- MVMCoreUI/Atomic/Actions/AlertModel.swift | 47 ++- .../BaseControllers/ViewController.swift | 2 +- MVMCoreUI/MVMCoreUI.h | 3 - MVMCoreUI/OtherHandlers/CoreUIObject.swift | 5 + .../MVMCoreUIActionDelegateProtocol.h | 3 - .../MVMCoreUIActionHandler.swift | 6 +- MVMCoreUI/TopAlert/MVMCoreTopAlertObject.m | 1 - MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m | 49 ++-- .../MVMCoreUITopAlertExpandableView.m | 1 - .../TopAlert/MVMCoreUITopAlertMainView.m | 1 - .../MVMCoreUITopAlertView+Extension.swift | 70 +---- MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m | 3 - 28 files changed, 752 insertions(+), 1188 deletions(-) create mode 100644 MVMCoreUI/Alerts/AlertController.swift create mode 100644 MVMCoreUI/Alerts/AlertHandler.swift create mode 100644 MVMCoreUI/Alerts/AlertOperation.swift delete mode 100644 MVMCoreUI/Alerts/MVMCoreAlertHandler.h delete mode 100644 MVMCoreUI/Alerts/MVMCoreAlertHandler.m delete mode 100644 MVMCoreUI/Alerts/MVMCoreAlertObject.h delete mode 100644 MVMCoreUI/Alerts/MVMCoreAlertObject.m delete mode 100644 MVMCoreUI/Alerts/MVMCoreAlertOperation.h delete mode 100644 MVMCoreUI/Alerts/MVMCoreAlertOperation.m create mode 100644 MVMCoreUI/Alerts/TopNotificationHandler.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 104eef80..202e1d54 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -287,6 +287,10 @@ AF1C33732885D481006B1001 /* MVMCoreUIActionOpenPageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C33722885D481006B1001 /* MVMCoreUIActionOpenPageHandler.swift */; }; AF60A7F62892D2E300919EEB /* ActionDismissNotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F52892D2E300919EEB /* ActionDismissNotificationModel.swift */; }; AF60A7F82892D34D00919EEB /* ActionDismissNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F72892D34D00919EEB /* ActionDismissNotificationHandler.swift */; }; + AF7E509829E477C1009DC2AD /* AlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7E509629E477C0009DC2AD /* AlertHandler.swift */; }; + AF7E509929E477C1009DC2AD /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7E509729E477C0009DC2AD /* AlertController.swift */; }; + AFA4932029E5CA73001A9663 /* AlertOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4931F29E5CA73001A9663 /* AlertOperation.swift */; }; + AFA4932229E5EF2E001A9663 /* TopNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */; }; AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; }; AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; }; BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */; }; @@ -561,13 +565,6 @@ 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 */; }; - D2ED27FD254B0E0300A1C293 /* MVMCoreAlertOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27F4254B0E0200A1C293 /* MVMCoreAlertOperation.m */; }; - D2ED27FE254B0E0300A1C293 /* MVMCoreAlertObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED27F5254B0E0200A1C293 /* MVMCoreAlertObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D2ED27FF254B0E0300A1C293 /* MVMCoreAlertHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED27F6254B0E0200A1C293 /* MVMCoreAlertHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D2ED2800254B0E0300A1C293 /* MVMCoreAlertHandler+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27F7254B0E0200A1C293 /* MVMCoreAlertHandler+Extension.swift */; }; - D2ED2801254B0E0300A1C293 /* MVMCoreAlertOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED27F8254B0E0200A1C293 /* MVMCoreAlertOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D2ED2802254B0E0300A1C293 /* MVMCoreAlertObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27F9254B0E0200A1C293 /* MVMCoreAlertObject.m */; }; - D2ED2803254B0E0300A1C293 /* MVMCoreAlertHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27FA254B0E0300A1C293 /* MVMCoreAlertHandler.m */; }; 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 */; }; @@ -897,6 +894,10 @@ AF1C33722885D481006B1001 /* MVMCoreUIActionOpenPageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIActionOpenPageHandler.swift; sourceTree = ""; }; AF60A7F52892D2E300919EEB /* ActionDismissNotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionDismissNotificationModel.swift; sourceTree = ""; }; AF60A7F72892D34D00919EEB /* ActionDismissNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionDismissNotificationHandler.swift; sourceTree = ""; }; + AF7E509629E477C0009DC2AD /* AlertHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertHandler.swift; sourceTree = ""; }; + AF7E509729E477C0009DC2AD /* AlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertController.swift; sourceTree = ""; }; + AFA4931F29E5CA73001A9663 /* AlertOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertOperation.swift; sourceTree = ""; }; + AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopNotificationHandler.swift; sourceTree = ""; }; AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = ""; }; AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewLeftAlignedLayout.swift; sourceTree = ""; }; @@ -1172,13 +1173,7 @@ 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 = ""; }; - D2ED27F4254B0E0200A1C293 /* MVMCoreAlertOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreAlertOperation.m; sourceTree = ""; }; - D2ED27F5254B0E0200A1C293 /* MVMCoreAlertObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreAlertObject.h; sourceTree = ""; }; - D2ED27F6254B0E0200A1C293 /* MVMCoreAlertHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreAlertHandler.h; sourceTree = ""; }; D2ED27F7254B0E0200A1C293 /* MVMCoreAlertHandler+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MVMCoreAlertHandler+Extension.swift"; sourceTree = ""; }; - D2ED27F8254B0E0200A1C293 /* MVMCoreAlertOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreAlertOperation.h; sourceTree = ""; }; - D2ED27F9254B0E0200A1C293 /* MVMCoreAlertObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreAlertObject.m; sourceTree = ""; }; - D2ED27FA254B0E0300A1C293 /* MVMCoreAlertHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreAlertHandler.m; 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 = ""; }; D2ED2807254B0EB700A1C293 /* MVMCoreTopAlertOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreTopAlertOperation.m; sourceTree = ""; }; @@ -2533,14 +2528,12 @@ isa = PBXGroup; children = ( D2ED27F2254B0E0200A1C293 /* MVMCoreAlertDelegateProtocol.h */, - D2ED27F6254B0E0200A1C293 /* MVMCoreAlertHandler.h */, - D2ED27FA254B0E0300A1C293 /* MVMCoreAlertHandler.m */, D2ED27F7254B0E0200A1C293 /* MVMCoreAlertHandler+Extension.swift */, - D2ED27F5254B0E0200A1C293 /* MVMCoreAlertObject.h */, - D2ED27F9254B0E0200A1C293 /* MVMCoreAlertObject.m */, D2ED27F3254B0E0200A1C293 /* MVMCoreAlertObject+Swift.swift */, - D2ED27F8254B0E0200A1C293 /* MVMCoreAlertOperation.h */, - D2ED27F4254B0E0200A1C293 /* MVMCoreAlertOperation.m */, + AF7E509729E477C0009DC2AD /* AlertController.swift */, + AF7E509629E477C0009DC2AD /* AlertHandler.swift */, + AFA4931F29E5CA73001A9663 /* AlertOperation.swift */, + AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */, ); path = Alerts; sourceTree = ""; @@ -2592,15 +2585,12 @@ D29DF12D21E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.h in Headers */, D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */, D2ED2811254B0EB800A1C293 /* MVMCoreTopAlertObject.h in Headers */, - D2ED27FE254B0E0300A1C293 /* MVMCoreAlertObject.h in Headers */, D2ED280F254B0EB800A1C293 /* MVMCoreTopAlertViewProtocol.h in Headers */, D2ED280C254B0EB800A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h in Headers */, D2ED280D254B0EB800A1C293 /* MVMCoreTopAlertOperation.h in Headers */, - D2ED2801254B0E0300A1C293 /* MVMCoreAlertOperation.h in Headers */, D2ED2818254B115400A1C293 /* MVMCoreUIActionDelegateProtocol.h in Headers */, D2ED27FB254B0E0300A1C293 /* MVMCoreAlertDelegateProtocol.h in Headers */, D2ED2810254B0EB800A1C293 /* MVMCoreTopAlertDelegateProtocol.h in Headers */, - D2ED27FF254B0E0300A1C293 /* MVMCoreAlertHandler.h in Headers */, D2ED2815254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h in Headers */, D29DF26F21E6AA0B003B2FB9 /* FLAnimatedImageView.h in Headers */, D29DF2A121E7AF4E003B2FB9 /* MVMCoreUIUtility.h in Headers */, @@ -2776,7 +2766,6 @@ 012A88C8238DB02000FE3DA1 /* MoleculeDelegateProtocol.swift in Sources */, 8D8067D12444472F00203BE8 /* ListRightVariablePriceChangeAllTextAndLinksModel.swift in Sources */, 0A7EF86123D8AC2500B2AAD1 /* DigitEntryFieldModel.swift in Sources */, - D2ED2802254B0E0300A1C293 /* MVMCoreAlertObject.m in Sources */, D224798C231450C8003FCCF9 /* HeadlineBodyToggle.swift in Sources */, 9458C3182406C8FD00930963 /* UIFont+FontWrapping.m in Sources */, 522679C123FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift in Sources */, @@ -2803,7 +2792,6 @@ D2E2A99823D8D63C000B42E6 /* ActionDetailWithImageModel.swift in Sources */, D28764AC245898A400CB882D /* ThreeLayerFillMiddleTemplateModel.swift in Sources */, BBBBC87D24374A4900B0F079 /* ListThreeColumnBillChangesDividerModel.swift in Sources */, - D2ED2800254B0E0300A1C293 /* MVMCoreAlertHandler+Extension.swift in Sources */, D2ED27EE254B0CE700A1C293 /* ActionAlertModel.swift in Sources */, D2E2A99D23DA3217000B42E6 /* UIStackViewAlignment+Extension.swift in Sources */, 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */, @@ -2872,6 +2860,7 @@ 8DDD6C1D244D90B8006A2232 /* ListThreeColumnDataUsage.swift in Sources */, 0A849EFE246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift in Sources */, D28764FB245A33A500CB882D /* TwoLinkViewModel.swift in Sources */, + AFA4932029E5CA73001A9663 /* AlertOperation.swift in Sources */, AAA74A192410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift in Sources */, AAE96FA525341F7D0037A989 /* ListStoreLocator.swift in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, @@ -2916,7 +2905,6 @@ D2092357244FA1EF0044AD09 /* ThreeLayerModelBase.swift in Sources */, D2FD4A4925199BD9000C28A9 /* AccessibilityProtocol.swift in Sources */, D2CAC7CD251104FE00C75681 /* NotificationModel.swift in Sources */, - D2ED2803254B0E0300A1C293 /* MVMCoreAlertHandler.m in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */, EAA0CFAF275E7D8000D65EB0 /* FormFieldEffectProtocol.swift in Sources */, D20923592450ECE00044AD09 /* TableView.swift in Sources */, @@ -2971,6 +2959,7 @@ AA7F47732541AD560015A2C1 /* ListStarRatingModel.swift in Sources */, AA7F47762541AD6A0015A2C1 /* ListStarRating.swift in Sources */, 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */, + AF7E509829E477C1009DC2AD /* AlertHandler.swift in Sources */, D2ED27EB254B0CE700A1C293 /* UIAlertActionStyle+Codable.swift in Sources */, BB55B51D244482C1002001AD /* ListRightVariablePriceChangeBodyText.swift in Sources */, 017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */, @@ -3015,7 +3004,6 @@ 3265B30224BCA737000D154B /* HeadersH1NoButtonsBodyTextModel.swift in Sources */, D28A838F23CCDEDE00DFE4FC /* TwoButtonViewModel.swift in Sources */, D264FAAC2441009400D98315 /* RadioBoxCollectionViewCell.swift in Sources */, - D2ED27FD254B0E0300A1C293 /* MVMCoreAlertOperation.m in Sources */, BB2C969224330F73006FF80C /* ListRightVariableTextLinkAllTextAndLinks.swift in Sources */, D2D90B42240463E100DD6EC9 /* MoleculeHeaderModel.swift in Sources */, 012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */, @@ -3050,6 +3038,7 @@ D20C7009250BF99B0095B21C /* TopNotificationModel.swift in Sources */, D29C558A25C05C7D0082E7D6 /* BGVideoImageMoleculeModel.swift in Sources */, 8D24041123E7FB9E009E23BE /* ListLeftVariableIconWithRightCaret.swift in Sources */, + AFA4932229E5EF2E001A9663 /* TopNotificationHandler.swift in Sources */, BB2FB3BD247E7EF200DF73CD /* Tags.swift in Sources */, AA104ADC244734EA004D2810 /* HeadersH1LandingPageHeaderModel.swift in Sources */, BBAA4F03243D8E3B005AAD5F /* RadioBoxes.swift in Sources */, @@ -3078,6 +3067,7 @@ 0AE14F64238315D2005417F8 /* TextField.swift in Sources */, 0A51F3E22475CB73002E08B6 /* LoadingSpinnerModel.swift in Sources */, D2169303251E53D9002A6324 /* SectionListTemplateModel.swift in Sources */, + AF7E509929E477C1009DC2AD /* AlertController.swift in Sources */, 0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */, BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */, D253BB9C245874F8002DE544 /* BGImageMolecule.swift in Sources */, diff --git a/MVMCoreUI/Alerts/AlertController.swift b/MVMCoreUI/Alerts/AlertController.swift new file mode 100644 index 00000000..b5a2f71c --- /dev/null +++ b/MVMCoreUI/Alerts/AlertController.swift @@ -0,0 +1,38 @@ +// +// AlertController.swift +// MVMCore +// +// Created by Scott Pfeil on 3/24/23. +// Copyright © 2023 myverizon. All rights reserved. +// + +import Foundation + +@objc (MVMCoreAlertController) +public class AlertController: UIAlertController { + @objc dynamic public var visible = false + private let visibleKey = "isVisible" + + public override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return MVMCoreGetterUtility.isOnIPad() ? .all : .portrait + } + + public override var description: String { + return "\(super.description)|title=\(title ?? "")|message=\(message ?? "")" + } + + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + willChangeValue(forKey: visibleKey) + visible = true + didChangeValue(forKey: visibleKey) + } + + public override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + willChangeValue(forKey: visibleKey) + visible = false + didChangeValue(forKey: visibleKey) + } +} + diff --git a/MVMCoreUI/Alerts/AlertHandler.swift b/MVMCoreUI/Alerts/AlertHandler.swift new file mode 100644 index 00000000..39b2d046 --- /dev/null +++ b/MVMCoreUI/Alerts/AlertHandler.swift @@ -0,0 +1,89 @@ +// +// AlertHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 4/10/23. +// Copyright © 2023 myverizon. All rights reserved. +// + +import MVMCore + +public class AlertHandler { + + /// Returns the action handler stored in the CoreUIObject + public static func shared() -> Self { + return MVMCoreActionUtility.fatalClassCheck(object: CoreUIObject.sharedInstance()?.alertHandler) + } + + /// The operation queue of alert operations. + private var queue = OperationQueue() + + public init() {} + + /// Returns if an alert is currently showing in the hierarchy, even if it is not the top presented view. + public func isAlertShowing() -> Bool { + return queue.operations.contains(where: { operation in + return !operation.isCancelled && + !operation.isFinished && + operation.isExecuting + }) + } + + /// 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 + return !operation.isCancelled && + !operation.isFinished && + operation.isExecuting && + (operation as? AlertOperation)?.isGreedy ?? false + }) + } + + @MainActor + public func createAlertController(with alertModel: AlertModel) -> AlertController { + // ActionSheets are not supported on iPad interfaces without a source rect (i.e. a source element) which isn't currently supported for our generic handling. + // TODO: Find a way to support this. + var alertStyle = alertModel.style + if alertStyle == .actionSheet, UIDevice.current.userInterfaceIdiom != .phone { + alertStyle = .alert + } + + // Create the alert. Adds the actions one by one. + let alertController = AlertController(title: alertModel.title, message: alertModel.message, preferredStyle: alertStyle) + for action in alertModel.actions { + alertController.addAction(action) + } + return alertController + } + + /// Shows an alert using the alert object. + @MainActor + public func queueAlertToShow(with alertObject: AlertObject) -> UIAlertController { + + // It's a greedy alert! Clear all alerts that are queued up and the one that is showing + if alertObject.isGreedy { + removeAllAlertViews() + } + + let alertController = createAlertController(with: alertObject.alertModel) + let alertOperation = AlertOperation(with: alertController, isGreedy: alertObject.isGreedy, alertDelegate: alertObject.alertDelegate) + + // If an existing greedy alert is showing, add it as a dependency. + if let greedyAlertOperation = queue.operations.first(where: { operation in + guard !operation.isFinished, + !operation.isCancelled, + let alertOperation = operation as? AlertOperation else { return false } + return alertOperation.isGreedy + }) { + alertOperation.addDependency((greedyAlertOperation as! AlertOperation)) + } + + queue.addOperation(alertOperation) + return alertController + } +} diff --git a/MVMCoreUI/Alerts/AlertOperation.swift b/MVMCoreUI/Alerts/AlertOperation.swift new file mode 100644 index 00000000..ee280473 --- /dev/null +++ b/MVMCoreUI/Alerts/AlertOperation.swift @@ -0,0 +1,114 @@ +// +// 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() + + //private var observer: NSKeyValueObservation? + private var cancellable: Cancellable? + + public let alertController: AlertController + + public let isGreedy: Bool + + public weak var alertDelegate: MVMCoreAlertDelegateProtocol? + + public init(with alert: AlertController, isGreedy: Bool = false, alertDelegate: MVMCoreAlertDelegateProtocol? = nil) { + self.alertController = alert + self.isGreedy = isGreedy + self.alertDelegate = alertDelegate + } + + deinit { + stopObservingAlertView() + } + + public override func main() { + guard !checkAndHandleForCancellation() else { return } + + // Observe for when it is removed. + observeForCurrentAlertViewDismissal() + + // Adds the presentation to the animation queue. + MVMCoreNavigationHandler.shared()?.present(alertController, animated: true, delegate: nil) { [weak self] in + guard let self = self else { 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 if self.isCancelled { + await self.dismissAlertView() + } + } + } + } + + public override func cancel() { + super.cancel() + Task { @MainActor in + self.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() + } + } + } + } + + // 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 { @MainActor in + await self.properties.set(displayed: visible) + if visible { + self.alertDelegate?.alertShown?(self.alertController) + } else { + self.alertDelegate?.alertDismissed?(self.alertController) + + // Is visible was set to NO, meaning that the alertview is no longer visible. + self.stopObservingAlertView() + self.markAsFinished() + } + } + } +// observer = alertController.observe(\AlertController.visible, options: [.old, .new]) { [weak self] (object, change) in +// +// } + } + + private func stopObservingAlertView() { + //observer?.invalidate() + cancellable?.cancel() + } +} diff --git a/MVMCoreUI/Alerts/MVMCoreAlertHandler.h b/MVMCoreUI/Alerts/MVMCoreAlertHandler.h deleted file mode 100644 index 311b0c61..00000000 --- a/MVMCoreUI/Alerts/MVMCoreAlertHandler.h +++ /dev/null @@ -1,119 +0,0 @@ -// -// MVMCoreAlertHandler.h -// myverizon -// -// Created by Scott Pfeil on 3/10/14. -// Copyright (c) 2014 Verizon Wireless. All rights reserved. -// -// Keeps track of alerts and handles them. Should always use this to present alerts in mf. - -#import -#import -#import -#import - -@class MVMCoreAlertObject; -@class MVMCoreTopAlertOperation; - -@interface MVMCoreAlertHandler : NSObject - -// An operation queue for displaying popup alerts. -@property (nonnull, strong, nonatomic) NSOperationQueue *popupAlertQueue; - -// An operation queue for top alerts -@property (nonnull, strong, nonatomic) NSOperationQueue *topAlertQueue; - -/// Returns the shared instance of this singleton -+ (nullable instancetype)sharedAlertHandler; - -#pragma mark - Popup Alert Functions - -/// Returns if any alert is currently showing (even if supressed). -- (BOOL)alertCurrentlyShowing; - -/// Returns if a greedy alert is currently showing (even if supressed). -- (BOOL)greedyAlertShowing; - -/** Shows the popup with the passed in parameter. - * @param title The title of the alert. - * @param message The message of the alert. - * @param actions An array of actions for the alert. - * @param isGreedy Sets up a greedy popup. In other words, any popups currently shown or queued are dismissed. - * @return Returns the UIAlertController. - */ -- (nonnull UIAlertController *)showAlertWithTitle:(nullable NSString *)title message:(nullable NSString *)message actions:(nullable NSArray*)actions isGreedy:(BOOL)isGreedy; - -/** Shows the alert. - * @param title The title of the alert. - * @param message The message of the alert. - * @param actions An array of actions for the alert. - * @param alertStyle Popup or action sheet - * @param isGreedy Sets up a greedy alert. In other words, any alerts currently shown or queued are dismissed. - * @param alertDelegate The delegate to be notified. - * @return Returns the UIAlertController. - */ -- (nonnull UIAlertController *)showAlertWithTitle:(nullable NSString *)title message:(nullable NSString *)message actions:(nullable NSArray*)actions alertStyle:(UIAlertControllerStyle)alertStyle isGreedy:(BOOL)isGreedy alertDelegate:(nullable NSObject *)alertDelegate; - -/** Shows the popup with the passed in alert object. This is a convenience method that automatically handles using the proper alert type based on what's available. - * @param alertObject The alert object to use for the alert. - * @return Returns UIAlertController. - */ -- (nonnull UIAlertController *)showAlertWithAlertObject:(nonnull MVMCoreAlertObject *)alertObject; - -/** Cancels and removes an alert operation for the given alertObject. - * @param alertObject The alertObject scheduled to be shown. - */ -- (void)removeAlertViewForObject:(nonnull MVMCoreAlertObject *)alertObject; - -/** 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. -*/ -- (void)removeAlertViewUsingPredicate:(BOOL(^_Nonnull)(MVMCoreAlertObject * _Nonnull obj))predicate; - -/// Removes all alerts. -- (void)removeAllAlertViews; - -#pragma mark - Supression Functions - -/// Returns true if alerts are supressed. -- (BOOL)mfAlertsSupressed; - -/// Supresses the alerts (Used by other "apps" in our app). -- (void)supressMFAlerts; - -/// Unsupresses the alerts (Used by other "apps" in our app). -- (void)unSupressMFAlerts; - -#pragma mark - Top Alert Functions - -/// Show based on the object. Will be used by the architecture. Creates an operation and calls addTopAlertOperation. -- (void)showTopAlertWithObject:(nullable MVMCoreTopAlertObject *)topAlertObject; - -/// Adds the top alert operation to the queue. -- (void)addTopAlertOperation:(nonnull MVMCoreTopAlertOperation *)topAlertOperation; - -/// Convenience functions -- (void)showTopAlertErrorWithMessage:(nullable NSString *)message; -- (void)showTopAlertConfirmationWithMessage:(nullable NSString *)message; -- (void)showTopAlertWithType:(nullable NSString *)type message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage persistent:(BOOL)persistent actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData; -- (void)showTopAlertWithType:(nullable NSString *)type message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage persistent:(BOOL)persistent buttonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler; - -/// Hides the current alert view. -- (void)hideTopAlertView; - -/// Hides a persistent alert based on the type string. -- (void)hidePersistentTopAlertViewOfType:(nullable NSString *)type; - -/// Hides a alert based on the type string. -- (void)hideTopAlertViewOfType:(nullable NSString *)type; - -/// Removes a scheduled top alert given its top alert object. -- (void)removeTopAlertForObject:(nonnull MVMCoreTopAlertObject *)topAlertObject; - -/// Removes all top alerts. -- (void)removeAllTopAlerts; - -/// Returns YES if the persistent type is already registered in the alert queue. -- (BOOL)hasPersistentTopAlertOfType:(nullable NSString *)type; - -@end diff --git a/MVMCoreUI/Alerts/MVMCoreAlertHandler.m b/MVMCoreUI/Alerts/MVMCoreAlertHandler.m deleted file mode 100644 index 16fb1f4a..00000000 --- a/MVMCoreUI/Alerts/MVMCoreAlertHandler.m +++ /dev/null @@ -1,274 +0,0 @@ -// -// MVMCoreAlertHandler.m -// myverizon -// -// Created by Scott Pfeil on 3/10/14. -// Copyright (c) 2014 Verizon Wireless. All rights reserved. -// - -#import "MVMCoreAlertHandler.h" -#import "MVMCoreAlertObject.h" -@import MVMCore.MVMCoreAlertController; -#import "MVMCoreAlertOperation.h" -#import "MVMCoreTopAlertOperation.h" -@import MVMCore.MVMCoreJSONConstants; -@import MVMCore.NSDictionary_MFConvenience; -@import MVMCore.NSArray_MFConvenience; -#import - -@interface MVMCoreAlertHandler () - -// Flag that keeps track of if the alerts are supressed or not. -@property (assign, nonatomic) BOOL mfAlertsSupressed; - -@end - -@implementation MVMCoreAlertHandler - -+ (instancetype)sharedAlertHandler { - static dispatch_once_t once; - static id sharedInstance; - - dispatch_once(&once, ^{ - sharedInstance = [[self alloc] init]; - }); - - return sharedInstance; -} - -- (nullable instancetype)init { - if (self = [super init]) { - self.popupAlertQueue = [[NSOperationQueue alloc] init]; - self.popupAlertQueue.maxConcurrentOperationCount = 1; - self.topAlertQueue = [[NSOperationQueue alloc] init]; - self.topAlertQueue.maxConcurrentOperationCount = 1; - [self registerForPageChanges]; - } - return self; -} - -#pragma mark - Popup Alert Functions - -- (BOOL)alertCurrentlyShowing { - return (self.popupAlertQueue.operationCount > 0); -} - -- (BOOL)greedyAlertShowing { - if ([self alertCurrentlyShowing]) { - NSInteger index = [self.popupAlertQueue.operations indexOfObjectPassingTest:^BOOL(__kindof MVMCoreAlertOperation * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - if (obj.isExecuting && obj.isGreedy && stop) { - *stop = YES; - return YES; - } else { - return NO; - } - }]; - return (index != NSNotFound); - } - return NO; -} - -- (nonnull UIAlertController *)showAlertWithTitle:(nullable NSString *)title message:(nullable NSString *)message actions:(nullable NSArray*)actions isGreedy:(BOOL)isGreedy { - return [self showAlertWithTitle:title message:message actions:actions isGreedy:isGreedy alertDelegate:nil]; -} - -- (nonnull UIAlertController *)showAlertWithTitle:(nullable NSString *)title message:(nullable NSString *)message actions:(nullable NSArray*)actions isGreedy:(BOOL)isGreedy alertDelegate:(nullable NSObject *)alertDelegate { - return [self showAlertWithTitle:title message:message actions:actions alertStyle:UIAlertControllerStyleAlert isGreedy:isGreedy alertDelegate:alertDelegate]; -} - -- (nonnull UIAlertController *)showAlertWithTitle:(nullable NSString *)title message:(nullable NSString *)message actions:(nullable NSArray*)actions alertStyle:(UIAlertControllerStyle)alertStyle isGreedy:(BOOL)isGreedy alertDelegate:(nullable NSObject *)alertDelegate { - - // It's a greedy alert! Clear all alerts that are queued up and the one that is showing - if (isGreedy) { - [self removeAllAlertViews]; - } - - if (alertStyle == UIAlertControllerStyleActionSheet && UIDevice.currentDevice.userInterfaceIdiom != UIUserInterfaceIdiomPhone) { - // ActionSheets are not supported on iPad interfaces without a source rect (i.e. a source element) which isn't currently supported for our generic handling. - alertStyle = UIAlertControllerStyleAlert; - } - - // Create the alert. Adds the actions one by one. - MVMCoreAlertController *alertController = [MVMCoreAlertController alertControllerWithTitle:(title ?: @"") message:message preferredStyle:alertStyle]; - for (NSUInteger i = 0; i < [actions count]; i++) { - UIAlertAction *action = [actions objectAtIndex:i ofType:[UIAlertAction class]]; - if (action) { - [alertController addAction:action]; - } - } - - MVMCoreAlertOperation *alertOperation = [[MVMCoreAlertOperation alloc] initWithAlert:alertController isGreedy:isGreedy alertDelegate:alertDelegate]; - [self.popupAlertQueue addOperation:alertOperation]; - return alertController; -} - -- (nonnull UIAlertController *)showAlertWithAlertObject:(nonnull MVMCoreAlertObject *)alertObject { - MVMCoreAlertController *controller = (MVMCoreAlertController *)[self showAlertWithTitle:alertObject.title message:alertObject.message actions:alertObject.actions alertStyle:alertObject.alertStyle isGreedy:alertObject.isGreedy alertDelegate:alertObject.alertDelegate]; - controller.alertObject = alertObject; - return controller; -} - -- (void)removeAlertViewForObject:(MVMCoreAlertObject *)alertObject { - for (MVMCoreAlertOperation *operation in self.popupAlertQueue.operations) { - if ([operation.currentAlertView isKindOfClass:[MVMCoreAlertController class]] && [(MVMCoreAlertController *)operation.currentAlertView alertObject] == alertObject) { - [operation cancel]; - } - } -} - -- (void)removeAlertViewUsingPredicate:(BOOL(^)(MVMCoreAlertObject *obj))predicate { - for (MVMCoreAlertOperation *operation in self.popupAlertQueue.operations) { - if ([operation.currentAlertView isKindOfClass:[MVMCoreAlertController class]]) { - MVMCoreAlertObject *alertObject = [(MVMCoreAlertController *)operation.currentAlertView alertObject]; - if (alertObject && predicate(alertObject)) { - [operation cancel]; - } - } - } -} - -- (void)removeAllAlertViews { - [self.popupAlertQueue cancelAllOperations]; -} - -#pragma mark - Supression Functions - -- (BOOL)mfAlertsSupressed { - return _mfAlertsSupressed; -} - -- (void)supressMFAlerts { - if (!self.mfAlertsSupressed) { - self.mfAlertsSupressed = YES; - for (MVMCoreAlertOperation *operation in self.popupAlertQueue.operations) { - [operation pause]; - } - for (MVMCoreTopAlertOperation *operation in self.topAlertQueue.operations) { - [operation pause]; - } - } -} - -- (void)unSupressMFAlerts { - if (self.mfAlertsSupressed) { - self.mfAlertsSupressed = NO; - for (MVMCoreAlertOperation *operation in self.popupAlertQueue.operations) { - [operation unpause]; - } - for (MVMCoreTopAlertOperation *operation in self.topAlertQueue.operations) { - [operation unpause]; - } - } -} - -#pragma mark - Top Alert Functions - -- (void)addTopAlertOperation:(nonnull MVMCoreTopAlertOperation *)topAlertOperation { - __block MVMCoreTopAlertOperation *alertOperation = topAlertOperation; - __weak typeof(self) weakSelf = self; - [alertOperation setCompletionBlock:^{ - - // If the alert was cancelled to show another with higher priority, re-add to the operation when cancelled to the queue. - if (alertOperation.reAddAfterCancel) { - MVMCoreTopAlertOperation *newOperation = [alertOperation copy]; - newOperation.reAddAfterCancel = NO; - [weakSelf addTopAlertOperation:newOperation]; - } - alertOperation = nil; - }]; - - NSString *currentPageType = ((UIViewController *)[[MVMCoreUISplitViewController mainSplitViewController] getCurrentDetailViewController]).pageType; - [alertOperation updateDisplayableByPageType:currentPageType]; - - [self.topAlertQueue addOperation:alertOperation]; - [self reevaluteQueue]; -} - -- (void)showTopAlertWithObject:(nullable MVMCoreTopAlertObject *)topAlertObject { - MVMCoreTopAlertOperation *alertOperation = [[MVMCoreTopAlertOperation alloc] initWithTopAlertObject:topAlertObject]; - [self addTopAlertOperation:alertOperation]; -} - -- (void)showTopAlertErrorWithMessage:(nullable NSString *)message { - - MVMCoreTopAlertObject *topAlertObject = [[MVMCoreTopAlertObject alloc] initWithType:ValueTypeError message:message]; - [self showTopAlertWithObject:topAlertObject]; -} - -- (void)showTopAlertConfirmationWithMessage:(nullable NSString *)message { - - MVMCoreTopAlertObject *topAlertObject = [[MVMCoreTopAlertObject alloc] initWithType:ValueTypeSuccess message:message]; - [self showTopAlertWithObject:topAlertObject]; -} - -- (void)showTopAlertWithType:(nullable NSString *)type message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage persistent:(BOOL)persistent actionMap:(nullable NSDictionary *)actionMap additionalData:(nullable NSDictionary *)additionalData { - - MVMCoreTopAlertObject *topAlertObject = [[MVMCoreTopAlertObject alloc] initWithType:type message:message subMessage:subMessage persistent:persistent actionMap:actionMap additionalData:additionalData]; - [self showTopAlertWithObject:topAlertObject]; -} - -- (void)showTopAlertWithType:(nullable NSString *)type message:(nullable NSString *)message subMessage:(nullable NSString *)subMessage persistent:(BOOL)persistent buttonTitle:(nullable NSString *)buttonTitle userActionHandler:(nullable void (^)(id _Nonnull sender))userActionHandler { - - MVMCoreTopAlertObject *topAlertObject = [[MVMCoreTopAlertObject alloc] initWithType:type message:message subMessage:subMessage persistent:persistent buttonTitle:buttonTitle userActionHandler:userActionHandler]; - [self showTopAlertWithObject:topAlertObject]; -} - -- (void)hideTopAlertView { - - MVMCoreTopAlertOperation *currentOperation = [self.topAlertQueue.operations firstObject]; - currentOperation.topAlertObject.persistent = NO; - currentOperation.reAddAfterCancel = NO; - [currentOperation cancel]; -} - -- (BOOL)hasPersistentTopAlertOfType:(nullable NSString *)type { - BOOL hasAlert = NO; - for (MVMCoreTopAlertOperation *operation in self.topAlertQueue.operations) { - if (operation.topAlertObject.persistent && [operation.topAlertObject.type isEqualToString:type]) { - hasAlert = YES; - } - } - return hasAlert; -} - -- (void)hidePersistentTopAlertViewOfType:(nullable NSString *)type { - if (type) { - for (MVMCoreTopAlertOperation *operation in self.topAlertQueue.operations) { - - // Cancel all persistent operations of this type. - if (operation.topAlertObject.persistent && [operation.topAlertObject.type isEqualToString:type]) { - operation.reAddAfterCancel = NO; - [operation cancel]; - } - } - } -} - -- (void)hideTopAlertViewOfType:(nullable NSString *)type { - if (type) { - for (MVMCoreTopAlertOperation *operation in self.topAlertQueue.operations) { - - // Cancel all operations of this type. - if ([operation.topAlertObject.type isEqualToString:type]) { - operation.reAddAfterCancel = NO; - [operation cancel]; - } - } - } -} - -- (void)removeTopAlertForObject:(MVMCoreTopAlertObject *)topAlertObject { - for (MVMCoreTopAlertOperation *operation in self.topAlertQueue.operations) { - // Finds an cancels top alerts associated with the object. - if (operation.topAlertObject == topAlertObject) { - operation.reAddAfterCancel = NO; - [operation cancel]; - } - } -} - -- (void)removeAllTopAlerts { - [self.topAlertQueue cancelAllOperations]; -} - -@end diff --git a/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift b/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift index 0adae2e2..60444ef5 100644 --- a/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift +++ b/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift @@ -6,79 +6,98 @@ // Copyright © 2020 myverizon. All rights reserved. // +import MVMCore -public extension MVMCoreAlertObject { +/// An object with properties for managing the alert. +public struct AlertObject { - static func alertObject(from alertModel: AlertModel, actions: [UIAlertAction]? = nil, 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 - } + /// Greedy alerts dismiss any other alerts and do not allow any other alerts to show until finished. + public var isGreedy = false - 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 - } - } + /// The alert model for the alert to show. + public let alertModel: AlertModel - @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 + 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/MVMCoreAlertObject.h b/MVMCoreUI/Alerts/MVMCoreAlertObject.h deleted file mode 100644 index a3a019b3..00000000 --- a/MVMCoreUI/Alerts/MVMCoreAlertObject.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// MVMCoreAlertObject.h -// myverizon -// -// Created by Scott Pfeil on 11/21/14. -// Copyright (c) 2014 Verizon Wireless. All rights reserved. -// -// An object for keeping track of all alert variables. Easier to pass around. - -#import -@import MVMCore.MVMCoreActionDelegateProtocol; -@import MVMCore.MVMCoreLoadDelegateProtocol; -@import MVMCore.MVMCorePresentationDelegateProtocol; -#import - -@class MVMCoreErrorObject; -@class MVMCoreLoadObject; -@class DelegateObject; - -typedef NS_ENUM(NSInteger, MFAlertType) { - MFAlertTypePopup = 0, - MFAlertTypeField, - MFAlertTypeTop, - MFAlertTypeNone -}; - -typedef void (^TextFieldErrorHandler)(NSArray * _Nonnull fieldErrors); - -@interface MVMCoreAlertObject : NSObject - -@property (nullable, strong, nonatomic) NSString *title; -@property (nullable, copy, nonatomic) NSDictionary *pageJson; -@property (nullable, strong, nonatomic) NSString *message; -@property (nonnull, strong, nonatomic) NSArray *actions; -@property (nonatomic) BOOL isGreedy; -@property (nonatomic) UIAlertControllerStyle alertStyle; -@property (nonatomic) MFAlertType type; -@property (nonatomic) BOOL defaultAction; - -@property (nonnull, strong, nonatomic) NSArray *fieldErrors; -@property (nullable, nonatomic, copy) TextFieldErrorHandler textFieldErrorHandler; - -// Set to be notified of popup style alert events. -@property (nonatomic, weak, nullable) NSObject *alertDelegate; - -// Creates an alert object for an error with the passed in load object response info -+ (nullable instancetype)alertObjectForLoadObject:(nullable MVMCoreLoadObject *)loadObject error:(nullable MVMCoreErrorObject *)error delegateObject:(nullable DelegateObject *)delegateObject; -+ (nullable instancetype)alertObjectForPageType:(nullable NSString *)pageType responseInfo:(nullable NSDictionary *)responseInfo additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -// Initializes a popup style alert object. Look at the alert handler to see what each is used for. -- (nullable instancetype)initPopupAlertWithTitle:(nullable NSString *)title message:(nullable NSString *)message actions:(nonnull NSArray *)actions isGreedy:(BOOL)isGreedy; - -// Initializes a popup style alert object using the error passed in. Message is formatted default style. By defualt uses the Okay button to just dismiss the error. -- (nullable instancetype)initPopupAlertWithError:(nullable MVMCoreErrorObject *)error isGreedy:(BOOL)isGreedy; - -// Same as above but no default actions. They are passed in. -- (nullable instancetype)initPopupAlertWithError:(nullable MVMCoreErrorObject *)error actions:(nonnull NSArray *)actions isGreedy:(BOOL)isGreedy; - -// Returns the alert object made with the page json. If there is not enough data to make, we will set error -+ (nullable instancetype)alertObjectWithPage:(nullable NSDictionary *)page isGreedy:(BOOL)isGreedy additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject error:(MVMCoreErrorObject *_Nullable *_Nullable)error; - -// Will show this alert in it's appropriate type style. -- (void)showAlert; - -@end diff --git a/MVMCoreUI/Alerts/MVMCoreAlertObject.m b/MVMCoreUI/Alerts/MVMCoreAlertObject.m deleted file mode 100644 index b99a5870..00000000 --- a/MVMCoreUI/Alerts/MVMCoreAlertObject.m +++ /dev/null @@ -1,197 +0,0 @@ -// -// MVMCoreAlertObject.m -// myverizon -// -// Created by Scott Pfeil on 11/21/14. -// Copyright (c) 2014 Verizon Wireless. All rights reserved. -// - -#import "MVMCoreAlertObject.h" -#import "MVMCoreAlertHandler.h" -#import "MVMCoreTopAlertObject.h" -@import MVMCore.MVMCoreCache; -@import MVMCore.MVMCoreErrorConstants; -@import MVMCore.MVMCoreErrorObject; -@import MVMCore.MVMCoreLoadObject; -@import MVMCore.MVMCoreGetterUtility; -@import MVMCore.NSDictionary_MFConvenience; -@import MVMCore.MVMCoreHardcodedStringsConstants; -@import MVMCore.MVMCoreJSONConstants; -@import MVMCore.Swift; -#import - -@interface MVMCoreAlertObject () - -@property (strong, nonatomic) MVMCoreLoadObject *loadObject; -@property (nullable, strong, nonatomic) NSString *systemDomain; -@property (nullable, strong, nonatomic) MVMCoreTopAlertObject *topAlertObject; -@end - -@implementation MVMCoreAlertObject - -+ (nullable instancetype)alertObjectForLoadObject:(nullable MVMCoreLoadObject *)loadObject error:(nullable MVMCoreErrorObject *)error delegateObject:(nullable DelegateObject *)delegateObject { - - MVMCoreAlertObject *alert = nil; - if (!error || [ErrorDomainServer isEqualToString:error.domain]) { - alert = [MVMCoreAlertObject alertObjectForPageType:loadObject.pageType responseInfo:loadObject.responseInfoMap additionalData:loadObject.dataForPage delegateObject:delegateObject]; - } else { - alert = [[MVMCoreAlertObject alloc] initPopupAlertWithError:error isGreedy:NO]; - } - - // only if actions are empty, then go inside and set OK as default action - if (alert.type == MFAlertTypePopup && alert.actions.count == 0) { - alert.defaultAction = YES; - alert.actions = @[[UIAlertAction actionWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedOK] style:UIAlertActionStyleDefault handler:nil]]; - } - return alert; -} - -+ (nullable instancetype)alertObjectForPageType:(nullable NSString *)pageType responseInfo:(nullable NSDictionary *)responseInfo additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - MVMCoreUIDelegateObject *alertDelegateObject = nil; - if ([delegateObject isKindOfClass:[MVMCoreUIDelegateObject class]]) { - alertDelegateObject = (MVMCoreUIDelegateObject *)delegateObject; - } - - __block MVMCoreAlertObject *alert = [[MVMCoreAlertObject alloc] init]; - alert.title = [responseInfo string:KeyErrorHeading] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle]; - alert.message = [responseInfo string:KeyUserMessage] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess]; - - NSString *messageStyle = [responseInfo stringForKey:KeyMessageStyle]; - if ([ValueTypeFieldErrors isEqualToString:[responseInfo string:KeyType]]) { - - // field errors. - alert.type = MFAlertTypeField; - alert.fieldErrors = [responseInfo array:ValueTypeFieldErrors]; - } else { - - // Check for top alert (persistent or regular). - if ([messageStyle isEqualToString:ValueMessageStyleTopPersistent] || [messageStyle isEqualToString:ValueMessageStyleTop]) { - - alert.topAlertObject = [[MVMCoreTopAlertObject alloc] initWithResponseInfo:responseInfo]; - alert.topAlertObject.delegate = alertDelegateObject.topAlertDelegate; - alert.topAlertObject.pageType = pageType; - alert.type = MFAlertTypeTop; - } else if ([messageStyle isEqualToString:ValueMessageStylePopup]) { - - // Perform a popup. - alert.type = MFAlertTypePopup; - alert.alertStyle = UIAlertControllerStyleAlert; - - // Check if we have a popup driven by page object (otherwise by default it will just use response info title message with an OK button). - NSString *pageTypeForPopup = [responseInfo stringForKey:@"popupPageType"]; - [[MVMCoreCache sharedCache] fetchJSONForPageType:pageTypeForPopup queue:nil waitUntilFinished:YES completionHandler:^(NSDictionary * _Nullable jsonDictionary) { - - MVMCoreErrorObject *error = nil; - MVMCoreAlertObject *popupAlert = [MVMCoreAlertObject alertObjectWithPage:jsonDictionary isGreedy:NO additionalData:additionalData delegateObject:delegateObject error:&error]; - if (error) { - - // Error, popup page not found for page type. - popupAlert = [[MVMCoreAlertObject alloc] initPopupAlertWithError:error isGreedy:NO]; - } - - if (popupAlert) { - alert = popupAlert; - } - }]; - } else if (messageStyle.length == 0 && pageType) { - - // No message style! - alert.type = MFAlertTypeNone; - } else { - - // Default to popup - alert.type = MFAlertTypePopup; - alert.alertStyle = UIAlertControllerStyleAlert; - } - } - alert.alertDelegate = alertDelegateObject.alertDelegate; - return alert; -} - -- (nullable instancetype)initPopupAlertWithTitle:(nullable NSString *)title message:(nullable NSString *)message actions:(nonnull NSArray *)actions isGreedy:(BOOL)isGreedy { - if (self = [super init]) { - self.title = title; - self.message = message; - self.actions = actions; - self.isGreedy = isGreedy; - self.type = MFAlertTypePopup; - self.alertStyle = UIAlertControllerStyleAlert; - } - return self; -} - -- (nullable instancetype)initPopupAlertWithError:(nullable MVMCoreErrorObject *)error isGreedy:(BOOL)isGreedy { - - if (self = [super init]) { - self.title = error.title; - self.message = [NSString stringWithFormat:@"%@ (%@)",error.messageToDisplay,[error stringErrorCode]]; - self.actions = @[[UIAlertAction actionWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedOK] style:UIAlertActionStyleDefault handler:nil]]; - self.defaultAction = YES; - self.isGreedy = isGreedy; - self.type = MFAlertTypePopup; - self.alertStyle = UIAlertControllerStyleAlert; - } - return self; -} - -- (nullable instancetype)initPopupAlertWithError:(nullable MVMCoreErrorObject *)error actions:(nonnull NSArray *)actions isGreedy:(BOOL)isGreedy { - - if (self = [super init]) { - self.title = error.title; - self.message = [NSString stringWithFormat:@"%@ (%@)",error.messageToDisplay,[error stringErrorCode]]; - self.actions = actions; - self.isGreedy = isGreedy; - self.type = MFAlertTypePopup; - self.alertStyle = UIAlertControllerStyleAlert; - } - return self; -} - -+ (nullable instancetype)alertObjectWithPage:(nullable NSDictionary *)page isGreedy:(BOOL)isGreedy additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject error:(MVMCoreErrorObject *_Nullable *_Nullable)error { - - MVMCoreAlertObject *alert = [[MVMCoreAlertObject alloc] init]; - alert.title = [page string:KeyTitle]; - alert.pageJson = page; - alert.message = [page string:KeyMessage]; - alert.isGreedy = isGreedy; - alert.type = MFAlertTypePopup; - alert.alertStyle = UIAlertControllerStyleAlert; - - NSArray *actions = [page array:KeyLinks]; - NSMutableArray *actionsForAlert = [NSMutableArray array]; - for (NSDictionary *actionMap in actions) { - [actionsForAlert addObject:[UIAlertAction actionWithTitle:[actionMap stringForKey:KeyTitle] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - [[MVMCoreUIActionHandler sharedActionHandler] handleActionWithDictionary:actionMap additionalData:additionalData delegateObject:delegateObject]; - }]]; - } - alert.actions = actionsForAlert; - - if ((alert.title.length > 0 || alert.message.length > 0) && alert.actions.count > 0) { - return alert; - } else { - if (error) { - id delegate = [delegateObject isKindOfClass:[MVMCoreUIDelegateObject class]] ? ((MVMCoreUIDelegateObject *)delegateObject).alertDelegate : nil; - *error = [[MVMCoreErrorObject alloc] initWithTitle:nil message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess] code:ErrorCodePopupFailed domain:ErrorDomainNative location:[NSString stringWithFormat:@"%@_Popup_pageType:%@",NSStringFromClass([delegate class]),[page stringForKey:KeyPageType]]]; - } - return nil; - } -} - -- (void)showAlert { - - switch (self.type) { - case MFAlertTypeField: - self.textFieldErrorHandler(self.fieldErrors); - break; - case MFAlertTypeTop: - [[MVMCoreAlertHandler sharedAlertHandler] showTopAlertWithObject:self.topAlertObject]; - break; - case MFAlertTypePopup: - [[MVMCoreAlertHandler sharedAlertHandler] showAlertWithAlertObject:self]; - break; - default: - break; - } -} - -@end diff --git a/MVMCoreUI/Alerts/MVMCoreAlertOperation.h b/MVMCoreUI/Alerts/MVMCoreAlertOperation.h deleted file mode 100644 index 2347acd7..00000000 --- a/MVMCoreUI/Alerts/MVMCoreAlertOperation.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// MVMCoreAlertOperation.h -// myverizon -// -// Created by Scott Pfeil on 9/28/15. -// Copyright © 2015 Verizon Wireless. All rights reserved. -// -// Operation for handling an alert. Should NOT be on the main queue. - -#import -#import -@import MVMCore.MVMCoreOperation; -#import - -@interface MVMCoreAlertOperation : MVMCoreOperation - -/// Alert controller to be displayed. -@property (nonnull, readonly) UIAlertController *currentAlertView; - -/// If this operation is temporarily paused. -@property (readonly, getter=isPaused) BOOL paused; - -/// If this alert is a greedy alert (See MVMCoreAlertHandler). -@property (readonly, getter=isGreedy) BOOL greedy; - -/// The alert delegate if needed. -@property (readonly, nullable, nonatomic, weak) NSObject *alertDelegate; - -/// Initializes the operation with the alert to display and if it is greedy or not. -- (nullable instancetype)initWithAlert:(nonnull UIAlertController *)alert isGreedy:(BOOL)isGreedy; -- (nullable instancetype)initWithAlert:(nonnull UIAlertController *)alert isGreedy:(BOOL)isGreedy alertDelegate:(nullable id )alertDelegate; - -/// Pauses the operation. Temporarily removes any alert. -- (void)pause; - -/// Unpauses the operation, resuming any alert. -- (void)unpause; - -@end diff --git a/MVMCoreUI/Alerts/MVMCoreAlertOperation.m b/MVMCoreUI/Alerts/MVMCoreAlertOperation.m deleted file mode 100644 index b39a3267..00000000 --- a/MVMCoreUI/Alerts/MVMCoreAlertOperation.m +++ /dev/null @@ -1,242 +0,0 @@ -// -// MVMCoreAlertOperation.m -// myverizon -// -// Created by Scott Pfeil on 9/28/15. -// Copyright © 2015 Verizon Wireless. All rights reserved. -// - -#import "MVMCoreAlertOperation.h" -#import -@import MVMCore.MVMCoreAlertController; -@import MVMCore.MVMCoreNavigationHandler; - -@interface MVMCoreAlertOperation () { - __block BOOL _paused; - __block BOOL _displayed; -} - -@property (readwrite, getter=isPaused) BOOL paused; - -@property (readwrite, getter=isGreedy) BOOL greedy; - -@property (readwrite, getter=isDisplayed) BOOL displayed; - -@property (readwrite, nullable, nonatomic, weak) NSObject *alertDelegate; - -// The currently displayed alert view. -@property (nullable, strong, nonatomic) UIAlertController *currentAlertView; - -// A boolean to keep track of if we alreadys signed up to observe. -@property (assign, nonatomic) BOOL alertBeingObserved; - -// For thread safety -@property (strong, nonatomic) dispatch_queue_t pausedQueue; -@property (strong, nonatomic) dispatch_queue_t displayedQueue; - -// Dismisses the alert. -- (void)dismissAlertView; - -// Begins observing for when the alert is dismissed. -- (void)observeForCurrentAlertViewDismissal; - -// Stops observing for when the alert is dismissed. -- (void)stopObservingAlertView; - -@end - -@implementation MVMCoreAlertOperation - -// The context for kvo -static void * XXContext = &XXContext; - -- (instancetype)init { - - self = [super init]; - if (self) { - self.pausedQueue = dispatch_queue_create("paused", DISPATCH_QUEUE_CONCURRENT); - self.displayedQueue = dispatch_queue_create("displayed", DISPATCH_QUEUE_CONCURRENT); - } - return self; -} - -- (nullable instancetype)initWithAlert:(nonnull UIAlertController *)alert isGreedy:(BOOL)isGreedy { - - if (self = [self init]) { - self.currentAlertView = alert; - self.greedy = isGreedy; - } - return self; -} - -- (nullable instancetype)initWithAlert:(nonnull UIAlertController *)alert isGreedy:(BOOL)isGreedy alertDelegate:(nullable NSObject *)alertDelegate { - if (self = [self initWithAlert:alert isGreedy:isGreedy]) { - self.alertDelegate = alertDelegate; - } - return self; -} - -- (void)dealloc { - [self stopObservingAlertView]; -} - -- (BOOL)isPaused { - __block BOOL isPaused; - dispatch_sync(self.pausedQueue, ^{ - isPaused = self->_paused; - }); - return isPaused; -} - -- (void)setPaused:(BOOL)paused { - dispatch_barrier_async(self.pausedQueue, ^{ - self->_paused = paused; - }); -} - -- (BOOL)isDisplayed { - __block BOOL isDisplayed; - dispatch_sync(self.displayedQueue, ^{ - isDisplayed = self->_displayed; - }); - return isDisplayed; -} - -- (void)setDisplayed:(BOOL)displayed { - dispatch_barrier_async(self.displayedQueue, ^{ - self->_displayed = displayed; - }); -} - -- (void)main { - - // Always check for cancellation before launching the task. - if ([self checkAndHandleForCancellation]) { - return; - } - - // Display alert only if alerts aren't supressed. - if (![[MVMCoreAlertHandler sharedAlertHandler] mfAlertsSupressed] && self.currentAlertView) { - - // Observe for when it is removed. - [self observeForCurrentAlertViewDismissal]; - - // Adds the presentation to the animation queue. - [[MVMCoreNavigationHandler sharedNavigationHandler] presentViewController:self.currentAlertView animated:YES delegate:nil completionHandler:^{ - - // We finished but it was not displayed yet. It's possible that it was cancelled. Finish this task - if (!self.isDisplayed) { - [self markAsFinished]; - } else if (self.isCancelled) { - [self dismissAlertView]; - } - }]; - } -} - -- (void)cancel { - [super cancel]; - - // Notify delegate that the alert is cancelled. - if ([self.alertDelegate respondsToSelector:@selector(alertCancelled:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self.alertDelegate alertCancelled:self.currentAlertView]; - }); - } - - [self dismissAlertView]; -} - -- (void)dismissAlertView { - - if (self.isDisplayed) { - - // Dismisses. - [[MVMCoreNavigationHandler sharedNavigationHandler] dismissViewController:self.currentAlertView animated:YES]; - } -} - -- (void)pause { - [self willChangeValueForKey:@"isPaused"]; - self.paused = YES; - [self didChangeValueForKey:@"isPaused"]; - - // Notify delegate of pause. - if ([self.alertDelegate respondsToSelector:@selector(alertPaused:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self.alertDelegate alertPaused:self.currentAlertView]; - }); - } - - // Dismiss until unpaused. - [self dismissAlertView]; -} - -- (void)unpause { - [self willChangeValueForKey:@"isPaused"]; - self.paused = NO; - [self didChangeValueForKey:@"isPaused"]; - - // Notify delegate of unpause. - if ([self.alertDelegate respondsToSelector:@selector(alertUnpaused:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self.alertDelegate alertUnpaused:self.currentAlertView]; - }); - } - - // Show alert... - if (self.currentAlertView) { - [self start]; - } -} - -#pragma mark - Observer Functions - -- (void)observeForCurrentAlertViewDismissal { - if (!self.alertBeingObserved && ![[MVMCoreAlertHandler sharedAlertHandler] mfAlertsSupressed] && self.currentAlertView && [self.currentAlertView isKindOfClass:[UIAlertController class]]) { - self.alertBeingObserved = YES; - [self.currentAlertView addObserver:self forKeyPath:@"visible" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:XXContext]; - } -} - -- (void)stopObservingAlertView { - if (self.alertBeingObserved) { - [self.currentAlertView removeObserver:self forKeyPath:@"visible" context:XXContext]; - self.alertBeingObserved = NO; - } -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if (context == XXContext && [keyPath isEqualToString:@"visible"]) { - if (![object isVisible]) { - - self.displayed = NO; - - // Notify delegate that the alert is dismissed. - if ([self.alertDelegate respondsToSelector:@selector(alertDismissed:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self.alertDelegate alertDismissed:self.currentAlertView]; - }); - } - - // Is visible was set to NO, meaning that the alertview is no longer visible. - if (!self.isPaused) { - [self stopObservingAlertView]; - self.currentAlertView = nil; - [self markAsFinished]; - } - } else { - - self.displayed = YES; - - // Notify delegate that the alert is shown. - if ([self.alertDelegate respondsToSelector:@selector(alertShown:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self.alertDelegate alertShown:self.currentAlertView]; - }); - } - } - } -} - -@end diff --git a/MVMCoreUI/Alerts/TopNotificationHandler.swift b/MVMCoreUI/Alerts/TopNotificationHandler.swift new file mode 100644 index 00000000..82fe4889 --- /dev/null +++ b/MVMCoreUI/Alerts/TopNotificationHandler.swift @@ -0,0 +1,230 @@ +// +// TopNotificationHandler.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/11/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import MVMCore + +public class TopNotificationHandler { + + /// The operation queue of alert operations. + private var queue = OperationQueue() + + /// Returns the action handler stored in the CoreUIObject + public static func shared() -> Self { + return MVMCoreActionUtility.fatalClassCheck(object: CoreUIObject.sharedInstance()?.topNotificationHandler) + } + + init() { + registerWithNotificationCenter() + registerForPageChanges() + } + + // MARK: - JSON Handling + + /// Registers with the notification center to know when json is updated. + private func registerWithNotificationCenter() { + NotificationCenter.default.addObserver(self, selector: #selector(responseJSONUpdated(notification:)), name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil) + } + + /// Registers to know when pages change. + private func registerForPageChanges() { + MVMCoreNavigationHandler.shared()?.addDelegate(self) + } + + private func getDelegateObject() -> MVMCoreUIDelegateObject? { + // TODO: Top alert view is current delegate. Should move to current view controller eventually? + guard let alertView = MVMCoreUISplitViewController.main()?.topAlertView else { return nil } + return MVMCoreUIDelegateObject.create(withDelegateForAll: alertView) + } + + /// Checks for new top alert json + @objc private func responseJSONUpdated(notification: Notification) { + guard let loadObject = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject) else { return } + + // Dismiss any top alerts that server wants us to dismiss/ + if let disableType = loadObject.responseInfoMap?.optionalStringForKey("disableType") { + TopNotificationHandler.shared().hideTopAlertView(of: disableType) + } + + // Show any new top alert. + guard let responseJSON = loadObject.responseJSON, + let json = responseJSON.optionalDictionaryForKey(KeyTopAlert) else { return } + showTopNotification(with: json) + } + + /// Decodes the json into a TopNotificationModel + public func decodeTopNotification(with json: [AnyHashable: Any], delegateObject: MVMCoreUIDelegateObject?) -> TopNotificationModel? { + do { + return try TopNotificationModel.decode(json: json, delegateObject: delegateObject) + } catch { + if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "\(self)") { + MVMCoreUILoggingHandler.addError(toLog: errorObject) + } + return nil + } + } + + // MARK: - Operation Handling + + private func add(operation: MVMCoreTopAlertOperation) { + operation.completionBlock = { [weak self] in + // If the alert was cancelled to show another with higher priority, re-add to the operation when cancelled to the queue. + if operation.reAddAfterCancel { + let newOperation: MVMCoreTopAlertOperation = operation.copy() as! MVMCoreTopAlertOperation + newOperation.reAddAfterCancel = false + self?.add(operation: newOperation) + } + operation.completionBlock = nil + } + + let currentPageType = (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType + operation.updateDisplayable(byPageType: currentPageType) + + queue.addOperation(operation) + reevaluteQueue() + } + + /// Checks for existing top alert object of same type and updates it. Only happens for molecular top alerts. Returns true if we updated. + private func checkAndUpdateExisting(with topAlertObject: MVMCoreTopAlertObject) -> Bool { + for case let operation as MVMCoreTopAlertOperation in queue.operations { + guard topAlertObject.json != nil, + operation.topAlertObject.type == topAlertObject.type else { continue } + operation.update(with: topAlertObject) + let pageType = (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType + operation.updateDisplayable(byPageType: pageType) + reevaluteQueue() + return true + } + return false + } + + /// Re-evaluates the queue operations + private func reevaluteQueue() { + var highestReadyOperation: MVMCoreTopAlertOperation? + var executingOperation: MVMCoreTopAlertOperation? + for case let operation as MVMCoreTopAlertOperation in queue.operations { + guard !operation.isCancelled, + !operation.isFinished else { continue } + if operation.isReady, + highestReadyOperation == nil || operation.queuePriority.rawValue > highestReadyOperation!.queuePriority.rawValue { + highestReadyOperation = operation + } + if operation.isExecuting { + executingOperation = operation + } + } + guard let currentOperation = executingOperation else { return } + + // Cancel the executing operation if it is no longer ready to run. Re-add for later if it is persistent. + guard currentOperation.isReady else { + currentOperation.reAddAfterCancel = currentOperation.topAlertObject.persistent + currentOperation.cancel() + return + } + + // If the highest priority operation is not executing, and the executing operation is persistent, cancel it. + if let newOperation = highestReadyOperation, + currentOperation != newOperation, + currentOperation.topAlertObject.persistent { + currentOperation.reAddAfterCancel = true + currentOperation.cancel() + } + } + + // MARK: - Show and hide + + /// Shows the top alert with the json. + func showTopNotification(with json: [AnyHashable: Any]) { + guard let model = decodeTopNotification(with: json, delegateObject: getDelegateObject()) else { return } + showTopNotification(with: model) + } + + /// Shows the top notification with the model. + func showTopNotification(with model: TopNotificationModel) { + let object = model.createTopAlertObject() + guard !checkAndUpdateExisting(with: object), + let operation = MVMCoreTopAlertOperation(topAlertObject: object) else { return } + TopNotificationHandler.shared().add(operation: operation) + } + + /// Show the top alert with the legacy object. + public func showTopAlert(with topAlertObject: MVMCoreTopAlertObject) { + let alertOperation = MVMCoreTopAlertOperation(topAlertObject: topAlertObject)! + add(operation: alertOperation) + } + + public func showTopAlertError(with message: String) { + let topAlertObject = MVMCoreTopAlertObject(type: ValueTypeError, message: message)! + showTopAlert(with: topAlertObject) + } + + public func showTopAlertConfirmation(with message: String) { + let topAlertObject = MVMCoreTopAlertObject(type: ValueTypeSuccess, message: message)! + showTopAlert(with: topAlertObject) + } + + /// Cancel the current top alert view. + public func hideTopAlertView() { + guard let currentOperation = queue.operations.first(where: { operation in + return operation.isExecuting + }) as? MVMCoreTopAlertOperation else { return } + currentOperation.topAlertObject.persistent = false + currentOperation.reAddAfterCancel = false + currentOperation.cancel() + } + + /// Cancel all operations of this type. + public func hideTopAlertView(of type: String) { + for operation in queue.operations { + guard let operation = operation as? MVMCoreTopAlertOperation, + operation.topAlertObject.type == type else { continue } + operation.reAddAfterCancel = false + 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) { + for operation in queue.operations { + guard let operation = operation as? MVMCoreTopAlertOperation, + operation.topAlertObject.persistent, + operation.topAlertObject.type == type else { continue } + operation.reAddAfterCancel = false + operation.cancel() + } + } + + public func removeAllTopAlerts() { + queue.cancelAllOperations() + } +} + +extension TopNotificationHandler: MVMCorePresentationDelegateProtocol { + // Update displayable for each top alert operation when page type changes, in top queue priority order. + public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { + guard queue.operations.count > 0 else { return } + let viewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) + guard viewController == MVMCoreUISplitViewController.main()?.getCurrentViewController() else { return } + let pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType + queue.operations.compactMap { + $0 as? MVMCoreTopAlertOperation + }.sorted { + $0.queuePriority.rawValue > $1.queuePriority.rawValue + }.forEach { + $0.updateDisplayable(byPageType: pageType) + } + reevaluteQueue() + } +} diff --git a/MVMCoreUI/Atomic/Actions/ActionAlertHandler.swift b/MVMCoreUI/Atomic/Actions/ActionAlertHandler.swift index f3004f71..c1febb95 100644 --- a/MVMCoreUI/Atomic/Actions/ActionAlertHandler.swift +++ b/MVMCoreUI/Atomic/Actions/ActionAlertHandler.swift @@ -10,29 +10,12 @@ import Foundation import MVMCore /// Shows an alert using the model. -open class ActionAlertHandler: MVMCoreJSONActionHandlerProtocol { +open class ActionAlertHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { - var error: MVMCoreErrorObject? = nil - guard let alertObject = MVMCoreAlertObject.alertObjectWith(action: JSON, additionalData: additionalData, delegateObject: delegateObject, error: &error) else { - throw MVMCoreError.errorObject(error!) - } - (delegateObject?.actionDelegate as? MVMCoreUIActionDelegateProtocol)?.willShowPopup(with: alertObject, alertJson: JSON) - _ = await MainActor.run { - MVMCoreAlertHandler.shared()?.showAlert(with: alertObject) - } - } - - open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + public func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionAlertModel else { return } - var error: MVMCoreErrorObject? = nil - guard let alertObject = MVMCoreAlertObject.alertObject(from: model.alert, additionalData: additionalData, delegateObject: delegateObject, error: &error) else { - throw MVMCoreError.errorObject(error!) - } - (delegateObject?.actionDelegate as? MVMCoreUIActionDelegateProtocol)?.willShowPopup(with: alertObject, alertJson: try MVMCoreActionHandler.convertActionToJSON(model)) - _ = await MainActor.run { - MVMCoreAlertHandler.shared()?.showAlert(with: alertObject) - } + let alertObject = AlertObject(alertModel: model.alert, alertDelegate: (delegateObject as? MVMCoreUIDelegateObject)?.alertDelegate) + _ = await AlertHandler.shared().queueAlertToShow(with: alertObject) } } diff --git a/MVMCoreUI/Atomic/Actions/ActionPopupHandler.swift b/MVMCoreUI/Atomic/Actions/ActionPopupHandler.swift index 554a000a..6749838d 100644 --- a/MVMCoreUI/Atomic/Actions/ActionPopupHandler.swift +++ b/MVMCoreUI/Atomic/Actions/ActionPopupHandler.swift @@ -13,21 +13,109 @@ import MVMCore open class ActionPopupHandler: MVMCoreActionHandlerProtocol { required public init() {} + public enum Error: MVMError, CustomStringConvertible { + case jsonNotFound + + public var description: String { + switch self { + case ActionPopupHandler.Error.jsonNotFound: + return "JSON for popup not found." + } + } + + public var errorCode: Int { + ErrorCode.popupFailed.rawValue + } + } + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionPopupModel else { return } - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in MVMCoreCache.shared()?.fetchJSON(forPageType: model.pageType, queue: nil, waitUntilFinished: true, completionHandler: { json in - var error: MVMCoreErrorObject? = nil - guard let alertObject = MVMCoreAlertObject(page: json, isGreedy: false, additionalData: additionalData, delegateObject: delegateObject, error: &error) else { - continuation.resume(throwing: MVMCoreError.errorObject(error!)) + guard let page = json else { + continuation.resume(throwing: ActionPopupHandler.Error.jsonNotFound) return } - (delegateObject?.actionDelegate as? MVMCoreUIActionDelegateProtocol)?.willShowPopup(with: alertObject, alertJson: json!) - Task { @MainActor in - MVMCoreAlertHandler.shared()?.showAlert(with: alertObject) - continuation.resume() + Task(priority: .userInitiated) { + do { + try await self.showAlertObjectWithPage(page, isGreedy: false, additionalData: nil, delegateObject: delegateObject) + continuation.resume() + } catch { + continuation.resume(throwing: error) + } } }) } } + + // TODO: Find a way to wait for the actual alert to show? or finish? + /// Shows the alert using the legacy page format. + open func showAlertObjectWithPage(_ page: [AnyHashable: Any], isGreedy: Bool, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { + let alertObject = try alertObjectWithPage(page, isGreedy: false, additionalData: additionalData, delegateObject: delegateObject) + _ = await AlertHandler.shared().queueAlertToShow(with: alertObject) + } + + open func alertObjectWithPage(_ page: [AnyHashable: Any], isGreedy: Bool, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) throws -> AlertObject { + let data = try JSONSerialization.data(withJSONObject: page) + let decoder = JSONDecoder.create(with: delegateObject) + let popupModel = try decoder.decode(LegacyAlertModel.self, from: data) + let alert = AlertModel(title: popupModel.title ?? "", message: popupModel.message ?? "", actions: popupModel.buttonActions) + return AlertObject(alertModel: alert, isGreedy: isGreedy) + } +} + +private protocol ActionWithTitle: ActionModelProtocol { + var title: String { get set } +} + +private struct LegacyAlertModel: Codable { + var title: String? + var message: String? + var buttonActions: [UIAlertAction] + private var buttonsForEncode: JSONValueArray + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case title + case message + case Links + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + struct TitleObject: Codable { + var title: String + } + + public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + buttonsForEncode = try typeContainer.decode(JSONValueArray.self, forKey: .Links) + title = try typeContainer.decode(String.self, forKey: .title) + message = try typeContainer.decode(String.self, forKey: .message) + if title?.count == 0 && message?.count == 0 { + throw ModelRegistry.Error.decoderOther(message: "Popups must have either a title or a message") + } + + let buttonTitles = try typeContainer.decode([TitleObject].self, forKey: .Links).map({ object in + return object.title + }) + let delegateObject = try decoder.get() + self.buttonActions = (try typeContainer.decodeModels(codingKey: .Links) as [ActionModelProtocol]).enumerated().map { (index, action) in + return AlertButtonModel(buttonTitles[index], action) + }.map({ alertButtonModel in + return alertButtonModel.generateAction(delegateObject: delegateObject) + }) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(title, forKey: .title) + try container.encodeIfPresent(message, forKey: .message) + try container.encode(buttonsForEncode, forKey: .Links) + } } diff --git a/MVMCoreUI/Atomic/Actions/ActionTopAlertHandler.swift b/MVMCoreUI/Atomic/Actions/ActionTopAlertHandler.swift index 49ca8561..82fc0a67 100644 --- a/MVMCoreUI/Atomic/Actions/ActionTopAlertHandler.swift +++ b/MVMCoreUI/Atomic/Actions/ActionTopAlertHandler.swift @@ -22,12 +22,10 @@ open class ActionTopAlertHandler: MVMCoreActionHandlerProtocol { return } - var alertObject = MVMCoreAlertObject(forPageType: model.pageType, responseInfo: responseInfo, additionalData: additionalData, delegateObject: delegateObject) - if let object = alertObject, - let closure = (delegateObject?.actionDelegate as? MVMCoreUIActionDelegateProtocol)?.willShowTopAlert { - alertObject = closure(object, json!) - } - alertObject?.showAlert() + let topAlertObject = MVMCoreTopAlertObject(responseInfo: responseInfo)! + topAlertObject.delegate = (delegateObject as? MVMCoreUIDelegateObject)?.topAlertDelegate + topAlertObject.pageType = model.pageType + TopNotificationHandler.shared().showTopAlert(with: topAlertObject) continuation.resume() }) } diff --git a/MVMCoreUI/Atomic/Actions/ActionTopNotificationHandler.swift b/MVMCoreUI/Atomic/Actions/ActionTopNotificationHandler.swift index 858c1d09..a92158f7 100644 --- a/MVMCoreUI/Atomic/Actions/ActionTopNotificationHandler.swift +++ b/MVMCoreUI/Atomic/Actions/ActionTopNotificationHandler.swift @@ -15,6 +15,6 @@ open class ActionTopNotificationHandler: MVMCoreActionHandlerProtocol { open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionTopNotificationModel else { return } - await MVMCoreUITopAlertView.sharedGlobal()?.showTopAlert(with: model.topNotification) + TopNotificationHandler.shared().showTopNotification(with: model.topNotification) } } diff --git a/MVMCoreUI/Atomic/Actions/AlertModel.swift b/MVMCoreUI/Atomic/Actions/AlertModel.swift index 6825898c..4ee37ab9 100644 --- a/MVMCoreUI/Atomic/Actions/AlertModel.swift +++ b/MVMCoreUI/Atomic/Actions/AlertModel.swift @@ -9,8 +9,7 @@ import UIKit import MVMCore - -public class AlertButtonModel: Codable { +public struct AlertButtonModel: Codable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -43,7 +42,7 @@ public class AlertButtonModel: Codable { // MARK: - Codec //-------------------------------------------------- - required public init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) title = try typeContainer.decode(String.self, forKey: .title) @@ -53,7 +52,7 @@ public class AlertButtonModel: Codable { action = try typeContainer.decodeModel(codingKey: .action) } - open func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(title, forKey: .title) try container.encode(style.rawValueString, forKey: .style) @@ -61,7 +60,7 @@ public class AlertButtonModel: Codable { } } -public class AlertModel: Codable { +public struct AlertModel: Codable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -69,17 +68,27 @@ public class AlertModel: Codable { public var title: String public var message: String public var style: UIAlertController.Style = .alert - public var alertActions: [AlertButtonModel] + public var actions: [UIAlertAction] + private var alertActions: [AlertButtonModel]? public var analyticsData: JSONValueDictionary? //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public init(title: String, message: String, alertActions: [AlertButtonModel], style: UIAlertController.Style = .alert) { + public init(title: String, message: String, actions: [UIAlertAction], style: UIAlertController.Style = .alert) { self.title = title self.message = message - self.alertActions = alertActions + self.actions = actions + self.style = style + } + + public init(title: String, message: String, buttonModels: [AlertButtonModel], style: UIAlertController.Style = .alert, delegateObject: DelegateObject?) { + self.title = title + self.message = message + actions = buttonModels.map({ alertButtonModel in + return alertButtonModel.generateAction(delegateObject: delegateObject) + }) self.style = style } @@ -99,11 +108,15 @@ public class AlertModel: Codable { // MARK: - Codec //-------------------------------------------------- - required public init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + let delegateObject = try decoder.get() title = try typeContainer.decode(String.self, forKey: .title) message = try typeContainer.decode(String.self, forKey: .message) alertActions = try typeContainer.decode([AlertButtonModel].self, forKey: .alertActions) + actions = alertActions!.map({ alertButtonModel in + return alertButtonModel.generateAction(delegateObject: delegateObject) + }) analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData) if let style = try typeContainer.decodeIfPresent(String.self, forKey: .style) { @@ -111,12 +124,24 @@ public class AlertModel: Codable { } } - open func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(title, forKey: .title) try container.encode(message, forKey: .message) - try container.encode(alertActions, forKey: .alertActions) + try container.encodeIfPresent(alertActions, forKey: .alertActions) try container.encode(style.rawValueString, forKey: .style) try container.encodeIfPresent(analyticsData, forKey: .analyticsData) } } + +public extension AlertButtonModel { + func generateAction(with additionalData: [AnyHashable: Any]? = nil, delegateObject: DelegateObject? = nil, additionalHandling: ((AlertButtonModel, UIAlertAction)->())? = nil) -> UIAlertAction { + let alertAction = UIAlertAction(title: title, style: style) { action in + Task(priority: .userInitiated) { + try? await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction(with: self.action, additionalData: additionalData, delegateObject: delegateObject) + additionalHandling?(self, action) + } + } + return alertAction + } +} diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index f148c1cb..3c3ceefa 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -496,7 +496,7 @@ import MVMCore errorObject.silentError = false } - MVMCoreUIActionHandler.shared()?.defaultHandleActionError(errorObject, additionalData: additionalData) + MVMCoreUIActionHandler.shared()?.defaultHandleActionError(errorObject, additionalData: additionalData, delegateObject: delegateObject) } //-------------------------------------------------- diff --git a/MVMCoreUI/MVMCoreUI.h b/MVMCoreUI/MVMCoreUI.h index 37083fd0..f5a0c6fc 100644 --- a/MVMCoreUI/MVMCoreUI.h +++ b/MVMCoreUI/MVMCoreUI.h @@ -23,9 +23,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; #import // Alert Handling -#import -#import -#import #import #pragma mark - TopAlert diff --git a/MVMCoreUI/OtherHandlers/CoreUIObject.swift b/MVMCoreUI/OtherHandlers/CoreUIObject.swift index d44b49ac..57683bc4 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIObject.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIObject.swift @@ -7,9 +7,12 @@ // import UIKit +import MVMCore @objcMembers open class CoreUIObject: MVMCoreObject { public var globalTopAlertDelegate: MVMCoreGlobalTopAlertDelegateProtocol? + public var alertHandler: AlertHandler? + public var topNotificationHandler: TopNotificationHandler? open override func defaultInitialSetup() { CoreUIModelMapping.registerObjects() @@ -20,5 +23,7 @@ import UIKit session = MVMCoreUISession() viewControllerMapping = MVMCoreUIViewControllerMappingObject() loggingDelegate = MVMCoreUILoggingHandler() + alertHandler = AlertHandler() + topNotificationHandler = TopNotificationHandler() } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIActionDelegateProtocol.h b/MVMCoreUI/OtherHandlers/MVMCoreUIActionDelegateProtocol.h index 41533f34..c2f2a4b9 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIActionDelegateProtocol.h +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIActionDelegateProtocol.h @@ -13,7 +13,4 @@ // Gives the delegate a chance to alter the alert object - (void)willShowPopupWithAlertObject:(nonnull MVMCoreAlertObject *)alertObject alertJson:(nonnull NSDictionary *)alertJson; -// Gives the delegate a chance to alter the alert object -- (nullable MVMCoreAlertObject *)willShowTopAlertWithAlertObject:(nonnull MVMCoreAlertObject *)alertObject alertJson:(nonnull NSDictionary *)alertJson; - @end diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.swift b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.swift index 3de8fdde..b2c3d42c 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.swift +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.swift @@ -38,13 +38,13 @@ import SafariServices } /// Logs the error and shows a popup if the error is not silent. - open override func defaultHandleActionError(_ error: MVMCoreErrorObject, additionalData: [AnyHashable : Any]?) { + open override func defaultHandleActionError(_ error: MVMCoreErrorObject, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject? = nil) { super.defaultHandleActionError(error, additionalData: additionalData) guard !error.silentError else { return } error.silentError = true // Silence if this error is triggered again. (Legacy action handler flow.) Task(priority: .userInitiated) { @MainActor in - let alertObject = MVMCoreAlertObject.init(popupAlertWithError: error, isGreedy: false)! - MVMCoreAlertHandler.shared()?.showAlert(with: alertObject) + let alertAction = ActionAlertModel(alert: AlertModel(title: error.title ?? "", message: "\(String(describing: error.messageToDisplay)) (\(error.stringErrorCode()))", buttonModels: [AlertButtonModel(MVMCoreGetterUtility.hardcodedString(withKey: HardcodedOK) ?? "OK", ActionCancelModel())], delegateObject: delegateObject)) + try? await MVMCoreUIActionHandler.shared()?.handleAction(with: alertAction, additionalData: additionalData, delegateObject: delegateObject) } } diff --git a/MVMCoreUI/TopAlert/MVMCoreTopAlertObject.m b/MVMCoreUI/TopAlert/MVMCoreTopAlertObject.m index 9286737a..45f5ae35 100644 --- a/MVMCoreUI/TopAlert/MVMCoreTopAlertObject.m +++ b/MVMCoreUI/TopAlert/MVMCoreTopAlertObject.m @@ -10,7 +10,6 @@ @import MVMCore.NSDictionary_MFConvenience; @import MVMCore.MVMCoreGetterUtility; @import MVMCore.MVMCoreJSONConstants; -#import "MVMCoreAlertHandler.h" NSUInteger const TopAlertDismissTime = 5; diff --git a/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m b/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m index f0011719..acdf3b1c 100644 --- a/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m +++ b/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m @@ -8,7 +8,6 @@ #import "MVMCoreTopAlertOperation.h" #import "MVMCoreTopAlertObject.h" -#import "MVMCoreAlertHandler.h" #import #import @@ -166,36 +165,32 @@ // Do nothing if paused if (!self.isPaused) { - - // Display alert only if alerts aren't supressed. - if (![[MVMCoreAlertHandler sharedAlertHandler] mfAlertsSupressed]) { - // Show - if (![[CoreUIObject sharedInstance].globalTopAlertDelegate respondsToSelector:@selector(getTopAlertView)]) { + // Show + if (![[CoreUIObject sharedInstance].globalTopAlertDelegate respondsToSelector:@selector(getTopAlertView)]) { - // Needs to be a top alert view.... - [self markAsFinished]; - } else { - UIView *topAlertView = [[CoreUIObject sharedInstance].globalTopAlertDelegate getTopAlertView]; - [topAlertView showWithTopAlertObject:self.topAlertObject animationDelegate:self completionHandler:^(BOOL finished) { - - self.displayed = YES; - if (self.isCancelled) { - - // Cancelled, dismiss immediately. - [self dismissAlertView:YES]; - } else if (self.isPaused) { - - // Paused, dismiss for the time being if persistent. - [self dismissAlertView:YES]; - } else { - [self updateDismissTimer]; - } - }]; - } + // Needs to be a top alert view.... + [self markAsFinished]; } else { - [self pause]; + UIView *topAlertView = [[CoreUIObject sharedInstance].globalTopAlertDelegate getTopAlertView]; + [topAlertView showWithTopAlertObject:self.topAlertObject animationDelegate:self completionHandler:^(BOOL finished) { + + self.displayed = YES; + if (self.isCancelled) { + + // Cancelled, dismiss immediately. + [self dismissAlertView:YES]; + } else if (self.isPaused) { + + // Paused, dismiss for the time being if persistent. + [self dismissAlertView:YES]; + } else { + [self updateDismissTimer]; + } + }]; } + } else { + [self pause]; } } diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m index 0a548aea..eab93d0c 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m @@ -11,7 +11,6 @@ #import "MVMCoreUITopAlertMainView.h" @import MVMCore.MVMCoreDispatchUtility; #import -#import @import MVMCore.MVMCoreBlockOperation; @import MVMCore.MVMCoreNavigationHandler; #import "MFStyler.h" diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m index 1a9131a6..1e660906 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m @@ -13,7 +13,6 @@ @import MVMCore.MVMCoreDispatchUtility; #import #import "UIColor+MFConvenience.h" -#import #import @import MVMCore.MVMCoreJSONConstants; #import "MVMCoreUICommonViewsUtility.h" diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift index 0ae1e879..4fbe51d6 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift @@ -16,82 +16,20 @@ protocol StatusBarUI { public extension MVMCoreUITopAlertView { - /// Registers with the notification center to know when json is updated. - @objc func registerWithNotificationCenter() { - NotificationCenter.default.addObserver(self, selector: #selector(responseJSONUpdated(notification:)), name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil) - } - - private func getDelegateObject() -> MVMCoreUIDelegateObject { - // TODO: Top alert view is current delegate. Should move to current view controller eventually? - return MVMCoreUIDelegateObject.create(withDelegateForAll: self) - } - - /// Checks for new top alert json - @objc private func responseJSONUpdated(notification: Notification) { - guard let loadObject = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject) else { return } - - // Dismiss any top alerts that server wants us to dismiss/ - if let disableType = loadObject.responseInfoMap?.optionalStringForKey("disableType") { - MVMCoreAlertHandler.shared()?.hideTopAlertView(ofType: disableType) - } - - // Show any new top alert. - guard let responseJSON = loadObject.responseJSON, - let json = responseJSON.optionalDictionaryForKey(KeyTopAlert), - let model = decodeTopNotification(with: json, delegateObject: getDelegateObject()) else { return } - showTopAlert(with: model) - } - - /// Decodes the json into a TopNotificationModel - private func decodeTopNotification(with json: [AnyHashable: Any], delegateObject: MVMCoreUIDelegateObject?) -> TopNotificationModel? { - do { - return try TopNotificationModel.decode(json: json, delegateObject: delegateObject) - } catch { - if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "\(self)") { - MVMCoreUILoggingHandler.addError(toLog: errorObject) - } - return nil - } - } - - /// Shows the top alert with the model. - func showTopAlert(with model: TopNotificationModel) { - let object = model.createTopAlertObject() - guard !checkAndUpdateExisting(with: object), - let operation = MVMCoreTopAlertOperation(topAlertObject: object) else { return } - MVMCoreAlertHandler.shared()?.add(operation) - } - /// Shows the top alert with the json. @objc func showTopAlert(with json: [AnyHashable: Any]) { - guard let model = decodeTopNotification(with: json, delegateObject: getDelegateObject()) else { return } - showTopAlert(with: model) - } - - /// Checks for existing top alert object of same type and updates it. Only happens for molecular top alerts. Returns true if we updated. - private func checkAndUpdateExisting(with topAlertObject: MVMCoreTopAlertObject) -> Bool { - guard let queue = MVMCoreAlertHandler.shared()?.topAlertQueue.operations else { return false } - for case let operation as MVMCoreTopAlertOperation in queue { - guard topAlertObject.json != nil, - operation.topAlertObject.type == topAlertObject.type else { continue } - operation.update(with: topAlertObject) - let pageType = (MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() as? MVMCoreViewControllerProtocol)?.pageType - operation.updateDisplayable(byPageType: pageType) - MVMCoreAlertHandler.shared()?.reevaluteQueue() - return true - } - return false + TopNotificationHandler.shared().showTopNotification(with: json) } /// Updates the current top alert molecule with the new object @objc func updateMolecule(with topAlertObject: MVMCoreTopAlertObject) { guard topAlertObject.type == self.topAlertObject?.type else { return } - let delegateObject = getDelegateObject() + let delegateObject = MVMCoreUIDelegateObject.create(withDelegateForAll: self) guard let newJson = topAlertObject.json, - let newModel = decodeTopNotification(with: newJson, delegateObject: delegateObject), + let newModel = TopNotificationHandler.shared().decodeTopNotification(with: newJson, delegateObject: delegateObject), let newModelName = ModelRegistry.getMoleculeClass(newModel.molecule)?.nameForReuse(with: newModel.molecule, delegateObject), let currentJson = self.topAlertObject?.json, - let currentModel = decodeTopNotification(with: currentJson, delegateObject: delegateObject), + let currentModel = TopNotificationHandler.shared().decodeTopNotification(with: currentJson, delegateObject: delegateObject), let currentModelName = ModelRegistry.getMoleculeClass(currentModel.molecule)?.nameForReuse(with: currentModel.molecule, delegateObject), newModelName == currentModelName, let molecule = currentAlert as? MoleculeViewProtocol else { diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m index 292d9132..755fc8e9 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m @@ -21,8 +21,6 @@ @import MVMCore.MVMCoreLoadHandler; @import MVMCore.MVMCoreNavigationHandler; @import MVMCore.MVMCoreBlockOperation; -#import -#import @import MVMCore.NSDictionary_MFConvenience; @import MVMCore.MVMCoreRequestParameters; @import MVMCore.MVMCoreJSONConstants; @@ -87,7 +85,6 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; self.clipsToBounds = YES; self.height = [self.heightAnchor constraintEqualToConstant:0]; self.height.active = YES; - [self registerWithNotificationCenter]; } - (void)updateView:(CGFloat)size { From e55d9e14f680e37282afe975ae68cffa025776d0 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 17 Apr 2023 13:04:45 -0400 Subject: [PATCH 02/12] Swiftify Alert --- MVMCoreUI.xcodeproj/project.pbxproj | 16 +-- MVMCoreUI/Alerts/AlertHandler.swift | 6 +- MVMCoreUI/Alerts/AlertOperation.swift | 22 ++-- .../Alerts/MVMCoreAlertDelegateProtocol.h | 11 +- .../Alerts/MVMCoreAlertObject+Swift.swift | 2 +- MVMCoreUI/Alerts/TopNotificationHandler.swift | 10 -- .../Atomic/Actions/ActionPopupHandler.swift | 121 ------------------ .../Atomic/Actions/ActionPopupModel.swift | 31 ----- MVMCoreUI/Atomic/Actions/AlertModel.swift | 2 +- MVMCoreUI/MVMCoreUI.h | 1 - .../OtherHandlers/CoreUIModelMapping.swift | 1 - .../MVMCoreUIActionDelegateProtocol.h | 16 --- .../MVMCoreUILoggingDelegateProtocol.swift | 13 ++ 13 files changed, 36 insertions(+), 216 deletions(-) delete mode 100644 MVMCoreUI/Atomic/Actions/ActionPopupHandler.swift delete mode 100644 MVMCoreUI/Atomic/Actions/ActionPopupModel.swift delete mode 100644 MVMCoreUI/OtherHandlers/MVMCoreUIActionDelegateProtocol.h create mode 100644 MVMCoreUI/OtherHandlers/MVMCoreUILoggingDelegateProtocol.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 202e1d54..1a59212b 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -278,7 +278,6 @@ AAE96FA225341F6A0037A989 /* ListStoreLocatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE96FA125341F6A0037A989 /* ListStoreLocatorModel.swift */; }; AAE96FA525341F7D0037A989 /* ListStoreLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE96FA425341F7D0037A989 /* ListStoreLocator.swift */; }; AF1C33652883B5A4006B1001 /* ActionTopNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C33642883B5A4006B1001 /* ActionTopNotificationHandler.swift */; }; - AF1C33672883B712006B1001 /* ActionPopupHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C33662883B712006B1001 /* ActionPopupHandler.swift */; }; AF1C336928859778006B1001 /* ActionAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C336828859778006B1001 /* ActionAlertHandler.swift */; }; AF1C336B28859C73006B1001 /* ActionTopAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C336A28859C73006B1001 /* ActionTopAlertHandler.swift */; }; AF1C336D28859EE1006B1001 /* ActionOpenPanelHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1C336C28859EE1006B1001 /* ActionOpenPanelHandler.swift */; }; @@ -291,6 +290,7 @@ AF7E509929E477C1009DC2AD /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7E509729E477C0009DC2AD /* AlertController.swift */; }; AFA4932029E5CA73001A9663 /* AlertOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4931F29E5CA73001A9663 /* AlertOperation.swift */; }; AFA4932229E5EF2E001A9663 /* TopNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */; }; + AFA4933F29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */; }; AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; }; AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; }; BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */; }; @@ -560,7 +560,6 @@ D2EC7BDD2527B83700F540AF /* SectionHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EC7BDC2527B83700F540AF /* SectionHeaderFooterView.swift */; }; D2ED27EB254B0CE700A1C293 /* UIAlertActionStyle+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27E6254B0CE600A1C293 /* UIAlertActionStyle+Codable.swift */; }; D2ED27EC254B0CE700A1C293 /* UIAlertControllerStyle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27E7254B0CE600A1C293 /* UIAlertControllerStyle+Extension.swift */; }; - D2ED27ED254B0CE700A1C293 /* ActionPopupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27E8254B0CE600A1C293 /* ActionPopupModel.swift */; }; 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, ); }; }; @@ -573,7 +572,6 @@ D2ED2811254B0EB800A1C293 /* MVMCoreTopAlertObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED280A254B0EB700A1C293 /* MVMCoreTopAlertObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2ED2812254B0EB800A1C293 /* MVMCoreTopAlertObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2ED280B254B0EB800A1C293 /* MVMCoreTopAlertObject.m */; }; D2ED2815254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED2814254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D2ED2818254B115400A1C293 /* MVMCoreUIActionDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D2ED2817254B112900A1C293 /* MVMCoreUIActionDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2FA83D22513EA6900564112 /* NotificationXButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D12513EA6900564112 /* NotificationXButton.swift */; }; D2FA83D42514F80C00564112 /* CollapsableNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D32514F80C00564112 /* CollapsableNotification.swift */; }; D2FA83D62515021F00564112 /* CollapsableNotificationTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D52515021F00564112 /* CollapsableNotificationTopView.swift */; }; @@ -885,7 +883,6 @@ AAE96FA125341F6A0037A989 /* ListStoreLocatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStoreLocatorModel.swift; sourceTree = ""; }; AAE96FA425341F7D0037A989 /* ListStoreLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStoreLocator.swift; sourceTree = ""; }; AF1C33642883B5A4006B1001 /* ActionTopNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionTopNotificationHandler.swift; sourceTree = ""; }; - AF1C33662883B712006B1001 /* ActionPopupHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionPopupHandler.swift; sourceTree = ""; }; AF1C336828859778006B1001 /* ActionAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionAlertHandler.swift; sourceTree = ""; }; AF1C336A28859C73006B1001 /* ActionTopAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionTopAlertHandler.swift; sourceTree = ""; }; AF1C336C28859EE1006B1001 /* ActionOpenPanelHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenPanelHandler.swift; sourceTree = ""; }; @@ -898,6 +895,7 @@ AF7E509729E477C0009DC2AD /* AlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertController.swift; sourceTree = ""; }; AFA4931F29E5CA73001A9663 /* AlertOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertOperation.swift; sourceTree = ""; }; AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopNotificationHandler.swift; sourceTree = ""; }; + AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingDelegateProtocol.swift; sourceTree = ""; }; AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = ""; }; AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewLeftAlignedLayout.swift; sourceTree = ""; }; @@ -1168,7 +1166,6 @@ D2EC7BDC2527B83700F540AF /* SectionHeaderFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderFooterView.swift; sourceTree = ""; }; D2ED27E6254B0CE600A1C293 /* UIAlertActionStyle+Codable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertActionStyle+Codable.swift"; sourceTree = ""; }; D2ED27E7254B0CE600A1C293 /* UIAlertControllerStyle+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertControllerStyle+Extension.swift"; sourceTree = ""; }; - D2ED27E8254B0CE600A1C293 /* ActionPopupModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionPopupModel.swift; sourceTree = ""; }; 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 = ""; }; @@ -1182,7 +1179,6 @@ D2ED280A254B0EB700A1C293 /* MVMCoreTopAlertObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreTopAlertObject.h; sourceTree = ""; }; D2ED280B254B0EB800A1C293 /* MVMCoreTopAlertObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreTopAlertObject.m; sourceTree = ""; }; D2ED2814254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreGlobalTopAlertDelegateProtocol.h; sourceTree = ""; }; - D2ED2817254B112900A1C293 /* MVMCoreUIActionDelegateProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIActionDelegateProtocol.h; sourceTree = ""; }; D2FA83D12513EA6900564112 /* NotificationXButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationXButton.swift; sourceTree = ""; }; D2FA83D32514F80C00564112 /* CollapsableNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableNotification.swift; sourceTree = ""; }; D2FA83D52515021F00564112 /* CollapsableNotificationTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableNotificationTopView.swift; sourceTree = ""; }; @@ -1522,8 +1518,6 @@ D2ED27E9254B0CE600A1C293 /* ActionAlertModel.swift */, D2ED27EA254B0CE700A1C293 /* AlertModel.swift */, AF1C336828859778006B1001 /* ActionAlertHandler.swift */, - D2ED27E8254B0CE600A1C293 /* ActionPopupModel.swift */, - AF1C33662883B712006B1001 /* ActionPopupHandler.swift */, C6687440259D92D400F32D13 /* ActionTopNotificationModel.swift */, AF1C33642883B5A4006B1001 /* ActionTopNotificationHandler.swift */, AF1C33722885D481006B1001 /* MVMCoreUIActionOpenPageHandler.swift */, @@ -2329,11 +2323,11 @@ D29DF27821E7A533003B2FB9 /* MVMCoreUISession.m */, D29DF27321E79E81003B2FB9 /* MVMCoreUILoggingHandler.h */, D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */, + AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */, D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */, D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */, D2092352244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift */, D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */, - D2ED2817254B112900A1C293 /* MVMCoreUIActionDelegateProtocol.h */, AF1C33702885AE76006B1001 /* MVMCoreUIActionHandler.swift */, D23A90672614B0B4007E14CE /* CoreUIModelMapping.swift */, ); @@ -2588,7 +2582,6 @@ D2ED280F254B0EB800A1C293 /* MVMCoreTopAlertViewProtocol.h in Headers */, D2ED280C254B0EB800A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h in Headers */, D2ED280D254B0EB800A1C293 /* MVMCoreTopAlertOperation.h in Headers */, - D2ED2818254B115400A1C293 /* MVMCoreUIActionDelegateProtocol.h in Headers */, D2ED27FB254B0E0300A1C293 /* MVMCoreAlertDelegateProtocol.h in Headers */, D2ED2810254B0EB800A1C293 /* MVMCoreTopAlertDelegateProtocol.h in Headers */, D2ED2815254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h in Headers */, @@ -2784,7 +2777,6 @@ AAB9C10A243496DD00151545 /* RadioSwatch.swift in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, 011D9602240DA20A000E3791 /* FormRuleWatcherFieldProtocol.swift in Sources */, - AF1C33672883B712006B1001 /* ActionPopupHandler.swift in Sources */, D23A900926125FFB007E14CE /* GetContactBehavior.swift in Sources */, D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */, AA617AB22453012400910B8F /* ListDeviceComplexLinkSmallModel.swift in Sources */, @@ -2846,7 +2838,6 @@ D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, - D2ED27ED254B0CE700A1C293 /* ActionPopupModel.swift in Sources */, 94C2D9A723872DA90006CF46 /* LabelAttributeColorModel.swift in Sources */, 943820842432382400B43AF3 /* WebView.swift in Sources */, 0103B84E23D7E33A009C315C /* HeadlineBodyToggleModel.swift in Sources */, @@ -2882,6 +2873,7 @@ D28A838323CCBD3F00DFE4FC /* WheelModel.swift in Sources */, D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */, 0A9D091D2433796500D2E6C0 /* BarsCarouselIndicatorModel.swift in Sources */, + AFA4933F29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift in Sources */, DBEFFA04225A829700230692 /* Label.swift in Sources */, D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */, AF1C33712885AE76006B1001 /* MVMCoreUIActionHandler.swift in Sources */, diff --git a/MVMCoreUI/Alerts/AlertHandler.swift b/MVMCoreUI/Alerts/AlertHandler.swift index 39b2d046..5a7b843a 100644 --- a/MVMCoreUI/Alerts/AlertHandler.swift +++ b/MVMCoreUI/Alerts/AlertHandler.swift @@ -40,7 +40,7 @@ public class AlertHandler { return !operation.isCancelled && !operation.isFinished && operation.isExecuting && - (operation as? AlertOperation)?.isGreedy ?? false + (operation as? AlertOperation)?.alertObject.isGreedy ?? false }) } @@ -71,14 +71,14 @@ public class AlertHandler { } let alertController = createAlertController(with: alertObject.alertModel) - let alertOperation = AlertOperation(with: alertController, isGreedy: alertObject.isGreedy, alertDelegate: alertObject.alertDelegate) + let alertOperation = AlertOperation(with: alertController, alertObject: alertObject) // If an existing greedy alert is showing, add it as a dependency. if let greedyAlertOperation = queue.operations.first(where: { operation in guard !operation.isFinished, !operation.isCancelled, let alertOperation = operation as? AlertOperation else { return false } - return alertOperation.isGreedy + return alertOperation.alertObject.isGreedy }) { alertOperation.addDependency((greedyAlertOperation as! AlertOperation)) } diff --git a/MVMCoreUI/Alerts/AlertOperation.swift b/MVMCoreUI/Alerts/AlertOperation.swift index ee280473..fdd1c190 100644 --- a/MVMCoreUI/Alerts/AlertOperation.swift +++ b/MVMCoreUI/Alerts/AlertOperation.swift @@ -30,14 +30,11 @@ public class AlertOperation: MVMCoreOperation { public let alertController: AlertController - public let isGreedy: Bool + public let alertObject: AlertObject - public weak var alertDelegate: MVMCoreAlertDelegateProtocol? - - public init(with alert: AlertController, isGreedy: Bool = false, alertDelegate: MVMCoreAlertDelegateProtocol? = nil) { + public init(with alert: AlertController, alertObject: AlertObject) { self.alertController = alert - self.isGreedy = isGreedy - self.alertDelegate = alertDelegate + self.alertObject = alertObject } deinit { @@ -57,8 +54,11 @@ public class AlertOperation: MVMCoreOperation { // 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 if self.isCancelled { - await self.dismissAlertView() + } else { + (CoreUIObject.sharedInstance()?.loggingDelegate as? MVMCoreUILoggingDelegateProtocol)?.logAlert(with: self.alertObject) + if self.isCancelled { + await self.dismissAlertView() + } } } } @@ -67,7 +67,7 @@ public class AlertOperation: MVMCoreOperation { public override func cancel() { super.cancel() Task { @MainActor in - self.alertDelegate?.alertCancelled?(self.alertController) + self.alertObject.alertDelegate?.alertCancelled?(self.alertController) await self.dismissAlertView() } } @@ -92,9 +92,9 @@ public class AlertOperation: MVMCoreOperation { Task { @MainActor in await self.properties.set(displayed: visible) if visible { - self.alertDelegate?.alertShown?(self.alertController) + self.alertObject.alertDelegate?.alertShown?(self.alertController) } else { - self.alertDelegate?.alertDismissed?(self.alertController) + self.alertObject.alertDelegate?.alertDismissed?(self.alertController) // Is visible was set to NO, meaning that the alertview is no longer visible. self.stopObservingAlertView() diff --git a/MVMCoreUI/Alerts/MVMCoreAlertDelegateProtocol.h b/MVMCoreUI/Alerts/MVMCoreAlertDelegateProtocol.h index 1b8d65c1..4b66fcdb 100644 --- a/MVMCoreUI/Alerts/MVMCoreAlertDelegateProtocol.h +++ b/MVMCoreUI/Alerts/MVMCoreAlertDelegateProtocol.h @@ -8,7 +8,6 @@ // Called for popup style alerts. #import -@class MVMCoreAlertObject; @class MVMCoreLoadObject; @class MVMCoreErrorObject; @@ -16,9 +15,6 @@ @optional -// helps tracking alert state -- (nullable NSDictionary *)additionalAlertDataToTrackForAlertWithObject:(nullable MVMCoreAlertObject *)alertObject; - // All are performed on the main thread. - (void)alertShown:(nonnull UIAlertController *)alertController; - (void)alertCancelled:(nonnull UIAlertController *)alertController; @@ -26,11 +22,10 @@ - (void)alertPaused:(nonnull UIAlertController *)alertController; - (void)alertUnpaused:(nonnull UIAlertController *)alertController; -/** Get the alert object whose data will be presented. Overwrite this to alter how you want the alert to show. +/** Overwrite this to alter how you want the alert to show. * @param loadObject The load object. * @param errorObject An error object if there was an error. - * @return Returns the alert object. - * Details: Gets the alert that will display to the screen. Easier to subclass here to avoid subclassing the displaying logic. */ -- (nullable MVMCoreAlertObject *)alertObjectToShow:(nonnull MVMCoreLoadObject *)loadObject error:(nullable MVMCoreErrorObject *)errorObject; + * Details: Easier to subclass here to avoid subclassing the displaying logic. */ +- (void)handleAlertFor:(nonnull MVMCoreLoadObject *)loadObject error:(nullable MVMCoreErrorObject *)errorObject; @end diff --git a/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift b/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift index 60444ef5..69305385 100644 --- a/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift +++ b/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift @@ -15,7 +15,7 @@ public struct AlertObject { public var isGreedy = false /// The alert model for the alert to show. - public let alertModel: AlertModel + public var alertModel: AlertModel public weak var alertDelegate: MVMCoreAlertDelegateProtocol? diff --git a/MVMCoreUI/Alerts/TopNotificationHandler.swift b/MVMCoreUI/Alerts/TopNotificationHandler.swift index 82fe4889..ce6a34a0 100644 --- a/MVMCoreUI/Alerts/TopNotificationHandler.swift +++ b/MVMCoreUI/Alerts/TopNotificationHandler.swift @@ -156,16 +156,6 @@ public class TopNotificationHandler { let alertOperation = MVMCoreTopAlertOperation(topAlertObject: topAlertObject)! add(operation: alertOperation) } - - public func showTopAlertError(with message: String) { - let topAlertObject = MVMCoreTopAlertObject(type: ValueTypeError, message: message)! - showTopAlert(with: topAlertObject) - } - - public func showTopAlertConfirmation(with message: String) { - let topAlertObject = MVMCoreTopAlertObject(type: ValueTypeSuccess, message: message)! - showTopAlert(with: topAlertObject) - } /// Cancel the current top alert view. public func hideTopAlertView() { diff --git a/MVMCoreUI/Atomic/Actions/ActionPopupHandler.swift b/MVMCoreUI/Atomic/Actions/ActionPopupHandler.swift deleted file mode 100644 index 6749838d..00000000 --- a/MVMCoreUI/Atomic/Actions/ActionPopupHandler.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// ActionPopupHandler.swift -// MVMCoreUI -// -// Created by Scott Pfeil on 7/16/22. -// Copyright © 2022 Verizon Wireless. All rights reserved. -// - -import Foundation -import MVMCore - -/// Shows a popup alert by grabbing the content from a Page in the cache using the pageType. -open class ActionPopupHandler: MVMCoreActionHandlerProtocol { - required public init() {} - - public enum Error: MVMError, CustomStringConvertible { - case jsonNotFound - - public var description: String { - switch self { - case ActionPopupHandler.Error.jsonNotFound: - return "JSON for popup not found." - } - } - - public var errorCode: Int { - ErrorCode.popupFailed.rawValue - } - } - - open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { - guard let model = model as? ActionPopupModel else { return } - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - MVMCoreCache.shared()?.fetchJSON(forPageType: model.pageType, queue: nil, waitUntilFinished: true, completionHandler: { json in - guard let page = json else { - continuation.resume(throwing: ActionPopupHandler.Error.jsonNotFound) - return - } - Task(priority: .userInitiated) { - do { - try await self.showAlertObjectWithPage(page, isGreedy: false, additionalData: nil, delegateObject: delegateObject) - continuation.resume() - } catch { - continuation.resume(throwing: error) - } - } - }) - } - } - - // TODO: Find a way to wait for the actual alert to show? or finish? - /// Shows the alert using the legacy page format. - open func showAlertObjectWithPage(_ page: [AnyHashable: Any], isGreedy: Bool, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { - let alertObject = try alertObjectWithPage(page, isGreedy: false, additionalData: additionalData, delegateObject: delegateObject) - _ = await AlertHandler.shared().queueAlertToShow(with: alertObject) - } - - open func alertObjectWithPage(_ page: [AnyHashable: Any], isGreedy: Bool, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) throws -> AlertObject { - let data = try JSONSerialization.data(withJSONObject: page) - let decoder = JSONDecoder.create(with: delegateObject) - let popupModel = try decoder.decode(LegacyAlertModel.self, from: data) - let alert = AlertModel(title: popupModel.title ?? "", message: popupModel.message ?? "", actions: popupModel.buttonActions) - return AlertObject(alertModel: alert, isGreedy: isGreedy) - } -} - -private protocol ActionWithTitle: ActionModelProtocol { - var title: String { get set } -} - -private struct LegacyAlertModel: Codable { - var title: String? - var message: String? - var buttonActions: [UIAlertAction] - private var buttonsForEncode: JSONValueArray - - //-------------------------------------------------- - // MARK: - Keys - //-------------------------------------------------- - - private enum CodingKeys: String, CodingKey { - case title - case message - case Links - } - - //-------------------------------------------------- - // MARK: - Codec - //-------------------------------------------------- - - struct TitleObject: Codable { - var title: String - } - - public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - buttonsForEncode = try typeContainer.decode(JSONValueArray.self, forKey: .Links) - title = try typeContainer.decode(String.self, forKey: .title) - message = try typeContainer.decode(String.self, forKey: .message) - if title?.count == 0 && message?.count == 0 { - throw ModelRegistry.Error.decoderOther(message: "Popups must have either a title or a message") - } - - let buttonTitles = try typeContainer.decode([TitleObject].self, forKey: .Links).map({ object in - return object.title - }) - let delegateObject = try decoder.get() - self.buttonActions = (try typeContainer.decodeModels(codingKey: .Links) as [ActionModelProtocol]).enumerated().map { (index, action) in - return AlertButtonModel(buttonTitles[index], action) - }.map({ alertButtonModel in - return alertButtonModel.generateAction(delegateObject: delegateObject) - }) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encodeIfPresent(title, forKey: .title) - try container.encodeIfPresent(message, forKey: .message) - try container.encode(buttonsForEncode, forKey: .Links) - } -} diff --git a/MVMCoreUI/Atomic/Actions/ActionPopupModel.swift b/MVMCoreUI/Atomic/Actions/ActionPopupModel.swift deleted file mode 100644 index cbade308..00000000 --- a/MVMCoreUI/Atomic/Actions/ActionPopupModel.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// ActionPopupModel.swift -// MVMCore -// -// Created by Suresh, Kamlesh on 12/16/19. -// Copyright © 2019 myverizon. All rights reserved. -// - -import MVMCore - -public struct ActionPopupModel: ActionModelProtocol { - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - - public static var identifier: String = "popup" - public var actionType: String = ActionPopupModel.identifier - public var pageType: String - public var extraParameters: JSONValueDictionary? - public var analyticsData: JSONValueDictionary? - - //-------------------------------------------------- - // MARK: - Initializer - //-------------------------------------------------- - - public init(pageType: String, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { - self.pageType = pageType - self.extraParameters = extraParameters - self.analyticsData = analyticsData - } -} diff --git a/MVMCoreUI/Atomic/Actions/AlertModel.swift b/MVMCoreUI/Atomic/Actions/AlertModel.swift index 4ee37ab9..e4b82018 100644 --- a/MVMCoreUI/Atomic/Actions/AlertModel.swift +++ b/MVMCoreUI/Atomic/Actions/AlertModel.swift @@ -69,7 +69,7 @@ public struct AlertModel: Codable { public var message: String public var style: UIAlertController.Style = .alert public var actions: [UIAlertAction] - private var alertActions: [AlertButtonModel]? + public var alertActions: [AlertButtonModel]? public var analyticsData: JSONValueDictionary? //-------------------------------------------------- diff --git a/MVMCoreUI/MVMCoreUI.h b/MVMCoreUI/MVMCoreUI.h index f5a0c6fc..753c0a75 100644 --- a/MVMCoreUI/MVMCoreUI.h +++ b/MVMCoreUI/MVMCoreUI.h @@ -20,7 +20,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; #import #import #import -#import // Alert Handling #import diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index c27553ec..680fc2e4 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -229,7 +229,6 @@ open class CoreUIModelMapping: ModelMapping { open override class func registerActions() { super.registerActions() - ModelRegistry.register(handler: ActionPopupHandler.self, for: ActionPopupModel.self) ModelRegistry.register(handler: ActionAlertHandler.self, for: ActionAlertModel.self) ModelRegistry.register(handler: ActionTopAlertHandler.self, for: ActionTopAlertModel.self) ModelRegistry.register(handler: ActionCollapseNotificationHandler.self, for: ActionCollapseNotificationModel.self) diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIActionDelegateProtocol.h b/MVMCoreUI/OtherHandlers/MVMCoreUIActionDelegateProtocol.h deleted file mode 100644 index c2f2a4b9..00000000 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIActionDelegateProtocol.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// MVMCoreUIActionDelegateProtocol.h -// MVMCoreUI -// -// Created by Scott Pfeil on 10/28/20. -// Copyright © 2020 Verizon Wireless. All rights reserved. -// -@import MVMCore.MVMCoreActionDelegateProtocol; -@class MVMCoreAlertObject; - -@protocol MVMCoreUIActionDelegateProtocol - -// Gives the delegate a chance to alter the alert object -- (void)willShowPopupWithAlertObject:(nonnull MVMCoreAlertObject *)alertObject alertJson:(nonnull NSDictionary *)alertJson; - -@end diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUILoggingDelegateProtocol.swift b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingDelegateProtocol.swift new file mode 100644 index 00000000..3f135805 --- /dev/null +++ b/MVMCoreUI/OtherHandlers/MVMCoreUILoggingDelegateProtocol.swift @@ -0,0 +1,13 @@ +// +// MVMCoreUILoggingDelegateProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/13/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import MVMCore + +public protocol MVMCoreUILoggingDelegateProtocol: MVMCoreLoggingDelegateProtocol { + func logAlert(with alertObject: AlertObject) +} From a08a2c678b6e1af801db51c49e1932d1f70c3132 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 17 Apr 2023 18:52:01 -0400 Subject: [PATCH 03/12] fix compile time errors --- MVMCoreUI.xcodeproj/project.pbxproj | 8 +- MVMCoreUI/Alerts/AlertHandler.swift | 30 ++++- MVMCoreUI/Alerts/AlertObject.swift | 27 +++++ .../Alerts/MVMCoreAlertObject+Swift.swift | 103 ------------------ MVMCoreUI/Alerts/TopNotificationHandler.swift | 32 ++++-- MVMCoreUI/Atomic/Actions/AlertModel.swift | 14 ++- 6 files changed, 90 insertions(+), 124 deletions(-) create mode 100644 MVMCoreUI/Alerts/AlertObject.swift delete mode 100644 MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift 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) } } From 657e69b798e254329aced944611fecfe6decdc29 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Tue, 18 Apr 2023 09:07:11 -0400 Subject: [PATCH 04/12] more swift files --- MVMCoreUI.xcodeproj/project.pbxproj | 10 ++- MVMCoreUI/Alerts/AlertDelegateProtocol.swift | 19 +++++ MVMCoreUI/Alerts/AlertObject.swift | 4 +- MVMCoreUI/Alerts/AlertOperation.swift | 6 +- .../Alerts/MVMCoreAlertDelegateProtocol.h | 31 --------- .../MVMCoreAlertHandler+Extension.swift | 69 ------------------- MVMCoreUI/MVMCoreUI.h | 3 - .../MVMCoreUIDelegateObject.swift | 6 +- 8 files changed, 31 insertions(+), 117 deletions(-) create mode 100644 MVMCoreUI/Alerts/AlertDelegateProtocol.swift delete mode 100644 MVMCoreUI/Alerts/MVMCoreAlertDelegateProtocol.h delete mode 100644 MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 85b5fea1..665c4cb7 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -291,6 +291,7 @@ AFA4932029E5CA73001A9663 /* AlertOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4931F29E5CA73001A9663 /* AlertOperation.swift */; }; AFA4932229E5EF2E001A9663 /* TopNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */; }; AFA4933F29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */; }; + AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */; }; AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; }; AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; }; BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */; }; @@ -562,7 +563,6 @@ D2ED27EC254B0CE700A1C293 /* UIAlertControllerStyle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27E7254B0CE600A1C293 /* UIAlertControllerStyle+Extension.swift */; }; 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 /* 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, ); }; }; @@ -896,6 +896,7 @@ AFA4931F29E5CA73001A9663 /* AlertOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertOperation.swift; sourceTree = ""; }; AFA4932129E5EF2E001A9663 /* TopNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopNotificationHandler.swift; sourceTree = ""; }; AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingDelegateProtocol.swift; sourceTree = ""; }; + AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDelegateProtocol.swift; sourceTree = ""; }; AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = ""; }; AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewLeftAlignedLayout.swift; sourceTree = ""; }; @@ -1168,9 +1169,7 @@ D2ED27E7254B0CE600A1C293 /* UIAlertControllerStyle+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertControllerStyle+Extension.swift"; sourceTree = ""; }; 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 /* 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 = ""; }; D2ED2807254B0EB700A1C293 /* MVMCoreTopAlertOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreTopAlertOperation.m; sourceTree = ""; }; @@ -2521,8 +2520,7 @@ D2ED27D8254B0C1F00A1C293 /* Alerts */ = { isa = PBXGroup; children = ( - D2ED27F2254B0E0200A1C293 /* MVMCoreAlertDelegateProtocol.h */, - D2ED27F7254B0E0200A1C293 /* MVMCoreAlertHandler+Extension.swift */, + AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */, D2ED27F3254B0E0200A1C293 /* AlertObject.swift */, AF7E509729E477C0009DC2AD /* AlertController.swift */, AF7E509629E477C0009DC2AD /* AlertHandler.swift */, @@ -2582,7 +2580,6 @@ D2ED280F254B0EB800A1C293 /* MVMCoreTopAlertViewProtocol.h in Headers */, D2ED280C254B0EB800A1C293 /* MVMCoreTopAlertAnimationDelegateProtocol.h in Headers */, D2ED280D254B0EB800A1C293 /* MVMCoreTopAlertOperation.h in Headers */, - D2ED27FB254B0E0300A1C293 /* MVMCoreAlertDelegateProtocol.h in Headers */, D2ED2810254B0EB800A1C293 /* MVMCoreTopAlertDelegateProtocol.h in Headers */, D2ED2815254B0EE400A1C293 /* MVMCoreGlobalTopAlertDelegateProtocol.h in Headers */, D29DF26F21E6AA0B003B2FB9 /* FLAnimatedImageView.h in Headers */, @@ -2987,6 +2984,7 @@ D23118B325124E18001C8440 /* Notification.swift in Sources */, AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */, AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */, + AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */, D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, 27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */, diff --git a/MVMCoreUI/Alerts/AlertDelegateProtocol.swift b/MVMCoreUI/Alerts/AlertDelegateProtocol.swift new file mode 100644 index 00000000..4b032f78 --- /dev/null +++ b/MVMCoreUI/Alerts/AlertDelegateProtocol.swift @@ -0,0 +1,19 @@ +// +// AlertDelegateProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/17/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objc +public protocol AlertDelegateProtocol { + // All are performed on the main thread. + @MainActor func alertShown(_ alertController: UIAlertController) + @MainActor func alertCancelled(_ alertController: UIAlertController) + @MainActor func alertDismissed(_ alertController: UIAlertController) + @MainActor func alertPaused(_ alertController: UIAlertController) + @MainActor func alertUnpaused(_ alertController: UIAlertController) +} diff --git a/MVMCoreUI/Alerts/AlertObject.swift b/MVMCoreUI/Alerts/AlertObject.swift index 398fc7c3..fd20a2fa 100644 --- a/MVMCoreUI/Alerts/AlertObject.swift +++ b/MVMCoreUI/Alerts/AlertObject.swift @@ -17,9 +17,9 @@ public struct AlertObject { /// The alert model for the alert to show. public var alertModel: AlertModel - public weak var alertDelegate: MVMCoreAlertDelegateProtocol? + public weak var alertDelegate: AlertDelegateProtocol? - public init(alertModel: AlertModel, isGreedy: Bool = false, alertDelegate: MVMCoreAlertDelegateProtocol? = nil) { + public init(alertModel: AlertModel, isGreedy: Bool = false, alertDelegate: AlertDelegateProtocol? = nil) { self.alertModel = alertModel self.isGreedy = isGreedy self.alertDelegate = alertDelegate diff --git a/MVMCoreUI/Alerts/AlertOperation.swift b/MVMCoreUI/Alerts/AlertOperation.swift index fdd1c190..be5dadf1 100644 --- a/MVMCoreUI/Alerts/AlertOperation.swift +++ b/MVMCoreUI/Alerts/AlertOperation.swift @@ -67,7 +67,7 @@ public class AlertOperation: MVMCoreOperation { public override func cancel() { super.cancel() Task { @MainActor in - self.alertObject.alertDelegate?.alertCancelled?(self.alertController) + self.alertObject.alertDelegate?.alertCancelled(self.alertController) await self.dismissAlertView() } } @@ -92,9 +92,9 @@ public class AlertOperation: MVMCoreOperation { Task { @MainActor in await self.properties.set(displayed: visible) if visible { - self.alertObject.alertDelegate?.alertShown?(self.alertController) + self.alertObject.alertDelegate?.alertShown(self.alertController) } else { - self.alertObject.alertDelegate?.alertDismissed?(self.alertController) + self.alertObject.alertDelegate?.alertDismissed(self.alertController) // Is visible was set to NO, meaning that the alertview is no longer visible. self.stopObservingAlertView() diff --git a/MVMCoreUI/Alerts/MVMCoreAlertDelegateProtocol.h b/MVMCoreUI/Alerts/MVMCoreAlertDelegateProtocol.h deleted file mode 100644 index 4b66fcdb..00000000 --- a/MVMCoreUI/Alerts/MVMCoreAlertDelegateProtocol.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// MVMCoreAlertDelegateProtocol.h -// mobilefirst -// -// Created by Pfeil, Scott Robert on 8/8/17. -// Copyright © 2017 Verizon Wireless. All rights reserved. -// -// Called for popup style alerts. - -#import -@class MVMCoreLoadObject; -@class MVMCoreErrorObject; - -@protocol MVMCoreAlertDelegateProtocol - -@optional - -// All are performed on the main thread. -- (void)alertShown:(nonnull UIAlertController *)alertController; -- (void)alertCancelled:(nonnull UIAlertController *)alertController; -- (void)alertDismissed:(nonnull UIAlertController *)alertController; -- (void)alertPaused:(nonnull UIAlertController *)alertController; -- (void)alertUnpaused:(nonnull UIAlertController *)alertController; - -/** Overwrite this to alter how you want the alert to show. - * @param loadObject The load object. - * @param errorObject An error object if there was an error. - * Details: Easier to subclass here to avoid subclassing the displaying logic. */ -- (void)handleAlertFor:(nonnull MVMCoreLoadObject *)loadObject error:(nullable MVMCoreErrorObject *)errorObject; - -@end diff --git a/MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift b/MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift deleted file mode 100644 index 6f52ce93..00000000 --- a/MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// MVMCoreAlertHandler+Extension.swift -// MVMCore -// -// Created by Scott Pfeil on 9/15/20. -// Copyright © 2020 myverizon. All rights reserved. -// - -import Foundation - -public extension MVMCoreAlertHandler { - - /// Re-evaluates the queue operations - @objc func reevaluteQueue() { - var highestReadyOperation: MVMCoreTopAlertOperation? - var executingOperation: MVMCoreTopAlertOperation? - for case let operation as MVMCoreTopAlertOperation in topAlertQueue.operations { - guard !operation.isCancelled, - !operation.isFinished else { continue } - if operation.isReady, - highestReadyOperation == nil || operation.queuePriority.rawValue > highestReadyOperation!.queuePriority.rawValue { - highestReadyOperation = operation - } - if operation.isExecuting { - executingOperation = operation - } - } - guard let currentOperation = executingOperation else { return } - - // Cancel the executing operation if it is no longer ready to run. Re-add for later if it is persistent. - guard currentOperation.isReady else { - currentOperation.reAddAfterCancel = currentOperation.topAlertObject.persistent - currentOperation.cancel() - return - } - - // If the highest priority operation is not executing, and the executing operation is persistent, cancel it. - if let newOperation = highestReadyOperation, - currentOperation != newOperation, - currentOperation.topAlertObject.persistent { - currentOperation.reAddAfterCancel = true - currentOperation.cancel() - } - } - - /// Registers to know when pages change. - @objc func registerForPageChanges() { - MVMCoreNavigationHandler.shared()?.addDelegate(self) - } -} - -extension MVMCoreAlertHandler: MVMCorePresentationDelegateProtocol { - // Update displayable for each top alert operation when page type changes, in top queue priority order. - public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { - guard topAlertQueue.operations.count > 0 else { return } - let viewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) - guard viewController == MVMCoreUISplitViewController.main()?.getCurrentViewController() else { return } - let pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType - topAlertQueue.operations.compactMap { - $0 as? MVMCoreTopAlertOperation - }.sorted { - $0.queuePriority.rawValue > $1.queuePriority.rawValue - }.forEach { - $0.updateDisplayable(byPageType: pageType) - } - reevaluteQueue() - } -} - diff --git a/MVMCoreUI/MVMCoreUI.h b/MVMCoreUI/MVMCoreUI.h index 753c0a75..ceb4b574 100644 --- a/MVMCoreUI/MVMCoreUI.h +++ b/MVMCoreUI/MVMCoreUI.h @@ -21,9 +21,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; #import #import -// Alert Handling -#import - #pragma mark - TopAlert #import #import diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIDelegateObject.swift b/MVMCoreUI/OtherHandlers/MVMCoreUIDelegateObject.swift index b4ea0cd7..0a79ae9e 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIDelegateObject.swift +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIDelegateObject.swift @@ -7,7 +7,7 @@ // import UIKit - +import MVMCore open class MVMCoreUIDelegateObject: DelegateObject { @@ -17,7 +17,7 @@ open class MVMCoreUIDelegateObject: DelegateObject { public weak var uiTextViewDelegate: UITextViewDelegate? public weak var observingTextFieldDelegate: ObservingTextFieldDelegate? public weak var moleculeDelegate: MoleculeDelegateProtocol? - public weak var alertDelegate: (MVMCoreAlertDelegateProtocol & NSObjectProtocol)? + public weak var alertDelegate: (AlertDelegateProtocol & NSObjectProtocol)? public weak var topAlertDelegate: (MVMCoreTopAlertDelegateProtocol & NSObjectProtocol)? open override func setAll(withDelegate delegate: Any) { @@ -28,7 +28,7 @@ open class MVMCoreUIDelegateObject: DelegateObject { uiTextViewDelegate = delegate as? UITextViewDelegate observingTextFieldDelegate = delegate as? ObservingTextFieldDelegate moleculeDelegate = delegate as? MoleculeDelegateProtocol - alertDelegate = delegate as? (MVMCoreAlertDelegateProtocol & NSObjectProtocol) + alertDelegate = delegate as? (AlertDelegateProtocol & NSObjectProtocol) topAlertDelegate = delegate as? (MVMCoreTopAlertDelegateProtocol & NSObjectProtocol) } From 67cd073173f57bcb057f2a32c5c1034b3c33f745 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Tue, 18 Apr 2023 09:55:27 -0400 Subject: [PATCH 05/12] remove objc modifier MVMCoreAlertController --- MVMCoreUI/Alerts/AlertController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/MVMCoreUI/Alerts/AlertController.swift b/MVMCoreUI/Alerts/AlertController.swift index b5a2f71c..955fcb8c 100644 --- a/MVMCoreUI/Alerts/AlertController.swift +++ b/MVMCoreUI/Alerts/AlertController.swift @@ -8,7 +8,6 @@ import Foundation -@objc (MVMCoreAlertController) public class AlertController: UIAlertController { @objc dynamic public var visible = false private let visibleKey = "isVisible" From 800cc7671ae9095cadab1cfa15d4f7299a75b89f Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Tue, 18 Apr 2023 11:16:55 -0400 Subject: [PATCH 06/12] review changes --- MVMCoreUI/Alerts/AlertController.swift | 1 + MVMCoreUI/Alerts/AlertHandler.swift | 2 +- MVMCoreUI/Alerts/TopNotificationHandler.swift | 4 ++-- MVMCoreUI/Atomic/Actions/AlertModel.swift | 8 ++++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/Alerts/AlertController.swift b/MVMCoreUI/Alerts/AlertController.swift index 955fcb8c..3119face 100644 --- a/MVMCoreUI/Alerts/AlertController.swift +++ b/MVMCoreUI/Alerts/AlertController.swift @@ -7,6 +7,7 @@ // import Foundation +import MVMCore public class AlertController: UIAlertController { @objc dynamic public var visible = false diff --git a/MVMCoreUI/Alerts/AlertHandler.swift b/MVMCoreUI/Alerts/AlertHandler.swift index d2240cb7..abd087e8 100644 --- a/MVMCoreUI/Alerts/AlertHandler.swift +++ b/MVMCoreUI/Alerts/AlertHandler.swift @@ -10,7 +10,7 @@ import MVMCore public class AlertHandler { - /// Returns the action handler stored in the CoreUIObject + /// Returns the handler stored in the CoreUIObject public static func shared() -> Self { return MVMCoreActionUtility.fatalClassCheck(object: CoreUIObject.sharedInstance()?.alertHandler) } diff --git a/MVMCoreUI/Alerts/TopNotificationHandler.swift b/MVMCoreUI/Alerts/TopNotificationHandler.swift index 19970997..c711fb3e 100644 --- a/MVMCoreUI/Alerts/TopNotificationHandler.swift +++ b/MVMCoreUI/Alerts/TopNotificationHandler.swift @@ -10,10 +10,10 @@ import MVMCore public class TopNotificationHandler { - /// The operation queue of alert operations. + /// The operation queue of top notification operations. private var queue = OperationQueue() - /// Returns the action handler stored in the CoreUIObject + /// Returns the handler stored in the CoreUIObject public static func shared() -> Self { return MVMCoreActionUtility.fatalClassCheck(object: CoreUIObject.sharedInstance()?.topNotificationHandler) } diff --git a/MVMCoreUI/Atomic/Actions/AlertModel.swift b/MVMCoreUI/Atomic/Actions/AlertModel.swift index 4f22cfc7..b1428b99 100644 --- a/MVMCoreUI/Atomic/Actions/AlertModel.swift +++ b/MVMCoreUI/Atomic/Actions/AlertModel.swift @@ -69,7 +69,7 @@ public struct AlertModel: Codable, Identifiable { public var message: String public var style: UIAlertController.Style = .alert public var actions: [UIAlertAction] - public var alertActions: [AlertButtonModel]? + public var buttonModels: [AlertButtonModel]? public var analyticsData: JSONValueDictionary? public var id: String @@ -117,8 +117,8 @@ public struct AlertModel: Codable, Identifiable { let delegateObject = try decoder.get() title = try typeContainer.decode(String.self, forKey: .title) message = try typeContainer.decode(String.self, forKey: .message) - alertActions = try typeContainer.decode([AlertButtonModel].self, forKey: .alertActions) - actions = alertActions!.map({ alertButtonModel in + buttonModels = try typeContainer.decode([AlertButtonModel].self, forKey: .alertActions) + actions = buttonModels!.map({ alertButtonModel in return alertButtonModel.generateAction(delegateObject: delegateObject) }) analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData) @@ -133,7 +133,7 @@ public struct AlertModel: Codable, Identifiable { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(title, forKey: .title) try container.encode(message, forKey: .message) - try container.encodeIfPresent(alertActions, forKey: .alertActions) + try container.encodeIfPresent(buttonModels, forKey: .alertActions) try container.encode(style.rawValueString, forKey: .style) try container.encodeIfPresent(analyticsData, forKey: .analyticsData) try container.encode(id, forKey: .id) From ba14681a390d1156e5d43e9b32157354b4b72329 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Tue, 18 Apr 2023 11:33:47 -0400 Subject: [PATCH 07/12] review feedback --- MVMCoreUI/Alerts/TopNotificationHandler.swift | 2 +- MVMCoreUI/Atomic/Actions/AlertModel.swift | 1 + MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m | 54 +++++++++---------- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/MVMCoreUI/Alerts/TopNotificationHandler.swift b/MVMCoreUI/Alerts/TopNotificationHandler.swift index c711fb3e..937d174e 100644 --- a/MVMCoreUI/Alerts/TopNotificationHandler.swift +++ b/MVMCoreUI/Alerts/TopNotificationHandler.swift @@ -18,7 +18,7 @@ public class TopNotificationHandler { return MVMCoreActionUtility.fatalClassCheck(object: CoreUIObject.sharedInstance()?.topNotificationHandler) } - init() { + public init() { registerWithNotificationCenter() registerForPageChanges() } diff --git a/MVMCoreUI/Atomic/Actions/AlertModel.swift b/MVMCoreUI/Atomic/Actions/AlertModel.swift index b1428b99..96e90054 100644 --- a/MVMCoreUI/Atomic/Actions/AlertModel.swift +++ b/MVMCoreUI/Atomic/Actions/AlertModel.swift @@ -88,6 +88,7 @@ public struct AlertModel: Codable, Identifiable { public init(title: String, message: String, buttonModels: [AlertButtonModel], style: UIAlertController.Style = .alert, delegateObject: DelegateObject?, id: String = UUID().uuidString) { self.title = title self.message = message + self.buttonModels = buttonModels actions = buttonModels.map({ alertButtonModel in return alertButtonModel.generateAction(delegateObject: delegateObject) }) diff --git a/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m b/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m index acdf3b1c..0692de34 100644 --- a/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m +++ b/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m @@ -164,34 +164,34 @@ } // Do nothing if paused - if (!self.isPaused) { - - // Show - if (![[CoreUIObject sharedInstance].globalTopAlertDelegate respondsToSelector:@selector(getTopAlertView)]) { - - // Needs to be a top alert view.... - [self markAsFinished]; - } else { - UIView *topAlertView = [[CoreUIObject sharedInstance].globalTopAlertDelegate getTopAlertView]; - [topAlertView showWithTopAlertObject:self.topAlertObject animationDelegate:self completionHandler:^(BOOL finished) { - - self.displayed = YES; - if (self.isCancelled) { - - // Cancelled, dismiss immediately. - [self dismissAlertView:YES]; - } else if (self.isPaused) { - - // Paused, dismiss for the time being if persistent. - [self dismissAlertView:YES]; - } else { - [self updateDismissTimer]; - } - }]; - } - } else { - [self pause]; + if (self.isPaused) { + return; } + + // Show + if (![[CoreUIObject sharedInstance].globalTopAlertDelegate respondsToSelector:@selector(getTopAlertView)]) { + + // Needs to be a top alert view.... + [self markAsFinished]; + return; + } + + UIView *topAlertView = [[CoreUIObject sharedInstance].globalTopAlertDelegate getTopAlertView]; + [topAlertView showWithTopAlertObject:self.topAlertObject animationDelegate:self completionHandler:^(BOOL finished) { + + self.displayed = YES; + if (self.isCancelled) { + + // Cancelled, dismiss immediately. + [self dismissAlertView:YES]; + } else if (self.isPaused) { + + // Paused, dismiss for the time being if persistent. + [self dismissAlertView:YES]; + } else { + [self updateDismissTimer]; + } + }]; } /// Updates the timer to dismiss the top alert. From e61b9ed8f190c65c9a6a85fbacde2e88cfb60bea Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Tue, 18 Apr 2023 18:21:57 -0400 Subject: [PATCH 08/12] bugfixes for delegate, logging, and legacy --- MVMCoreUI/Alerts/AlertOperation.swift | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/MVMCoreUI/Alerts/AlertOperation.swift b/MVMCoreUI/Alerts/AlertOperation.swift index be5dadf1..4f56ca6e 100644 --- a/MVMCoreUI/Alerts/AlertOperation.swift +++ b/MVMCoreUI/Alerts/AlertOperation.swift @@ -25,7 +25,6 @@ public class AlertOperation: MVMCoreOperation { } private var properties = Properties() - //private var observer: NSKeyValueObservation? private var cancellable: Cancellable? public let alertController: AlertController @@ -89,26 +88,25 @@ public class AlertOperation: MVMCoreOperation { stopObservingAlertView() cancellable = alertController.publisher(for: \AlertController.visible).sink() { [weak self] visible in guard let self = self else { return } - Task { @MainActor in + Task { + guard await self.properties.getIsDisplayed() != visible else { return } await self.properties.set(displayed: visible) - 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() + 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() + } } } } -// observer = alertController.observe(\AlertController.visible, options: [.old, .new]) { [weak self] (object, change) in -// -// } } private func stopObservingAlertView() { - //observer?.invalidate() cancellable?.cancel() } } From 7df68829076a9969d0c612727bac3bff909fab77 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Wed, 19 Apr 2023 16:36:16 -0400 Subject: [PATCH 09/12] Bug fixes --- MVMCoreUI/Alerts/AlertHandler.swift | 6 +- MVMCoreUI/Alerts/AlertOperation.swift | 67 +++++++++++++++---- MVMCoreUI/Alerts/TopNotificationHandler.swift | 2 +- MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m | 7 +- .../TopAlert/MVMCoreTopAlertViewProtocol.h | 2 +- MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m | 14 ++-- 6 files changed, 74 insertions(+), 24 deletions(-) diff --git a/MVMCoreUI/Alerts/AlertHandler.swift b/MVMCoreUI/Alerts/AlertHandler.swift index abd087e8..0d9f8744 100644 --- a/MVMCoreUI/Alerts/AlertHandler.swift +++ b/MVMCoreUI/Alerts/AlertHandler.swift @@ -16,7 +16,11 @@ public class AlertHandler { } /// The operation queue of alert operations. - private var queue = OperationQueue() + private var queue = { + let queue = OperationQueue() + queue.maxConcurrentOperationCount = 1 + return queue + }() public init() {} diff --git a/MVMCoreUI/Alerts/AlertOperation.swift b/MVMCoreUI/Alerts/AlertOperation.swift index 4f56ca6e..bacc437e 100644 --- a/MVMCoreUI/Alerts/AlertOperation.swift +++ b/MVMCoreUI/Alerts/AlertOperation.swift @@ -24,16 +24,28 @@ public class AlertOperation: MVMCoreOperation { } } private var properties = Properties() - - private var cancellable: Cancellable? - + 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 { @@ -46,24 +58,38 @@ public class AlertOperation: MVMCoreOperation { // 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. - MVMCoreNavigationHandler.shared()?.present(alertController, animated: true, delegate: nil) { [weak self] in - guard let self = self else { 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() + 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) @@ -75,13 +101,22 @@ public class AlertOperation: MVMCoreOperation { 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() { @@ -93,8 +128,12 @@ public class AlertOperation: MVMCoreOperation { 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. diff --git a/MVMCoreUI/Alerts/TopNotificationHandler.swift b/MVMCoreUI/Alerts/TopNotificationHandler.swift index 937d174e..25a75c7f 100644 --- a/MVMCoreUI/Alerts/TopNotificationHandler.swift +++ b/MVMCoreUI/Alerts/TopNotificationHandler.swift @@ -148,7 +148,7 @@ public class TopNotificationHandler { guard operation.isExecuting, let operation = operation as? MVMCoreTopAlertOperation else { return false } return operation.topAlertObject.persistent && operation.topAlertObject.type == type - }) as? MVMCoreTopAlertOperation == nil + }) as? MVMCoreTopAlertOperation != nil } /// Shows the top alert with the json. diff --git a/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m b/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m index 0692de34..b3b6beaa 100644 --- a/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m +++ b/MVMCoreUI/TopAlert/MVMCoreTopAlertOperation.m @@ -30,6 +30,9 @@ @property (nonatomic, strong) dispatch_source_t timerSource; +// A reference to the show operation so it can be cancelled. +@property (nonatomic, weak) NSOperation *operation; + @end @implementation MVMCoreTopAlertOperation @@ -177,7 +180,7 @@ } UIView *topAlertView = [[CoreUIObject sharedInstance].globalTopAlertDelegate getTopAlertView]; - [topAlertView showWithTopAlertObject:self.topAlertObject animationDelegate:self completionHandler:^(BOOL finished) { + self.operation = [topAlertView showWithTopAlertObject:self.topAlertObject animationDelegate:self completionHandler:^(BOOL finished) { self.displayed = YES; if (self.isCancelled) { @@ -226,6 +229,8 @@ - (void)cancel { [super cancel]; + [self.operation cancel]; + // Do nothing if animating. if (!self.isAnimating) { diff --git a/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h b/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h index e799e858..d9b07d0e 100644 --- a/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h +++ b/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h @@ -14,7 +14,7 @@ @optional /// Show based on the object -- (void)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler; +- (nonnull NSOperation *)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler; /// Removes the notification - (void)hideAlertView:(BOOL)forceful completionHandler:(void (^ __nullable)(BOOL finished))completionHandler; diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m index 755fc8e9..6ad48095 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m @@ -141,7 +141,7 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; } } -- (void)showAlertView:(nullable UIView *)view topAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { +- (nonnull NSOperation *)showAlertView:(nullable UIView *)view topAlertObject:(nonnull MVMCoreTopAlertObject *)topAlertObject completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { __weak typeof(self) weakSelf = self; MVMCoreBlockOperation *operation = [MVMCoreBlockOperation blockOperationWithBlock:^(MVMCoreBlockOperation * _Nonnull operation) { @@ -176,6 +176,7 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; }]; }]; [[MVMCoreNavigationHandler sharedNavigationHandler] addNavigationOperation:operation]; + return operation; } @@ -190,11 +191,11 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; #pragma mark - MVMCoreTopAlertViewProtocol -- (void)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { +- (nonnull NSOperation *)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { self.animationDelegate = animationDelegate; - dispatch_async(dispatch_get_main_queue(), ^{ - + __block NSOperation *operation = nil; + [MVMCoreDispatchUtility performSyncBlockOnMainThread:^{ self.topAlertObject = topAlertObject; self.topAlertClearspotView = nil; @@ -208,8 +209,9 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; self.currentAlertOverridingStatusBar = YES; [[MVMCoreUISplitViewController mainSplitViewController] setStatusBarBackgroundColor:statusBarColor style:statusBarStyle]; } - [self showAlertView:view topAlertObject:topAlertObject completionHandler:completionHandler]; - }); + operation = [self showAlertView:view topAlertObject:topAlertObject completionHandler:completionHandler]; + }]; + return operation; } - (void)hideAlertView:(BOOL)forceful completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { From 67a0ccfbac34e5b0d7853eba40c84571f958dbc0 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Wed, 19 Apr 2023 16:44:39 -0400 Subject: [PATCH 10/12] revert --- MVMCoreUI/Alerts/AlertHandler.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/MVMCoreUI/Alerts/AlertHandler.swift b/MVMCoreUI/Alerts/AlertHandler.swift index 0d9f8744..e9f1484e 100644 --- a/MVMCoreUI/Alerts/AlertHandler.swift +++ b/MVMCoreUI/Alerts/AlertHandler.swift @@ -71,17 +71,6 @@ public class AlertHandler { let alertController = createAlertController(with: alertObject.alertModel) let alertOperation = AlertOperation(with: alertController, alertObject: alertObject) - - // If an existing greedy alert is showing, add it as a dependency. - if let greedyAlertOperation = queue.operations.first(where: { operation in - guard !operation.isFinished, - !operation.isCancelled, - let alertOperation = operation as? AlertOperation else { return false } - return alertOperation.alertObject.isGreedy - }) { - alertOperation.addDependency((greedyAlertOperation as! AlertOperation)) - } - queue.addOperation(alertOperation) return alertController } From 33eef9ceb60455951b48be9b4ac08ce1619be2c8 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Fri, 21 Apr 2023 15:44:25 -0400 Subject: [PATCH 11/12] remove debug code --- MVMCoreUI/Alerts/AlertOperation.swift | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/MVMCoreUI/Alerts/AlertOperation.swift b/MVMCoreUI/Alerts/AlertOperation.swift index bacc437e..0c96b492 100644 --- a/MVMCoreUI/Alerts/AlertOperation.swift +++ b/MVMCoreUI/Alerts/AlertOperation.swift @@ -27,14 +27,10 @@ public class AlertOperation: MVMCoreOperation { 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? @@ -58,8 +54,6 @@ public class AlertOperation: MVMCoreOperation { // 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 @@ -88,8 +82,6 @@ public class AlertOperation: MVMCoreOperation { } 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) @@ -101,11 +93,7 @@ public class AlertOperation: MVMCoreOperation { 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() } } @@ -128,12 +116,8 @@ public class AlertOperation: MVMCoreOperation { 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. From 369b96fdb572668fa8f898a3ce7f3e2361df3876 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Fri, 21 Apr 2023 15:45:02 -0400 Subject: [PATCH 12/12] remove debug code --- MVMCoreUI/Alerts/AlertOperation.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/MVMCoreUI/Alerts/AlertOperation.swift b/MVMCoreUI/Alerts/AlertOperation.swift index 0c96b492..bbffbd32 100644 --- a/MVMCoreUI/Alerts/AlertOperation.swift +++ b/MVMCoreUI/Alerts/AlertOperation.swift @@ -39,9 +39,6 @@ public class AlertOperation: MVMCoreOperation { self.alertController = alert self.alertObject = alertObject super.init() - MVMCoreDispatchUtility.performSyncBlock(onMainThread: { - self.alDescription = alert.description - }) } deinit {