Action modernization

This commit is contained in:
Scott Pfeil 2022-07-18 18:54:11 -04:00
parent 80a257c066
commit 606fd93fc3
31 changed files with 877 additions and 651 deletions

View File

@ -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 = "<group>"; };
AF69D4F4286E9F5900BC6862 /* ActionSettingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSettingHandler.swift; sourceTree = "<group>"; };
AF69D4F6286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionPreviousSubmitHandler.swift; sourceTree = "<group>"; };
AF69D4F8286EA27400BC6862 /* ActionRedirectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRedirectModel.swift; sourceTree = "<group>"; };
AF69D4FA286EA29300BC6862 /* ActionRedirectHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRedirectHandler.swift; sourceTree = "<group>"; };
AF706999287DD02400077CF6 /* ActionContactHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionContactHandler.swift; sourceTree = "<group>"; };
AF70699D2880D01400077CF6 /* ActionShareHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionShareHandler.swift; sourceTree = "<group>"; };
AF70699F2880F0EB00077CF6 /* ActionOpenPageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenPageHandler.swift; sourceTree = "<group>"; };
AF7069A12882293900077CF6 /* MVMCoreLoadHandler+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreLoadHandler+Extension.swift"; sourceTree = "<group>"; };
AF787412286DEF8B00670588 /* ActionBackHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBackHandler.swift; sourceTree = "<group>"; };
AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenUrlHandler.swift; sourceTree = "<group>"; };
AFBB96321FBA34310008D868 /* MVMCoreErrorConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreErrorConstants.h; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 = "<group>";
@ -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 */,

View File

@ -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)
}
}
}

View File

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

View File

@ -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)")
}
}

View File

@ -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)
}
}

View File

@ -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<Bool, Never>?
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)
})
}
}

View File

@ -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

View File

@ -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 {}
}

View File

@ -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
}
}
}

View File

@ -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?

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View File

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

View File

@ -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 }
}

View File

@ -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)
}
})
}
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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)!
}
}

View File

@ -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;

View File

@ -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<Void, Error> {
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)
}
}

View File

@ -7,72 +7,61 @@
//
// Can be subclassed to handle app specific actions as well.
#import <Foundation/Foundation.h>
#import <MVMCore/MVMCoreActionDelegateProtocol.h>
#import <MVMCore/MVMCoreLoadDelegateProtocol.h>
#import <MVMCore/MVMCorePresentationDelegateProtocol.h>
@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 <MVMCoreLoadDelegateProtocol,MVMCorePresentationDelegateProtocol,MVMCoreActionDelegateProtocol>*)delegate __deprecated;
@end
//#import <Foundation/Foundation.h>
//#import <MVMCore/MVMCoreActionDelegateProtocol.h>
//#import <MVMCore/MVMCoreLoadDelegateProtocol.h>
//#import <MVMCore/MVMCorePresentationDelegateProtocol.h>
//@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 <MVMCoreLoadDelegateProtocol,MVMCorePresentationDelegateProtocol,MVMCoreActionDelegateProtocol>*)delegate __deprecated;
//
//@end

View File

@ -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 <MVMCore/MVMCoreActionHandler.h>
//#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 <SafariServices/SafariServices.h>
//#import <MVMCore/MVMCore-Swift.h>
//#import "MVMCoreLoadingOverlayHandler.h"
//#import <ContactsUI/ContactsUI.h>
//
// 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 <MVMCore/MVMCoreActionHandler.h>
#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 <SafariServices/SafariServices.h>
#import <MVMCore/MVMCore-Swift.h>
#import "MVMCoreLoadingOverlayHandler.h"
#import <ContactsUI/ContactsUI.h>
NSString * const KeyActionType = @"actionType";
NSString * const KeyActionTypeLinkAway = @"openURL";
NSString * const KeyActionTypeOpen = @"openPage";
@interface MVMCoreActionHandler() <CNContactViewControllerDelegate, CNContactPickerDelegate>
@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<NSString *,id> * _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<CNLabeledValue *> *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 <MVMCoreLoadDelegateProtocol, MVMCorePresentationDelegateProtocol,MVMCoreActionDelegateProtocol>*)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() <CNContactViewControllerDelegate, CNContactPickerDelegate>
//@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<NSString *,id> * _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 <MVMCoreLoadDelegateProtocol, MVMCorePresentationDelegateProtocol,MVMCoreActionDelegateProtocol>*)delegate {
//
// DelegateObject *delegateObject = [[DelegateObject alloc] init];
// delegateObject.actionDelegate = delegate;
// delegateObject.presentationDelegate = delegate;
// delegateObject.loadDelegate = delegate;
// [self handleActionWithDictionary:dictionary additionalData:additionalData delegateObject:delegateObject];
//}
//
//@end

View File

@ -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;

View File

@ -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";

View File

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

View File

@ -48,6 +48,7 @@
self.registeredLoadQueues = [[NSMutableArray alloc] initWithCapacity:3];
[self registerLoadQueue:self.blockingLoadQueue];
[self registerLoadQueue:self.backgroundLoadQueue];
}
return self;
}

View File

@ -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).

View File

@ -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)
}
}

View File

@ -17,6 +17,7 @@
#import <MVMCore/MVMCoreLoggingDelegateProtocol.h>
#import <MVMCore/MVMCoreLoggingHandler.h>
#import <MVMCore/MVMCoreLoadHandler.h>
@class MVMCoreActionHandler;
@interface MVMCoreObject : NSObject