From 606fd93fc33d03590291d151d116ef57eb2b197e Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 18 Jul 2022 18:54:11 -0400 Subject: [PATCH] Action modernization --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 30 +- .../ActionHandling/ActionActionsHandler.swift | 21 +- .../ActionHandling/ActionBackHandler.swift | 14 +- .../ActionHandling/ActionCallHandler.swift | 5 +- .../ActionHandling/ActionCancelHandler.swift | 2 +- .../ActionHandling/ActionContactHandler.swift | 116 ++++ .../ActionContactModel.swift | 10 +- .../ActionHandling/ActionNoopHandler.swift | 2 +- .../ActionOpenPageHandler.swift | 69 +++ .../ActionOpenPageModel.swift | 2 +- .../ActionHandling/ActionOpenSMSHandler.swift | 19 +- .../ActionHandling/ActionOpenUrlHandler.swift | 128 ++-- .../ActionPreviousSubmitHandler.swift | 26 +- .../ActionRedirectHandler.swift | 18 - .../ActionHandling/ActionRedirectModel.swift | 18 - .../ActionHandling/ActionRestartHandler.swift | 31 +- .../ActionHandling/ActionSettingHandler.swift | 4 +- .../ActionHandling/ActionShareHandler.swift | 54 ++ .../ActionShareModel.swift | 10 +- ...CoreActionDelegateProtocol+Extension.swift | 7 +- .../MVMCoreActionDelegateProtocol.h | 3 - .../MVMCoreActionHandler+Extension.swift | 212 ++++--- .../ActionHandling/MVMCoreActionHandler.h | 127 ++-- .../ActionHandling/MVMCoreActionHandler.m | 549 ++++++------------ .../MVMCore/Constants/MVMCoreJSONConstants.h | 4 + .../MVMCore/Constants/MVMCoreJSONConstants.m | 4 + .../MVMCoreLoadHandler+Extension.swift | 22 + .../MVMCore/LoadHandling/MVMCoreLoadHandler.m | 1 + .../ClientParameterHandler.swift | 12 +- MVMCore/MVMCore/Models/ModelMapping.swift | 7 +- MVMCore/MVMCore/Singletons/MVMCoreObject.h | 1 + 31 files changed, 877 insertions(+), 651 deletions(-) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionContactModel.swift (85%) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionOpenPageModel.swift (95%) delete mode 100644 MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift delete mode 100644 MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift create mode 100644 MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionShareModel.swift (76%) create mode 100644 MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index 7529847..1ae7ffc 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -100,8 +100,10 @@ AF69D4F3286E9DCE00BC6862 /* ActionActionsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4F2286E9DCE00BC6862 /* ActionActionsHandler.swift */; }; AF69D4F5286E9F5900BC6862 /* ActionSettingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4F4286E9F5900BC6862 /* ActionSettingHandler.swift */; }; AF69D4F7286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4F6286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift */; }; - AF69D4F9286EA27400BC6862 /* ActionRedirectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4F8286EA27400BC6862 /* ActionRedirectModel.swift */; }; - AF69D4FB286EA29300BC6862 /* ActionRedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4FA286EA29300BC6862 /* ActionRedirectHandler.swift */; }; + AF70699A287DD02400077CF6 /* ActionContactHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF706999287DD02400077CF6 /* ActionContactHandler.swift */; }; + AF70699E2880D01400077CF6 /* ActionShareHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF70699D2880D01400077CF6 /* ActionShareHandler.swift */; }; + AF7069A02880F0EB00077CF6 /* ActionOpenPageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF70699F2880F0EB00077CF6 /* ActionOpenPageHandler.swift */; }; + AF7069A22882293900077CF6 /* MVMCoreLoadHandler+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7069A12882293900077CF6 /* MVMCoreLoadHandler+Extension.swift */; }; AF787413286DEF8B00670588 /* ActionBackHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF787412286DEF8B00670588 /* ActionBackHandler.swift */; }; AF8D13392774EA1D008AF4A9 /* ActionOpenUrlHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */; }; AFBB96341FBA34310008D868 /* MVMCoreErrorConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBB96321FBA34310008D868 /* MVMCoreErrorConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -261,8 +263,10 @@ AF69D4F2286E9DCE00BC6862 /* ActionActionsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionActionsHandler.swift; sourceTree = ""; }; AF69D4F4286E9F5900BC6862 /* ActionSettingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSettingHandler.swift; sourceTree = ""; }; AF69D4F6286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionPreviousSubmitHandler.swift; sourceTree = ""; }; - AF69D4F8286EA27400BC6862 /* ActionRedirectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRedirectModel.swift; sourceTree = ""; }; - AF69D4FA286EA29300BC6862 /* ActionRedirectHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRedirectHandler.swift; sourceTree = ""; }; + AF706999287DD02400077CF6 /* ActionContactHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionContactHandler.swift; sourceTree = ""; }; + AF70699D2880D01400077CF6 /* ActionShareHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionShareHandler.swift; sourceTree = ""; }; + AF70699F2880F0EB00077CF6 /* ActionOpenPageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenPageHandler.swift; sourceTree = ""; }; + AF7069A12882293900077CF6 /* MVMCoreLoadHandler+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreLoadHandler+Extension.swift"; sourceTree = ""; }; AF787412286DEF8B00670588 /* ActionBackHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBackHandler.swift; sourceTree = ""; }; AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenUrlHandler.swift; sourceTree = ""; }; AFBB96321FBA34310008D868 /* MVMCoreErrorConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreErrorConstants.h; sourceTree = ""; }; @@ -484,10 +488,7 @@ 1DAD0FFD26AAB3FF00216E83 /* ActionRunJavaScriptModel.swift */, 016FF6EC259A4E3E00F5E4AA /* Client Parameters */, 01F2A03523A80A7300D954D8 /* ActionModelProtocol.swift */, - 946EE1BB237B691A0036751F /* ActionOpenPageModel.swift */, 01DB1F2A26444F7F000F1AF4 /* ActionOpenPageProtocol.swift */, - 0AFF597923FC6E60005C24E8 /* ActionShareModel.swift */, - 0ACC81A12613C73800A9C886 /* ActionContactModel.swift */, ); path = ActionType; sourceTree = ""; @@ -574,6 +575,7 @@ AFBB96371FBA39E70008D868 /* MVMCoreLoadDelegateProtocol.h */, AFBB96391FBA3A550008D868 /* MVMCoreLoadHandler.h */, AFBB964B1FBA3A560008D868 /* MVMCoreLoadHandler.m */, + AF7069A12882293900077CF6 /* MVMCoreLoadHandler+Extension.swift */, AFBB964A1FBA3A560008D868 /* MVMCoreLoadRequestOperation.h */, AFBB96521FBA3A570008D868 /* MVMCoreLoadRequestOperation.m */, AFBB96471FBA3A560008D868 /* MVMCoreLoadObject.h */, @@ -636,6 +638,8 @@ AFBB96B61FBA3CEC0008D868 /* MVMCoreActionHandler.h */, AFBB96B71FBA3CEC0008D868 /* MVMCoreActionHandler.m */, D27073CC25BB4CEF001C7246 /* MVMCoreActionHandler+Extension.swift */, + 946EE1BB237B691A0036751F /* ActionOpenPageModel.swift */, + AF70699F2880F0EB00077CF6 /* ActionOpenPageHandler.swift */, AF130B8D2788DF6E00C6C03C /* OpenURLOptionsModel.swift */, 01F2A03823A812DD00D954D8 /* ActionOpenUrlModel.swift */, AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */, @@ -657,8 +661,10 @@ AF69D4F4286E9F5900BC6862 /* ActionSettingHandler.swift */, 94C014D2242119E6005811A9 /* ActionPreviousSubmitModel.swift */, AF69D4F6286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift */, - AF69D4F8286EA27400BC6862 /* ActionRedirectModel.swift */, - AF69D4FA286EA29300BC6862 /* ActionRedirectHandler.swift */, + 0ACC81A12613C73800A9C886 /* ActionContactModel.swift */, + AF706999287DD02400077CF6 /* ActionContactHandler.swift */, + 0AFF597923FC6E60005C24E8 /* ActionShareModel.swift */, + AF70699D2880D01400077CF6 /* ActionShareHandler.swift */, ); path = ActionHandling; sourceTree = ""; @@ -909,8 +915,10 @@ AF43A74D1FC6109F008E9347 /* MVMCoreSessionObject.m in Sources */, D282AAB62240085300C46919 /* MVMCoreGetterUtility+Extension.swift in Sources */, AFBB96901FBA3A9A0008D868 /* MVMCoreNavigationObject.m in Sources */, + AF7069A22882293900077CF6 /* MVMCoreLoadHandler+Extension.swift in Sources */, 1DAD0FFE26AAB40000216E83 /* ActionRunJavaScriptModel.swift in Sources */, 946EE1AB237B5C940036751F /* Decoder.swift in Sources */, + AF70699A287DD02400077CF6 /* ActionContactHandler.swift in Sources */, AF69D4F3286E9DCE00BC6862 /* ActionActionsHandler.swift in Sources */, 946EE1BC237B691A0036751F /* ActionOpenPageModel.swift in Sources */, 0A42538F23F3414800554656 /* Codable+Helpers.swift in Sources */, @@ -921,7 +929,6 @@ 30349BF21FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m in Sources */, D2DEDCB923C6400600C44CC4 /* UnitInterval.swift in Sources */, 94C014D3242119E6005811A9 /* ActionPreviousSubmitModel.swift in Sources */, - AF69D4FB286EA29300BC6862 /* ActionRedirectHandler.swift in Sources */, 8876D5E91FB50AB000EB2E3D /* NSArray+MFConvenience.m in Sources */, D27073B725BB45C4001C7246 /* ActionActionsModel.swift in Sources */, 946EE1B2237B5F260036751F /* JSONValue.swift in Sources */, @@ -947,13 +954,14 @@ D27073CD25BB4CEF001C7246 /* MVMCoreActionHandler+Extension.swift in Sources */, 01F2A05223A8325100D954D8 /* ModelMapping.swift in Sources */, 8876D5F51FB50AB000EB2E3D /* UILabel+MFCustom.m in Sources */, - AF69D4F9286EA27400BC6862 /* ActionRedirectModel.swift in Sources */, AFBB96B31FBA3B590008D868 /* MVMCoreGetterUtility.m in Sources */, AF43A7071FC4D7A2008E9347 /* MVMCoreObject.m in Sources */, 94C014D924212360005811A9 /* ActionSettingModel.swift in Sources */, D2DEDCB723C63F3B00C44CC4 /* Clamping.swift in Sources */, + AF70699E2880D01400077CF6 /* ActionShareHandler.swift in Sources */, 01DF561421F90ADC00CC099B /* Dictionary+MFConvenience.swift in Sources */, EA3B264C25FC0B7600008074 /* ModelHandlerProtocol.swift in Sources */, + AF7069A02880F0EB00077CF6 /* ActionOpenPageHandler.swift in Sources */, AFBB96B11FBA3B590008D868 /* MVMCoreDispatchUtility.m in Sources */, 946EE1A3237B59C30036751F /* ModelProtocol.swift in Sources */, AFEA17A9209B6A1C00BC6740 /* MVMCoreBlockOperation.m in Sources */, diff --git a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift index 1e88940..3ee233a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift @@ -11,14 +11,21 @@ import Foundation open class ActionActionsHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionActionsModel else { return } - for action in model.actions { - // TODO: Improve to make truly concurrent. - if model.concurrent { - MVMCoreActionHandler.shared()?.asyncHandleAction(with: action, additionalData: additionalData, delegateObject: delegateObject) - } else { - MVMCoreActionHandler.shared()?.syncHandleAction(with: action, additionalData: additionalData, delegateObject: delegateObject) + if model.concurrent { + // TODO: inspect warning. + await withThrowingTaskGroup(of: Void.self) { group in + for action in model.actions { + group.addTask{ + try await MVMCoreActionHandler.shared()?.handleAction(with: action, additionalData: additionalData, delegateObject: delegateObject) + } + } + } + } else { + for action in model.actions { + try Task.checkCancellation() + try await MVMCoreActionHandler.shared()?.handleAction(with: action, additionalData: additionalData, delegateObject: delegateObject) } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift index 258a26a..f93b009 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift @@ -11,12 +11,16 @@ import Foundation open class ActionBackHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionBackModel else { return } - do { - delegateObject?.actionDelegate?.handleBackAction?(try MVMCoreActionHandler.convertActionToJSON(model), additionalData: additionalData) ?? MVMCoreNavigationHandler.shared()?.removeCurrentViewController() - } catch { - MVMCoreActionHandler.shared()?.handle(error: error, model: model, delegateObject: delegateObject, additionalData: additionalData) + let json = try MVMCoreActionHandler.convertActionToJSON(model) + // TODO: Make this actually async properly. + if delegateObject?.actionDelegate?.handleBackAction != nil { + delegateObject?.actionDelegate?.handleBackAction?(json, additionalData: additionalData) + } else { + Task { + await MVMCoreNavigationHandler.shared()?.removeCurrentViewController() + } } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift index 47730a4..644bbb1 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift @@ -11,8 +11,9 @@ import Foundation open class ActionCallHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionCallModel else { return } - MVMCoreActionUtility.linkAway("tel://\(model.actionType)", appURLString: nil) + // https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/PhoneLinks/PhoneLinks.html#//apple_ref/doc/uid/TP40007899-CH6-SW1 + try await ActionOpenUrlHandler.openURL(with: "tel://\(model.callNumber)") } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift index d52885c..6206f4e 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift @@ -11,7 +11,7 @@ import Foundation open class ActionCancelHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { delegateObject?.actionDelegate?.handleCancel?(model.toJSON(), additionalData: additionalData) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift new file mode 100644 index 0000000..18ac4ec --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift @@ -0,0 +1,116 @@ +// +// ActionContactHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 7/12/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation +import ContactsUI + +open class ActionContactHandler: NSObject, MVMCoreActionHandlerProtocol, CNContactPickerDelegate, CNContactViewControllerDelegate { + + /// A continuation to keep the process running until we are finished. + private var continuation: CheckedContinuation? + + required public override init() {} + + /// Sets the continuation and runs the closure. + private func continueInTask(with closure: @escaping () async -> Void) async { + let _: Bool = await withCheckedContinuation { continuation in + self.continuation = continuation + Task { + await closure() + } + } + } + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) async throws { + guard let model = model as? ActionContactModel else { return } + + switch model.approach { + case .add: + await continueInTask { + await MainActor.run { + let controller = CNContactPickerViewController() + + // Setting to accessibilityValue as a workaround to pass data via the delegate function. + controller.view.accessibilityIdentifier = model.phoneNumber + controller.delegate = self + MVMCoreNavigationHandler.shared()?.present(controller, animated: true) + } + } + case .create: + let contact = CNMutableContact() + let phone = CNLabeledValue(label: CNLabelOther, value: CNPhoneNumber(stringValue: model.phoneNumber)) + contact.phoneNumbers = [phone] + if let givenName = model.firstName { + contact.givenName = givenName + } + if let familyName = model.lastName { + contact.familyName = familyName + } + + let store = CNContactStore() + await continueInTask { + await MainActor.run { + let controller = CNContactViewController(forNewContact: contact) + controller.contactStore = store + controller.delegate = self + MVMCoreNavigationHandler.shared()?.push(controller, animated: true) + } + } + case .view: + let symbols = CharacterSet(charactersIn: ".+()-  ") + let contactPhoneNumber = model.phoneNumber.components(separatedBy: symbols).joined(separator: "") + let number = CNPhoneNumber(stringValue: contactPhoneNumber) + let contactPredicate = CNContact.predicateForContacts(matching: number) + let displayedKeys = [await CNContactViewController.descriptorForRequiredKeys()] + let store = CNContactStore() + let viewContact = try store.unifiedContacts(matching: contactPredicate, keysToFetch: displayedKeys).first + await continueInTask { + await MainActor.run { + let controller = CNContactViewController(forNewContact: viewContact) + controller.contactStore = store + controller.delegate = self + MVMCoreNavigationHandler.shared()?.push(controller, animated: true) + } + } + } + } + + public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) { + MVMCoreNavigationHandler.shared()?.popTopViewController(animated: true, navigationController: nil, delegate: nil, completionHandler: { + self.continuation?.resume(returning: true) + }) + } + + public func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool { + true + } + + public func contactPickerDidCancel(_ picker: CNContactPickerViewController) { + MVMCoreNavigationHandler.shared()?.dismissTopViewController(animated: true, delegate: nil, completionHandler: { + self.continuation?.resume(returning: true) + }) + } + + public func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) { + // This is a means to pass the data to this delegate function. + guard let phoneNumber = picker.view.accessibilityIdentifier else { return } + let store = CNContactStore() + let existingContact = contact.mutableCopy() as! CNMutableContact + let number = CNPhoneNumber(stringValue: phoneNumber) + let labelValue = CNLabeledValue(label: CNLabelOther, value: number) + var phoneNumbers = [labelValue] + phoneNumbers.append(contentsOf: existingContact.phoneNumbers) + existingContact.phoneNumbers = phoneNumbers + MVMCoreDispatchUtility.performBlock(onMainThread: { + let controller = CNContactViewController(forNewContact: existingContact) + controller.contactStore = store + controller.delegate = self + MVMCoreNavigationHandler.shared()?.push(controller, animated: true) + }) + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionContactModel.swift b/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift similarity index 85% rename from MVMCore/MVMCore/Models/ActionType/ActionContactModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionContactModel.swift index f473042..3f4c2f8 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionContactModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift @@ -14,13 +14,19 @@ import ContactsUI // MARK: - Properties //-------------------------------------------------- + public enum Approach: String, Codable { + case add + case create + case view + } + public static var identifier: String = "contact" public var actionType: String = ActionContactModel.identifier public var phoneNumber: String public var firstName: String? public var lastName: String? - public var approach: String = KeyCreate + public var approach: Approach = .create public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? @@ -28,7 +34,7 @@ import ContactsUI // MARK: - Initializer //-------------------------------------------------- - public init(phoneNumber: String, firstName: String? = nil, lastName: String? = nil, approach: String = KeyCreate, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + public init(phoneNumber: String, firstName: String? = nil, lastName: String? = nil, approach: Approach = .create, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { self.phoneNumber = phoneNumber self.firstName = firstName self.lastName = lastName diff --git a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift index ac3af3e..f2608f3 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift @@ -11,5 +11,5 @@ import Foundation open class ActionNoopHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) {} + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {} } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift new file mode 100644 index 0000000..1aa1cc9 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -0,0 +1,69 @@ +// +// OpenPageHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 7/14/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +// TODO: Modernize this. +open class ActionOpenPageHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + guard let model = model as? ActionOpenPageModel else { return } + var requestParameters: MVMCoreRequestParameters + if let _ = delegateObject?.actionDelegate?.getRequestParameters { + requestParameters = try delegateObject!.actionDelegate!.getRequestParameters(for: model, delegateObject: delegateObject, additionData: additionalData) + } else { + let json = try MVMCoreActionHandler.convertActionToJSON(model) + requestParameters = MVMCoreRequestParameters(actionMap: json)! + } + try await performRequestAddingClientParameters(with: requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) + } + + /// Adds client parameters and makes calls performRequest() + open func performRequestAddingClientParameters(with requestParameters: MVMCoreRequestParameters, model: ActionOpenPageModel, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + // Adds any client parameters to the request parameters. + if let parametersToFetch = model.clientParameters, + let fetchedParameters = try await getClientParameters(with: parametersToFetch, requestParameters: requestParameters.parameters as? [String : Any] ?? [:], showLoadingOverlay: !requestParameters.backgroundRequest) { + requestParameters.add(fetchedParameters) + } + + // Makes the request and waits for it. + try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: requestParameters, delegateObject: delegateObject, additionalData: additionalData) + } + + /// Iterates through the clientParameters list. Gets values from the individual handlers and attaches the parameters to extraParameters. + func getClientParameters(with model: ClientParameterModel, requestParameters: [String: Any], showLoadingOverlay: Bool) async throws -> [String: Any]? { + + if showLoadingOverlay { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.startLoading() + } + + do { + let parameters: [String: Any]? = try await withCheckedThrowingContinuation({ continuation in + do { + let handler = ClientParameterHandler() + try handler.getParameters(with: model, requestParameters: requestParameters) { parameters in + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) + continuation.resume(returning: parameters) + } + } catch { + continuation.resume(throwing: error) + } + }) + if showLoadingOverlay { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) + } + return parameters + } catch { + if showLoadingOverlay { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) + } + throw error + } + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift similarity index 95% rename from MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift index a646ef7..3e8620b 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift @@ -13,7 +13,7 @@ //-------------------------------------------------- public class var identifier: String { "openPage" } - public var actionType: String { ActionOpenPageModel.identifier } + public var actionType: String = identifier public var pageType: String public var modules: [String]? public var baseURL: String? diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift index 67aa4bd..0adbe69 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift @@ -8,11 +8,26 @@ import Foundation +extension String { + public enum StringError: Error { + case addingPercentEncoding(string: String) + } + + func addingPercentEncodingThrowable(withAllowedCharacters characterSet: CharacterSet) throws -> String { + guard let string = addingPercentEncoding(withAllowedCharacters: characterSet) else { + throw StringError.addingPercentEncoding(string: self) + } + return string + } +} + open class ActionOpenSMSHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenSMSModel else { return } - MVMCoreActionUtility.linkAway("sms:\(model.phoneNumber)&body=\(model.message ?? "")".addingPercentEncoding(withAllowedCharacters: CharacterSet(charactersIn: "0123456789+-.")), appURLString: nil) + // https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/SMSLinks/SMSLinks.html#:~:text=Note%3A%20SMS%20text%20links%20are,number%20of%20the%20SMS%20message. + let string = try "sms:\(model.phoneNumber)&body=\(model.message ?? "")".addingPercentEncodingThrowable(withAllowedCharacters: CharacterSet.urlQueryAllowed) + try await ActionOpenUrlHandler.openURL(with: string) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift index 991ed89..177cc98 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift @@ -8,43 +8,105 @@ import Foundation -open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { - required public init() {} - - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - guard let model = model as? ActionOpenUrlModel else { return } - MVMCoreDispatchUtility.performBlock(onMainThread: { [self] in - // Try loading the app url first, otherwise fall back to browser url. - guard let appURL = model.appURL else { - openURL(model: model, additionalData: additionalData, delegateObject: delegateObject) - return +public extension URL { + private enum URLError: MVMError, CustomStringConvertible { + case invalid(string: String) + + public var description: String { + switch self { + case URLError.invalid(let string): + return "Failed to create url with string: \(string)" } - UIApplication.shared.open(appURL, options: model.appURLOptions?.options ?? [:]) { loaded in - guard !loaded else { return } - MVMCoreLoggingHandler.shared()?.handleDebugMessage("Failed to open app url: \(appURL)") - openURL(model: model, additionalData: additionalData, delegateObject: delegateObject) - } - }) - } - - /// Opens the url. - open func openURL(model: ActionOpenUrlModel, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - UIApplication.shared.open(model.browserUrl, options: [:]) { [self] loaded in - guard !loaded else { return } - handleError(model: model, additionalData: additionalData, delegateObject: delegateObject) } } - /// Handles any url loading errors. - open func handleError(model: ActionOpenUrlModel, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - if let error = MVMCoreErrorObject(title: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorTitle), - message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), - messageToLog: "Unable to load URL: \(String(describing: model.browserUrl))", code: ErrorCode.linkawayFailed.rawValue, - domain: ErrorDomainNative, - location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: ActionOpenUrlModel.identifier)) { - MVMCoreDispatchUtility.performBlock(inBackground: { - MVMCoreActionHandler.shared()?.handleActionError(error, actionInformation: model.toJSON(), additionalData: additionalData, delegateObject: delegateObject) - }) + static func createURL(with string: String) throws -> URL { + guard let url = URL(string: string) else { + throw URL.URLError.invalid(string: string) + } + return url + } +} + +public enum MVMCoreError: MVMError { + case error(code: Int, messageToDisplay: String, location: String) + case errorObject(_ object: MVMCoreErrorObject) + + public var errorCode: Int { + switch self { + case MVMCoreError.error(let code, _, _): + return code + case MVMCoreError.errorObject(let object): + return object.code + } + } + + public var description: String { + switch self { + case MVMCoreError.error(_, let message, _): + return message + case MVMCoreError.errorObject(let object): + return object.messageToDisplay ?? "Error" } } } + +protocol MVMError: LocalizedError, CustomNSError {} +extension MVMError { + public var errorDescription: String? { return MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) } + + public static var errorDomain: String { + return ErrorDomainNative + } +} + +open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + public enum URLError: MVMError, CustomStringConvertible { + case failedToOpen(url: URL) + + public var description: String { + switch self { + case ActionOpenUrlHandler.URLError.failedToOpen(let url): + return "Failed to open url: \(url.absoluteString)" + } + } + } + + public static func openURL(with string: String) async throws { + let url = try URL.createURL(with: string) + try await ActionOpenUrlHandler.open(url: url) + } + + @MainActor public static func open(url: URL) async throws { + try await withCheckedThrowingContinuation { continuation in + UIApplication.shared.open(url, options: [:]) { successful in + if successful { + continuation.resume() + } else { + continuation.resume(throwing: ActionOpenUrlHandler.URLError.failedToOpen(url: url)) + } + } + } as Void + } + + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + guard let model = model as? ActionOpenUrlModel else { return } + + // Try loading the app url first, otherwise fall back to browser url. + if let appURL = model.appURL { + do { + try await ActionOpenUrlHandler.open(url: appURL) + return + } catch { + // Log error and continue + MVMCoreLoggingHandler.shared()?.handleDebugMessage("Failed to open app url: \(appURL)") + if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) { + MVMCoreLoggingHandler.addError(toLog: errorObject) + } + } + } + try await ActionOpenUrlHandler.open(url: model.browserUrl) + } +} diff --git a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift index e803ab7..1717446 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift @@ -12,13 +12,23 @@ import Foundation open class ActionPreviousSubmitHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - guard let model = model as? ActionPreviousSubmitModel else { return } - let json = model.toJSON() - delegateObject?.actionDelegate?.prepareRequest?(forPreviousSubmission: json, additionalData: additionalData, submit: { requestParameters, dataForPage in - MVMCoreActionHandler.shared()?.updateRequestParameters(beforeHandleOpenPageAction: requestParameters, callBack: { requestParameters in - delegateObject?.actionDelegate?.handleOpenPage?(for: requestParameters, actionInformation: json, additionalData: dataForPage) ?? MVMCoreActionHandler.defaultHandleOpenPage(for: requestParameters, actionInformation: json, additionalData: additionalData, delegateObject: delegateObject) - }) - }) + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + guard let loadObject = (delegateObject?.actionDelegate as? MVMCoreViewControllerProtocol)?.loadObject, + let previousRequest = loadObject?.requestParameters else { return } + + // Combines data + var data = loadObject?.dataForPage + if let previousData = data, + let additionalData = additionalData { + data = previousData.merging(additionalData) {(new,_) in new} + } + + if let _ = delegateObject?.actionDelegate?.handleOpenPage { + // Legacy handling. Will lose the task. + let json = try MVMCoreActionHandler.convertActionToJSON(model) + delegateObject?.actionDelegate?.handleOpenPage?(for: previousRequest, actionInformation: json, additionalData: additionalData) + } else { + try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: previousRequest, delegateObject: delegateObject, additionalData: additionalData) + } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift deleted file mode 100644 index e890617..0000000 --- a/MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// ActionRedirectHandler.swift -// MVMCore -// -// Created by Scott Pfeil on 6/30/22. -// Copyright © 2022 myverizon. All rights reserved. -// - -import Foundation - -open class ActionRedirectHandler: MVMCoreActionHandlerProtocol { - required public init() {} - - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - // Have delegate redirect. - MVMCoreSessionObject.sharedGlobal()?.redirect(withInfo: model.toJSON()) - } -} diff --git a/MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift b/MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift deleted file mode 100644 index d36722e..0000000 --- a/MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// ActionRedirectModel.swift -// MVMCore -// -// Created by Scott Pfeil on 6/30/22. -// Copyright © 2022 myverizon. All rights reserved. -// - -import Foundation - -@objcMembers public class ActionRedirectModel: ActionOpenPageModel { - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - - public override class var identifier: String { KeyActionTypeRedirect } - public override var actionType: String { ActionRedirectModel.identifier } -} diff --git a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift index c468fe6..e8bbd7b 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift @@ -11,18 +11,25 @@ import Foundation open class ActionRestartHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionRestartModel else { return } - - // Invalidates the session before restarting. - MVMCoreSessionTimeHandler.sharedSession()?.invalidateSession({ error in - guard error?.code != NSURLErrorCancelled else { return } - if let error = error { - MVMCoreActionHandler.shared()?.handle(errorObject: error, model: model, delegateObject: delegateObject, additionalData: additionalData) - } else { - // Restarts the app (forcing any passed in page types). - MVMCoreSessionObject.sharedGlobal()?.restartSession(withPageType: model.pageType, parameters: model.extraParameters, clearAllVariables: true) - } - }) + + let _: Bool = try await withCheckedThrowingContinuation { continuation in + + // Invalidates the session before restarting. + MVMCoreSessionTimeHandler.sharedSession()?.invalidateSession({ error in + if let error = error { + guard error.code != NSURLErrorCancelled else { + continuation.resume(returning: false) + return + } + continuation.resume(throwing: MVMCoreError.errorObject(error)) + } else { + // Restarts the app (forcing any passed in page types). + MVMCoreSessionObject.sharedGlobal()?.restartSession(withPageType: model.pageType, parameters: model.extraParameters, clearAllVariables: true) + continuation.resume(returning: true) + } + }) + } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift index 9d09408..bea0fd1 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift @@ -11,7 +11,7 @@ import Foundation open class ActionSettingHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - MVMCoreActionUtility.linkAway(UIApplication.openSettingsURLString, appURLString: nil) + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + try await ActionOpenUrlHandler.openURL(with: await UIApplication.openSettingsURLString) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift new file mode 100644 index 0000000..7573434 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift @@ -0,0 +1,54 @@ +// +// ActionShareHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 7/14/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation +open class ActionShareHandler: MVMCoreActionHandlerProtocol { + + required public init() {} + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) async throws { + guard let model = model as? ActionShareModel else { return } + var shareData: [Any] + switch model.sharedType { + case .text: + shareData = [model.sharedText] + case .url: + let url = try URL.createURL(with: model.sharedText) + shareData = [model.sharedText, url] + } + try await shareWith(activityItems: shareData, model: model) + } + + @MainActor public func shareWith(activityItems: [Any], model: ActionShareModel) async throws { + try await withCheckedThrowingContinuation { continuation in + let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + controller.popoverPresentationController?.sourceView = MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn?.view + controller.completionWithItemsHandler = {(activityType: UIActivity.ActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in + if completed { + // Activity was completed, considered finished. + if activityType == .copyToPasteboard { + // Allow copy + MVMCoreSessionObject.sharedGlobal()?.copyString(toClipboard: model.sharedText) + } + continuation.resume() + } else if let _ = activityType { + // If a specific type of activity failed, the activity controller is still presented, cannot continue yet. + if let error = error, + let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: #function) { + MVMCoreLoggingHandler.addError(toLog: errorObject) + } + } else if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume() + } + } + MVMCoreNavigationHandler.shared()?.present(controller, animated: true) + } as Void + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionShareModel.swift b/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift similarity index 76% rename from MVMCore/MVMCore/Models/ActionType/ActionShareModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionShareModel.swift index 893c638..ed7cbea 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionShareModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift @@ -8,6 +8,12 @@ @objcMembers public class ActionShareModel: ActionModelProtocol { + + public enum SharedType: String, Codable { + case text + case url + } + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -15,7 +21,7 @@ public static var identifier: String = "share" public var actionType: String = ActionShareModel.identifier - public var sharedType: String + public var sharedType: SharedType public var sharedText: String public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? @@ -24,7 +30,7 @@ // MARK: - Initializer //-------------------------------------------------- - public init(sharedText: String, sharedType: String, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + public init(sharedText: String, sharedType: SharedType, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { self.sharedType = sharedType self.sharedText = sharedText self.extraParameters = extraParameters diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift index ab572f8..e857e84 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift @@ -15,8 +15,9 @@ public extension MVMCoreActionDelegateProtocol { return true } - /// Handles any action errors. - func handleAction(error: MVMCoreErrorObject, action: ActionModelProtocol, additionalData: [AnyHashable: Any]?) { - MVMCoreActionHandler.shared()?.defaultHandleActionError(error, additionalData: additionalData) + /// Allows the delegate to create the request parameters as desired. + func getRequestParameters(for model: ActionOpenPageModel, delegateObject: DelegateObject? = nil, additionData: [AnyHashable : Any]? = nil) throws -> MVMCoreRequestParameters { + let json = try MVMCoreActionHandler.convertActionToJSON(model) + return MVMCoreRequestParameters(actionMap: json)! } } diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h index 85616b7..e29a184 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h @@ -24,9 +24,6 @@ // Handles the back actions. Can overwrite for special loading. - (void)handleBackAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData; -// Prepares to call the previous submit request again. Can overwrite for special loading. Be sure to call submit() block to perform the actual load. -- (void)prepareRequestForPreviousSubmission:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData submit:(nonnull void (^)(MVMCoreRequestParameters * _Nonnull requestParameters, NSDictionary * _Nullable dataForPage))submit; - // Handles the linkaway action. Call the block to continue to linkaway. - (void)shouldLinkAwayWithURL:(nullable NSURL *)URL appURL:(nullable NSURL *)appURL actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData linkAwayBlock:(nonnull void (^)(NSURL * _Nullable appURL, NSURL * _Nullable URL, NSDictionary * _Nullable actionInformation, NSDictionary * _Nullable additionalData))linkAwayBlock; diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift index 4160c61..89759ce 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift @@ -8,15 +8,47 @@ import Foundation +/// Handlers that can be registered and used by the MVMCoreActionHandler to handle actions. public protocol MVMCoreActionHandlerProtocol: ModelHandlerProtocol { init() + /// Legacy function to handle actions. func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) + func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws } -public extension MVMCoreActionHandler { +extension MVMCoreActionHandlerProtocol { - /// Converts the action to json for old action handler to handle. - static func convertActionToJSON(_ model: ActionModelProtocol) throws -> [AnyHashable: Any] { + public func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + } + + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + handleAction(model, additionalData: additionalData, delegateObject: delegateObject) + } +} + +@objc open class MVMCoreActionHandler: NSObject { + + enum ActionError: MVMError { + case unknownAction(type: String) + + public var description: String { + switch self { + case MVMCoreActionHandler.ActionError.unknownAction(let type): + return "Couldn't perform action: \(type)" + } + } + } + + /// Returns the action handler stored in the MVMCoreObject + @objc(sharedActionHandler) + public static func shared() -> Self? { + return MVMCoreActionUtility.initializerClassCheck(MVMCoreObject.sharedInstance()?.actionHandler, classToVerify: self) as? Self + } + + // MARK: - Conversions + + /// Converts the action to json for legacy functions. + static public func convertActionToJSON(_ model: ActionModelProtocol) throws -> [AnyHashable: Any] { let data = try model.encode() guard let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [AnyHashable: Any] else { throw ModelRegistry.Error.decoderError @@ -25,86 +57,134 @@ public extension MVMCoreActionHandler { } /// Creates a model from the action json. - static func createModel(with json: [AnyHashable: Any]) throws -> ActionModelProtocol { + static public func createModel(with json: [AnyHashable: Any], delegateObject: DelegateObject? = nil) throws -> ActionModelProtocol { guard let castedSelf = json as? [String: Any] else { throw ModelRegistry.Error.decoderOther(message: "Dictionary is not of type [String: Any]") } guard let actionType = ModelRegistry.getType(for: castedSelf.stringForkey(KeyActionType), with: ActionModelProtocol.self) else { throw ModelRegistry.Error.decoderErrorModelNotMapped() } - guard let actionModel = try actionType.decode(jsonDict: castedSelf) as? ActionModelProtocol else { + guard let actionModel = try actionType.decode(jsonDict: castedSelf, delegateObject: delegateObject) as? ActionModelProtocol else { throw ModelRegistry.Error.decoderOther(message: "Could not decode to ActionModelProtocol") } return actionModel } - /// Converts the Error to an ErrorObject and calls handle(errorObject - func handle(error: Error, model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) { - guard let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) else { return } - handle(errorObject: errorObject, model: model, delegateObject: delegateObject, additionalData: additionalData) - } + // MARK: - Error Handling - /// Handles the error by calling actionDelegate handleAction, else ActionHandler defaultHandleActionError. - func handle(errorObject: MVMCoreErrorObject, model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) { - delegateObject?.actionDelegate?.handleAction(error: errorObject, action: model, additionalData: additionalData) ?? defaultHandleActionError(errorObject, additionalData: additionalData) - } - - @objc func hasActionHandler(actionType: String?, actionInformation: [String: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> Bool { - - guard //ensure there is a actinType - let actionType = actionType, - //ensure there is a serialized version of the Action - let actionInformation = actionInformation, - //esnure the actionModelType - let actionModelType = ModelRegistry.getType(for: actionType, with: ActionModelProtocol.self), - //ensure there is handlerType for the action of MVMCoreActionHandlerProtocol - let actionHandlerType = try? ModelRegistry.getHandlerType(for: actionModelType) as? MVMCoreActionHandlerProtocol.Type - else { return false } - - do { - //ensure the decoded actionModel is of ActionModelProtocol - guard let actionModel = try actionModelType.decode(jsonDict: actionInformation) as? ActionModelProtocol else { - throw ModelRegistry.Error.decoderOther(message: "Could not decode to ActionModelProtocol") - } - - //create the handler since we know it can initialize - let actionHandler = actionHandlerType.init() - - //call the handleAction of the handler - actionHandler.handleAction(actionModel, additionalData: additionalData, delegateObject: delegateObject) - - } catch { - //log the error - if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "") { - MVMCoreActionHandler.shared()?.defaultHandleActionError(errorObject, additionalData: additionalData) - } - } - - //found the handler, returning true no matter if there was a failure in the do...catch - return true - } - - /// Start action on current thread. - func syncHandleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - do { - let json = try MVMCoreActionHandler.convertActionToJSON(model) - synchronouslyHandleAction(model.actionType, actionInformation: json, additionalData: additionalData, delegateObject: delegateObject) - } catch { - handle(error: error, model: model, delegateObject: delegateObject, additionalData: additionalData) + /// Converts the Error into an ErrorObject. + open func getActionErrorObject(for error: Error, actionType: String, delegateObject: DelegateObject? = nil) -> MVMCoreErrorObject { + switch error { + case MVMCoreError.errorObject(let object): + return object + default: + return MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: actionType))! } } - /// Start action on dispatched background thread. - func asyncHandleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - do { - let json = try MVMCoreActionHandler.convertActionToJSON(model) - handleAction(model.actionType, actionInformation: json, additionalData: additionalData, delegateObject: delegateObject) - } catch { - handle(error: error, model: model, delegateObject: delegateObject, additionalData: additionalData) - } + /// Handles the error by calling actionDelegate handleActionError, else ActionHandler defaultHandleActionError. + open func handle(errorObject: MVMCoreErrorObject, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) { + delegateObject?.actionDelegate?.handleActionError?(errorObject, additionalData: additionalData) ?? MVMCoreActionHandler.shared()?.defaultHandleActionError(errorObject, additionalData: additionalData) } - @objc static func getErrorLocation(with delegate: MVMCoreActionDelegateProtocol?, actionType: String) -> String { + /// Returns a common description for the error location. + @objc public static func getErrorLocation(with delegate: MVMCoreActionDelegateProtocol?, actionType: String) -> String { return "\(String(describing: delegate))_\(actionType)" } + + // MARK: - Action Handling + + /// Legacy handle action with json. + @objc(handleActionWithDictionary:additionalData:delegateObject:) + open func handleAction(with json: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: json \(String(describing: json))") + Task(priority: .userInitiated) { + do { + guard let json = json else { + throw ModelRegistry.Error.keyNotFound + } + let model = try MVMCoreActionHandler.createModel(with: json, delegateObject: delegateObject) + _ = asyncHandleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + } catch { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Error \(error)") + let errorObject = getActionErrorObject(for: error, actionType: json?.stringForkey(KeyActionType) ?? "noAction", delegateObject: delegateObject) + handle(errorObject: errorObject, delegateObject: delegateObject, additionalData: additionalData) + } + } + } + + /// Handle an action with the given model. + open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { + try Task.checkCancellation() + // Allow the delegate to intercept. + guard delegateObject?.actionDelegate?.shouldPerform(action: model, additionalData: additionalData, delegateObject: delegateObject) ?? true else { return } + + // Log the action + logAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + let uuid = UUID() + do { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Begin Action \(model.actionType) \(uuid)") + let handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type + let handler = handlerType.init() + try await handler.performAction(model, delegateObject: delegateObject, additionalData: additionalData) + } catch ModelRegistry.Error.handlerNotMapped { + try Task.checkCancellation() + // Allows custom handling if there no handler for the action. + guard try await handleUnregisteredAction(with: model, additionalData: additionalData, delegateObject: delegateObject) else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Failed Action Unknown \(model.actionType) \(uuid)") + throw ActionError.unknownAction(type: model.actionType) + } + } catch { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Failed Action \(error) \(model.actionType) \(uuid)") + throw error + } + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Successful Action \(model.actionType) \(uuid)") + } + + /// Performs the action as a task and returns immediately. + open func asyncHandleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> Task { + let task = Task(priority: .userInitiated) { + try Task.checkCancellation() + do { + try await handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + } catch { + try Task.checkCancellation() + let errorObject = getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) + handle(errorObject: errorObject, delegateObject: delegateObject, additionalData: additionalData) + } + } + + Task { + //try await Task.sleep(nanoseconds: 1 * 1_000_000_000) + //task.cancel() + } + Task { + let result = await task.result + do { + try result.get() + print("ActionHandler: task done") + } catch { + print("ActionHandler: \(error)") + } + } + return task + } + + // MARK: - Subclassables + + /// Subclass to log the action was fired. + open func logAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + delegateObject?.actionDelegate?.logAction?(withActionInformation: model.toJSON(), additionalData: additionalData) + } + + /// Subclass to handle and any actions where a handler was not registered. Return if it was handled or not. + open func handleUnregisteredAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws -> Bool { + return false + } + + /// Logs the error. + @objc open func defaultHandleActionError(_ error: MVMCoreErrorObject, additionalData: [AnyHashable: Any]?) { + guard error.logError else { return } + MVMCoreLoggingHandler.addError(toLog: error) + } } diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h index 1854927..434a083 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h @@ -7,72 +7,61 @@ // // Can be subclassed to handle app specific actions as well. -#import -#import -#import -#import -@class DelegateObject; - -extern NSString * _Nonnull const KeyActionType; -extern NSString * _Nonnull const KeyActionTypeLinkAway; -extern NSString * _Nonnull const KeyActionTypeOpen; - -@interface MVMCoreActionHandler : NSObject - -/// Returns the shared action handler -+ (nullable instancetype)sharedActionHandler; - -/// Convenience function for handling actions. This will pull action and pageInfo out of the dictionary and call handleAction: actionInformation: with those values -- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Asynchronously handles action (dispatches and calls below function). Used by server driven user actions.. -- (void)handleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Synchronously handles action. Used by server driven user actions.. -- (void)synchronouslyHandleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Iterates through the clientParameters list. Gets values from the individual handlers and attaches the parameters to extraParameters. -- (void)getClientParameter:(nullable NSDictionary *)clientParametersMap requestParameters:(nullable NSDictionary *)requestParameters showLoadingOverlay:(BOOL)showLoadingOverlay completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler; - -#pragma mark - Actions -/// by default, returns the original RequestParameter that passed in. Can be overriden for some generic updates to the RequestParameter before handle open page action gets called. -- (void)updateRequestParametersBeforeHandleOpenPageAction:(nonnull MVMCoreRequestParameters *)requestParameters callBack:(void (^_Nonnull)(MVMCoreRequestParameters * _Nonnull requestParameters))callback; - -/// Logs the action. Currently is not action information driven... depends on delegate. -- (void)logAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Tries to open a page -- (void)openPageAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Goes back -- (void)backAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Subclass this to handle other custom actions. Return YES if handled, and NO if not. -- (BOOL)handleOtherActions:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Last chance to handle unknown actions before throwing an error -- (void)unknownAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Handles action errors. -- (void)handleActionError:(nullable MVMCoreErrorObject *)error actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -#pragma mark - Default Action Protocol Functions - -/// Currently no default log action but this will eventually be server driven. -+ (void)defaultLogAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Sends the request to the load handler. -+ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// By default, throws an error, calling defaultHandleActionError. -+ (void)defaultHandleUnknownActionType:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Logs the error. -- (void)defaultHandleActionError:(nonnull MVMCoreErrorObject *)error additionalData:(nullable NSDictionary *)additionalData; - -#pragma mark - Deprecated - -/// Convenience function for handling actions. This will pull action and pageInfo out of the dictionary and call handleAction: actionInformation: with those values -- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate __deprecated; - -@end +//#import +//#import +//#import +//#import +//@class DelegateObject; +// +//extern NSString * _Nonnull const KeyActionType; +//extern NSString * _Nonnull const KeyActionTypeLinkAway; +//extern NSString * _Nonnull const KeyActionTypeOpen; +// +//@interface MVMCoreActionHandler : NSObject +// +///// Returns the shared action handler +//+ (nullable instancetype)sharedActionHandler; +// +///// Convenience function for handling actions. This will pull action and pageInfo out of the dictionary and call handleAction: actionInformation: with those values +//- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Asynchronously handles action (dispatches and calls below function). Used by server driven user actions.. +//- (void)handleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Iterates through the clientParameters list. Gets values from the individual handlers and attaches the parameters to extraParameters. +//- (void)getClientParameter:(nullable NSDictionary *)clientParametersMap requestParameters:(nullable NSDictionary *)requestParameters showLoadingOverlay:(BOOL)showLoadingOverlay completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler; +// +//#pragma mark - Actions +// +///// Logs the action. Currently is not action information driven... depends on delegate. +//- (void)logAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Subclass this to handle other custom actions. Return YES if handled, and NO if not. +//- (BOOL)handleOtherActions:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Last chance to handle unknown actions before throwing an error +//- (void)unknownAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Handles action errors. +//- (void)handleActionError:(nullable MVMCoreErrorObject *)error actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +//#pragma mark - Default Action Protocol Functions +// +///// Currently no default log action but this will eventually be server driven. +//+ (void)defaultLogAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Sends the request to the load handler. +//+ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// By default, throws an error, calling defaultHandleActionError. +//+ (void)defaultHandleUnknownActionType:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Logs the error. +//- (void)defaultHandleActionError:(nonnull MVMCoreErrorObject *)error additionalData:(nullable NSDictionary *)additionalData; +// +//#pragma mark - Deprecated +// +///// Convenience function for handling actions. This will pull action and pageInfo out of the dictionary and call handleAction: actionInformation: with those values +//- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate __deprecated; +// +//@end diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m index f3974f0..37a6a02 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m @@ -1,379 +1,176 @@ +//// +//// MVMCoreActionHandler.m +//// myverizon +//// +//// Created by Scott Pfeil on 11/20/15. +//// Copyright © 2015 Verizon Wireless. All rights reserved. +//// // -// MVMCoreActionHandler.m -// myverizon +//#import +//#import "MVMCoreLoggingHandler.h" +//#import "MVMCoreCache.h" +//#import "MVMCoreSessionTimeHandler.h" +//#import "MVMCoreLoadHandler.h" +//#import "MVMCoreNavigationHandler.h" +//#import "MVMCoreDispatchUtility.h" +//#import "NSDictionary+MFConvenience.h" +//#import "MVMCoreGetterUtility.h" +//#import "MVMCoreRequestParameters.h" +//#import "MVMCoreErrorObject.h" +//#import "MVMCoreJSONConstants.h" +//#import "MVMCoreHardcodedStringsConstants.h" +//#import "MVMCoreErrorConstants.h" +//#import "MVMCoreActionUtility.h" +//#import "MVMCoreSessionObject.h" +//#import "MVMCoreObject.h" +//#import "MVMCorePresentationDelegateProtocol.h" +//#import +//#import +//#import "MVMCoreLoadingOverlayHandler.h" +//#import // -// Created by Scott Pfeil on 11/20/15. -// Copyright © 2015 Verizon Wireless. All rights reserved. +//NSString * const KeyActionType = @"actionType"; +//NSString * const KeyActionTypeLinkAway = @"openURL"; +//NSString * const KeyActionTypeOpen = @"openPage"; // - -#import -#import "MVMCoreLoggingHandler.h" -#import "MVMCoreCache.h" -#import "MVMCoreSessionTimeHandler.h" -#import "MVMCoreLoadHandler.h" -#import "MVMCoreNavigationHandler.h" -#import "MVMCoreDispatchUtility.h" -#import "NSDictionary+MFConvenience.h" -#import "MVMCoreGetterUtility.h" -#import "MVMCoreRequestParameters.h" -#import "MVMCoreErrorObject.h" -#import "MVMCoreJSONConstants.h" -#import "MVMCoreHardcodedStringsConstants.h" -#import "MVMCoreErrorConstants.h" -#import "MVMCoreActionUtility.h" -#import "MVMCoreSessionObject.h" -#import "MVMCoreObject.h" -#import "MVMCorePresentationDelegateProtocol.h" -#import -#import -#import "MVMCoreLoadingOverlayHandler.h" -#import - -NSString * const KeyActionType = @"actionType"; -NSString * const KeyActionTypeLinkAway = @"openURL"; -NSString * const KeyActionTypeOpen = @"openPage"; - -@interface MVMCoreActionHandler() -@end - -@implementation MVMCoreActionHandler -+ (nullable instancetype)sharedActionHandler { - return [MVMCoreActionUtility initializerClassCheck:[MVMCoreObject sharedInstance].actionHandler classToVerify:self]; -} - -- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - NSString *action = [dictionary stringForKey:KeyActionType]; - [self handleAction:action actionInformation:dictionary additionalData:additionalData delegateObject:delegateObject]; -} - -- (void)handleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self synchronouslyHandleAction:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; - }); -} - -- (void)synchronouslyHandleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - // Logs the action. - [self logAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - if ([actionType isEqualToString:KeyActionTypeOpen]) { - [self openPageAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeContact]) { - [self contactAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeShare]) { - [self shareAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if (![self handleOtherActions:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]) { - // not a known action type. - [self unknownAction:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; - } -} - -- (void)getClientParameter:(nullable NSDictionary *)clientParametersMap requestParameters:(nullable NSDictionary *)requestParameters showLoadingOverlay:(BOOL)showLoadingOverlay completionHandler:(nonnull void (^)(NSDictionary * _Nullable parameters))completionHandler { - - if (!clientParametersMap) { - completionHandler(nil); - return; - } - - if (showLoadingOverlay) { - [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading]; - } - - void (^stopLoadingOverlay)(void) = ^(void) { - if (showLoadingOverlay) { - [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:true]; - } - }; - - NSError *error = nil; - [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"Fetching client parameters"]; - - ClientParameterHandler *clientParameterHandler = [[ClientParameterHandler alloc] init]; - [clientParameterHandler getParametersWith:clientParametersMap - requestParameters:requestParameters - error:&error - completionHandler:^(NSDictionary * _Nullable clientParameters) { - [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"Finshed fetching client parameters"]; - if (clientParameters) { - stopLoadingOverlay(); - completionHandler(clientParameters); - } else { - [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"No client parameters"]; - stopLoadingOverlay(); - completionHandler(nil); - } - }]; - - if (error) { - stopLoadingOverlay(); - completionHandler(nil); - [MVMCoreLoggingHandler addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:@"MVMCoreActionHandler->setClientParameter"]]; - } -} - -#pragma mark - Actions - -- (void)updateRequestParametersBeforeHandleOpenPageAction:(nonnull MVMCoreRequestParameters *)requestParameters callBack:(void (^_Nonnull)(MVMCoreRequestParameters * _Nonnull requestParameters))callback { - //does not do anything by default, can be override - callback(requestParameters); -} - -- (void)logAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - if ([delegateObject.actionDelegate respondsToSelector:@selector(logActionWithActionInformation:additionalData:)]) { - [delegateObject.actionDelegate logActionWithActionInformation:actionInformation additionalData:additionalData]; - } else { - [[self class] defaultLogAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - } -} - -- (void)openPageAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - // Loads the given page type. - NSString *pageType = [actionInformation stringForKey:KeyPageType]; - - if (pageType.length == 0) { - // No page type to load, show error. - MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess] code:ErrorCodeNoPageType domain:ErrorDomainNative location:[NSString stringWithFormat:@"%@_%@",NSStringFromClass([delegateObject.actionDelegate class]),KeyActionTypeOpen]]; - [self handleActionError:error actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; - return; - } - - MVMCoreRequestParameters *requestParameters = [[MVMCoreRequestParameters alloc] initWithActionMap:actionInformation]; - [self updateRequestParametersBeforeHandleOpenPageAction:requestParameters callBack:^(MVMCoreRequestParameters * _Nonnull requestParameters) { - if ([delegateObject.actionDelegate respondsToSelector:@selector(handleOpenPageForRequestParameters:actionInformation:additionalData:)]) { - [delegateObject.actionDelegate handleOpenPageForRequestParameters:requestParameters actionInformation:actionInformation additionalData:additionalData]; - } else { - [MVMCoreActionHandler defaultHandleOpenPageForRequestParameters:requestParameters - actionInformation:actionInformation - additionalData:additionalData - delegateObject:delegateObject]; - } - }]; -} - -- (void)shareAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - NSString *shareType = [actionInformation string:KeyShareType]; - NSString *shareText = [actionInformation string:KeyShareText]; - - if (!shareText || !shareType) { - return; - } - - NSArray *shareData = nil; - - if ([shareType isEqualToString:@"text"]) { - shareData = @[shareText]; - - } else if ([shareType isEqualToString:@"url"]) { - NSURL *url = [NSURL URLWithString:shareText]; - if (url) { - shareData = @[shareText, url]; - } else { - shareData = @[shareText]; - } - } else if ([shareType isEqualToString:@"image"]) { - // TODO: Implement image parsing. 🏂 - - } else if ([shareType isEqualToString:@"file"]) { - // TODO: Implement file parsing. 🌋 - } - - if (shareData.count > 0) { - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:shareData applicationActivities:nil]; - - void(^activityCompletion)(UIActivityType, BOOL, NSArray*, NSError*) = ^(UIActivityType activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { - if (activityType == UIActivityTypeCopyToPasteboard) { - [[MVMCoreSessionObject sharedGlobal] copyStringToClipboard:shareText]; - } - }; - - activityViewController.completionWithItemsHandler = activityCompletion; - activityViewController.popoverPresentationController.sourceView = [MVMCoreNavigationHandler sharedNavigationHandler].viewControllerToPresentOn.view; - [[MVMCoreNavigationHandler sharedNavigationHandler] presentViewController:activityViewController animated:YES]; - }]; - } -} - -- (void)backAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - [self handleActionWithDictionary:actionInformation ?: @{KeyActionType: KeyActionTypeBack} additionalData:additionalData delegateObject:delegateObject]; -} - -- (void)contactAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - __weak typeof(self) weakSelf = self; - - NSString *phoneNumber = [actionInformation string:@"phoneNumber"]; - - if (!phoneNumber) { return; } - CNMutableContact *contact = [[CNMutableContact alloc] init]; - NSString *approach = [actionInformation stringForKey:@"approach"]; - - CNLabeledValue *phone = [[CNLabeledValue alloc] initWithLabel:CNLabelOther value:[[CNPhoneNumber alloc] initWithStringValue:phoneNumber]]; - contact.phoneNumbers = @[phone]; - - if ([approach isEqualToString:KeyAdd]) { - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - CNContactPickerViewController *controller = [[CNContactPickerViewController alloc] init]; - // Setting to accessibilityValue as a workaround to pass data via the delegate function. - [controller.view setAccessibilityIdentifier:phoneNumber]; - controller.delegate = weakSelf; - - [[MVMCoreNavigationHandler sharedNavigationHandler] presentViewController:controller animated:YES]; - }]; - } else if ([approach isEqualToString:KeyCreate]) { - contact.givenName = [actionInformation string:@"firstName"]; - contact.familyName = [actionInformation string:@"lastName"]; - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - CNContactStore *store = [[CNContactStore alloc] init]; - CNContactViewController *controller = [CNContactViewController viewControllerForNewContact:contact]; - controller.contactStore = store; - controller.delegate = weakSelf; - - [[MVMCoreNavigationHandler sharedNavigationHandler] pushViewController:controller animated:YES]; - }]; - } else if ([approach isEqualToString:KeyView]) { - NSCharacterSet *symbols = [NSCharacterSet characterSetWithCharactersInString:@".+()-  "]; - NSString *contactPhoneNumber = [actionInformation string:@"phoneNumber"]; - contactPhoneNumber = [[contactPhoneNumber componentsSeparatedByCharactersInSet:symbols] componentsJoinedByString:@""]; - CNPhoneNumber *number = [[CNPhoneNumber alloc] initWithStringValue:contactPhoneNumber]; - NSPredicate *contactPredicate = [CNContact predicateForContactsMatchingPhoneNumber:number]; - NSArray *displayedKeys = @[[CNContactViewController descriptorForRequiredKeys]]; - CNContactStore *store = [[CNContactStore alloc] init]; - NSError *error; - CNContact *viewContact = [[store unifiedContactsMatchingPredicate:contactPredicate keysToFetch:displayedKeys error:&error] firstObject]; - - if (viewContact) { - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - CNContactViewController *controller = [CNContactViewController viewControllerForNewContact:viewContact]; - controller.contactStore = store; - controller.delegate = weakSelf; - - [[MVMCoreNavigationHandler sharedNavigationHandler] pushViewController:controller animated:YES]; - }]; - } else { - // No contacts found, show an alert - MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorContactUnAvailable] code:ErrorCodeDefault domain:ErrorDomainNative location:[NSString stringWithFormat:@"%@_%@",NSStringFromClass([delegateObject.actionDelegate class]),KeyActionTypeContact]]; - error.silentError = NO; - [self handleActionError:error actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; - } - } -} - -- (BOOL)handleOtherActions:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - return [self hasActionHandlerWithActionType:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; -} - -- (void)unknownAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - if ([delegateObject.actionDelegate respondsToSelector:@selector(handleUnknownActionType:actionInformation:additionalData:)]) { - [delegateObject.actionDelegate handleUnknownActionType:actionType actionInformation:actionInformation additionalData:additionalData]; - } else { - [MVMCoreActionHandler defaultHandleUnknownActionType:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; - } -} - -- (void)handleActionError:(nullable MVMCoreErrorObject *)error actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - if (error) { - if ([delegateObject.actionDelegate respondsToSelector:@selector(handleActionError:additionalData:)]) { - [delegateObject.actionDelegate handleActionError:error additionalData:additionalData]; - } else { - [self defaultHandleActionError:error additionalData:additionalData]; - } - } -} - -#pragma mark - CNContactViewControllerDelegate - -- (void)contactViewController:(CNContactViewController *)viewController didCompleteWithContact:(CNContact *)contact { - [[MVMCoreNavigationHandler sharedNavigationHandler] removeCurrentViewController]; -} - -- (BOOL)contactViewController:(CNContactViewController *)viewController shouldPerformDefaultActionForContactProperty:(CNContactProperty *)property { - return YES; -} - -#pragma mark - CNContactPickerDelegate - -- (void)contactPickerDidCancel:(CNContactPickerViewController *)picker { - [[MVMCoreNavigationHandler sharedNavigationHandler] removeCurrentViewController]; -} - -- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact { - - // This is a means to pass the data to this delegate function. - NSString *phoneNumber = picker.view.accessibilityIdentifier; - - if (!phoneNumber) { return; } - CNContactStore *store = [[CNContactStore alloc] init]; - CNMutableContact *existingContact = [(CNMutableContact *)contact mutableCopy]; - CNPhoneNumber *number = [[CNPhoneNumber alloc] initWithStringValue:phoneNumber]; - CNLabeledValue *labelValue = [[CNLabeledValue alloc] initWithLabel:CNLabelOther value:number]; - NSMutableArray *phoneNumbers = [NSMutableArray new]; - [phoneNumbers addObject:labelValue]; - [phoneNumbers addObjectsFromArray:existingContact.phoneNumbers]; - existingContact.phoneNumbers = phoneNumbers; - - __weak typeof(self) weakSelf = self; - - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - CNContactViewController *controller = [CNContactViewController viewControllerForNewContact:existingContact]; - controller.contactStore = store; - controller.delegate = weakSelf; - - [[MVMCoreNavigationHandler sharedNavigationHandler] pushViewController:controller animated:YES]; - }]; -} - -#pragma mark - Default Action Protocol - -+ (void)defaultLogAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject{ - // Currently no default log action but this will eventually be server driven. -} - -+ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - NSDictionary *clientParamters = [actionInformation dict:KeyClientParameters]; - if (clientParamters) { - [[MVMCoreActionHandler sharedActionHandler] getClientParameter:clientParamters - requestParameters: requestParameters.parameters - showLoadingOverlay: !requestParameters.backgroundRequest - completionHandler: ^(NSDictionary * _Nullable jsonDictionary) { - [requestParameters addRequestParameters:jsonDictionary]; - [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegateObject:delegateObject]; - }]; - } else { - [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegateObject:delegateObject]; - } -} - -+ (void)defaultHandleUnknownActionType:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess] code:ErrorCodeUnknownActionType domain:ErrorDomainNative location:[NSString stringWithFormat:@"%@Requests_%@",NSStringFromClass([delegateObject.actionDelegate class]),actionType]]; - [[self sharedActionHandler] defaultHandleActionError:error additionalData:additionalData]; -} - -- (void)defaultHandleActionError:(nonnull MVMCoreErrorObject *)error additionalData:(nullable NSDictionary *)additionalData { - - // Logs the error. - if (error.logError) { - [MVMCoreLoggingHandler addErrorToLog:error]; - } -} - -#pragma mark - Deprecated - -- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate { - - DelegateObject *delegateObject = [[DelegateObject alloc] init]; - delegateObject.actionDelegate = delegate; - delegateObject.presentationDelegate = delegate; - delegateObject.loadDelegate = delegate; - [self handleActionWithDictionary:dictionary additionalData:additionalData delegateObject:delegateObject]; -} - -@end +//@interface MVMCoreActionHandler() +//@end +// +//@implementation MVMCoreActionHandler +//+ (nullable instancetype)sharedActionHandler { +// return [MVMCoreActionUtility initializerClassCheck:[MVMCoreObject sharedInstance].actionHandler classToVerify:self]; +//} +// +//- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// [self handleActionWith:dictionary additionalData:additionalData delegateObject:delegateObject]; +//} +// +//- (void)handleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// [self handleActionWith:actionInformation additionalData:additionalData delegateObject:delegateObject]; +//} +// +//- (void)getClientParameter:(nullable NSDictionary *)clientParametersMap requestParameters:(nullable NSDictionary *)requestParameters showLoadingOverlay:(BOOL)showLoadingOverlay completionHandler:(nonnull void (^)(NSDictionary * _Nullable parameters))completionHandler { +// +// if (!clientParametersMap) { +// completionHandler(nil); +// return; +// } +// +// if (showLoadingOverlay) { +// [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading]; +// } +// +// void (^stopLoadingOverlay)(void) = ^(void) { +// if (showLoadingOverlay) { +// [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:true]; +// } +// }; +// +// NSError *error = nil; +// [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"Fetching client parameters"]; +// +// ClientParameterHandler *clientParameterHandler = [[ClientParameterHandler alloc] init]; +// [clientParameterHandler getParametersWith:clientParametersMap +// requestParameters:requestParameters +// error:&error +// completionHandler:^(NSDictionary * _Nullable clientParameters) { +// [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"Finshed fetching client parameters"]; +// if (clientParameters) { +// stopLoadingOverlay(); +// completionHandler(clientParameters); +// } else { +// [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"No client parameters"]; +// stopLoadingOverlay(); +// completionHandler(nil); +// } +// }]; +// +// if (error) { +// stopLoadingOverlay(); +// completionHandler(nil); +// [MVMCoreLoggingHandler addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:@"MVMCoreActionHandler->setClientParameter"]]; +// } +//} +// +//#pragma mark - Actions +// +//- (void)logAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// if ([delegateObject.actionDelegate respondsToSelector:@selector(logActionWithActionInformation:additionalData:)]) { +// [delegateObject.actionDelegate logActionWithActionInformation:actionInformation additionalData:additionalData]; +// } else { +// [[self class] defaultLogAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; +// } +//} +// +// +// +//- (BOOL)handleOtherActions:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// return NO; +//} +// +//- (void)unknownAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// if ([delegateObject.actionDelegate respondsToSelector:@selector(handleUnknownActionType:actionInformation:additionalData:)]) { +// [delegateObject.actionDelegate handleUnknownActionType:actionType actionInformation:actionInformation additionalData:additionalData]; +// } else { +// [MVMCoreActionHandler defaultHandleUnknownActionType:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; +// } +//} +// +//- (void)handleActionError:(nullable MVMCoreErrorObject *)error actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// if (error) { +// if ([delegateObject.actionDelegate respondsToSelector:@selector(handleActionError:additionalData:)]) { +// [delegateObject.actionDelegate handleActionError:error additionalData:additionalData]; +// } else { +// [self defaultHandleActionError:error additionalData:additionalData]; +// } +// } +//} +// +//#pragma mark - Default Action Protocol +// +//+ (void)defaultLogAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject{ +// // Currently no default log action but this will eventually be server driven. +//} +// +//+ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// +// NSDictionary *clientParamters = [actionInformation dict:KeyClientParameters]; +// if (clientParamters) { +// [[MVMCoreActionHandler sharedActionHandler] getClientParameter:clientParamters +// requestParameters: requestParameters.parameters +// showLoadingOverlay: !requestParameters.backgroundRequest +// completionHandler: ^(NSDictionary * _Nullable jsonDictionary) { +// [requestParameters addRequestParameters:jsonDictionary]; +// [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegateObject:delegateObject]; +// }]; +// } else { +// [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegateObject:delegateObject]; +// } +//} +// +//+ (void)defaultHandleUnknownActionType:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// +// MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess] code:ErrorCodeUnknownActionType domain:ErrorDomainNative location:[NSString stringWithFormat:@"%@Requests_%@",NSStringFromClass([delegateObject.actionDelegate class]),actionType]]; +// [[self sharedActionHandler] defaultHandleActionError:error additionalData:additionalData]; +//} +// +//- (void)defaultHandleActionError:(nonnull MVMCoreErrorObject *)error additionalData:(nullable NSDictionary *)additionalData { +// +// // Logs the error. +// if (error.logError) { +// [MVMCoreLoggingHandler addErrorToLog:error]; +// } +//} +// +//#pragma mark - Deprecated +// +//- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate { +// +// DelegateObject *delegateObject = [[DelegateObject alloc] init]; +// delegateObject.actionDelegate = delegate; +// delegateObject.presentationDelegate = delegate; +// delegateObject.loadDelegate = delegate; +// [self handleActionWithDictionary:dictionary additionalData:additionalData delegateObject:delegateObject]; +//} +// +//@end diff --git a/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.h b/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.h index 2fdc9de..3a47d8b 100644 --- a/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.h +++ b/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.h @@ -33,6 +33,10 @@ extern NSString * const KeyView; extern NSString * const KeyLinks; extern NSString * const KeyTitle; extern NSString * const KeyMessage; + +extern NSString * const KeyActionType; +extern NSString * const KeyActionTypeLinkAway; +extern NSString * const KeyActionTypeOpen; extern NSString * const KeyActionTypeRestart; extern NSString * const KeyActionTypeBack; extern NSString * const KeyActionTypeShare; diff --git a/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.m b/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.m index 772fd05..3ad5e93 100644 --- a/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.m +++ b/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.m @@ -35,6 +35,10 @@ NSString * const KeyView = @"view"; NSString * const KeyLinks = @"Links"; NSString * const KeyTitle = @"title"; NSString * const KeyMessage = @"message"; + +NSString * const KeyActionType = @"actionType"; +NSString * const KeyActionTypeLinkAway = @"openURL"; +NSString * const KeyActionTypeOpen = @"openPage"; NSString * const KeyActionTypeRestart = @"restart"; NSString * const KeyActionTypeBack = @"back"; NSString * const KeyActionTypeShare = @"share"; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift new file mode 100644 index 0000000..e382d4e --- /dev/null +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift @@ -0,0 +1,22 @@ +// +// MVMCoreLoadHandler+Extension.swift +// MVMCore +// +// Created by Scott Pfeil on 7/15/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +public extension MVMCoreLoadHandler { + /// Performs the request. + func performRequest(with requestParameters: MVMCoreRequestParameters, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + // Makes the request and waits for it. + guard let operation = MVMCoreLoadHandler.sharedGlobal()?.loadRequest(requestParameters, dataForPage: additionalData, delegateObject: delegateObject) else { return } + await withCheckedContinuation { continuation in + operation.completionBlock = { + continuation.resume() + } + } + } +} diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m index 27043bf..b226ce9 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m @@ -48,6 +48,7 @@ self.registeredLoadQueues = [[NSMutableArray alloc] initWithCapacity:3]; [self registerLoadQueue:self.blockingLoadQueue]; [self registerLoadQueue:self.backgroundLoadQueue]; + } return self; } diff --git a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift index 67c738a..8ead4e1 100644 --- a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift +++ b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift @@ -44,21 +44,23 @@ ///} /// completionHandler can return flat dictinary or a map. It depends on the paramters handler open func getParameters(with clientParameters: [String: Any], requestParameters: [String: Any], completionHandler:@escaping ([String: Any]?) -> ()) throws { - guard let clientParameterModel = try getClientParameterModel(clientParameters) else { completionHandler(nil) return } + try getParameters(with: clientParameterModel, requestParameters: requestParameters, completionHandler: completionHandler) + } + + open func getParameters(with model: ClientParameterModel, requestParameters: [String: Any], completionHandler:@escaping ([String: Any]?) -> ()) throws { - let timeout = clientParameterModel.timeout ?? 30.0 - + let timeout = model.timeout ?? 30.0 // Dispatch setup on queue to ensure setup is complete before completion callbacks. // Don't use [weak self]. Object is deallocated in the dispatch queue. parametersWorkQueue.async(group: group, qos: .userInitiated) { // Create the handler list so that same object can be used when merging. Merging needs default value in case of timeout - for parameterModel in clientParameterModel.list { + for parameterModel in model.list { if let parameterHandler = self.createParametersHandler(parameterModel) { self.parameterHandlerList.append(parameterHandler) } @@ -101,7 +103,7 @@ // Queue the results for merge. self.parametersWorkQueue.async { if (returnedList[index] != nil) { - MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, message: "Client parameter \(parameterType) has already executed. The completion handler should only be called once!", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: String(describing: ClientParameterHandler.self))!) + MVMCoreLoggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, message: "Client parameter \(parameterType) has already executed. The completion handler should only be called once!", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: String(describing: ClientParameterHandler.self))!) } else { returnedList[index] = receivedParameter self.group.leave() // Leaving is only done after setup (barriered). diff --git a/MVMCore/MVMCore/Models/ModelMapping.swift b/MVMCore/MVMCore/Models/ModelMapping.swift index 605c7ff..f587849 100644 --- a/MVMCore/MVMCore/Models/ModelMapping.swift +++ b/MVMCore/MVMCore/Models/ModelMapping.swift @@ -12,20 +12,19 @@ open class func registerActions() { ModelRegistry.register(ActionRunJavaScriptModel.self) - ModelRegistry.register(ActionOpenPageModel.self) + ModelRegistry.register(handler: ActionOpenPageHandler.self, for: ActionOpenPageModel.self) ModelRegistry.register(handler: ActionOpenUrlHandler.self, for: ActionOpenUrlModel.self) ModelRegistry.register(handler: ActionCallHandler.self, for: ActionCallModel.self) ModelRegistry.register(handler: ActionCallHandler.self, for: ActionCallModel.self) ModelRegistry.register(handler: ActionBackHandler.self, for: ActionBackModel.self) - ModelRegistry.register(ActionShareModel.self) + ModelRegistry.register(handler: ActionShareHandler.self, for: ActionShareModel.self) ModelRegistry.register(handler: ActionRestartHandler.self, for: ActionRestartModel.self) - ModelRegistry.register(handler: ActionRedirectHandler.self, for: ActionRedirectModel.self) ModelRegistry.register(handler: ActionPreviousSubmitHandler.self, for: ActionPreviousSubmitModel.self) ModelRegistry.register(handler: ActionCancelHandler.self, for: ActionCancelModel.self) ModelRegistry.register(handler: ActionSettingHandler.self, for: ActionSettingModel.self) ModelRegistry.register(handler: ActionNoopHandler.self, for: ActionNoopModel.self) ModelRegistry.register(handler: ActionActionsHandler.self, for: ActionActionsModel.self) ModelRegistry.register(handler: ActionOpenSMSHandler.self, for: ActionOpenSMSModel.self) - ModelRegistry.register(ActionContactModel.self) + ModelRegistry.register(handler: ActionContactHandler.self, for: ActionContactModel.self) } } diff --git a/MVMCore/MVMCore/Singletons/MVMCoreObject.h b/MVMCore/MVMCore/Singletons/MVMCoreObject.h index 03a8411..e790fee 100644 --- a/MVMCore/MVMCore/Singletons/MVMCoreObject.h +++ b/MVMCore/MVMCore/Singletons/MVMCoreObject.h @@ -17,6 +17,7 @@ #import #import #import +@class MVMCoreActionHandler; @interface MVMCoreObject : NSObject