diff --git a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift index f93b009..14cefcb 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift @@ -13,13 +13,18 @@ open class ActionBackHandler: MVMCoreActionHandlerProtocol { public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionBackModel else { return } - let json = try MVMCoreActionHandler.convertActionToJSON(model) - // TODO: Make this actually async properly. - if delegateObject?.actionDelegate?.handleBackAction != nil { - delegateObject?.actionDelegate?.handleBackAction?(json, additionalData: additionalData) + if let closure = delegateObject?.actionDelegate?.handleBackAction { + // Legacy code will use the old handler function and break the task chain here. + try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in + closure(json, additionalData) + } } else { - Task { - await MVMCoreNavigationHandler.shared()?.removeCurrentViewController() + await withCheckedContinuation { (continuation: CheckedContinuation) in + Task { + await MVMCoreNavigationHandler.shared()?.removeCurrentViewController(true, completionHandler: { + continuation.resume() + }) + } } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift index 6206f4e..d8aa646 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift @@ -12,6 +12,8 @@ open class ActionCancelHandler: MVMCoreActionHandlerProtocol { required public init() {} open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { - delegateObject?.actionDelegate?.handleCancel?(model.toJSON(), additionalData: additionalData) + try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in + delegateObject?.actionDelegate?.handleCancel?(json, additionalData: additionalData) + } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift index b8b0477..e1de127 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -8,37 +8,47 @@ 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 - var additionalData = additionalData - if let _ = (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.getRequestParameters { - let value = try (delegateObject!.actionDelegate! as! ActionDelegateProtocol).getRequestParameters(for: model, delegateObject: delegateObject, additionalData: additionalData) - requestParameters = value.0 - additionalData = value.1 - } else { - let json = try MVMCoreActionHandler.convertActionToJSON(model) - requestParameters = MVMCoreRequestParameters(actionMap: json)! + try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in + var additionalData = additionalData + + // Allows the delegate a chance to create and modify request parameters. + var requestParameters: MVMCoreRequestParameters + if let _ = (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.getRequestParameters { + let value = try (delegateObject!.actionDelegate! as! ActionDelegateProtocol).getRequestParameters(for: model, delegateObject: delegateObject, additionalData: additionalData) + requestParameters = value.0 + additionalData = value.1 + } else { + requestParameters = MVMCoreRequestParameters(actionMap: json)! + } + + if let closure = delegateObject?.actionDelegate?.handleOpenPage { + // Legacy code will use the old handler function and break the task chain here. + closure(requestParameters, json, additionalData) + } else { + try await performRequestAddingClientParameters(with: requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) + } } - 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) { + let fetchedParameters = try await ClientParameterHandler().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) } - +} + +public extension ClientParameterHandler { /// 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]? { @@ -49,8 +59,7 @@ open class ActionOpenPageHandler: MVMCoreActionHandlerProtocol { do { let parameters: [String: Any]? = try await withCheckedThrowingContinuation({ continuation in do { - let handler = ClientParameterHandler() - try handler.getParameters(with: model, requestParameters: requestParameters) { parameters in + try getParameters(with: model, requestParameters: requestParameters) { parameters in MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) continuation.resume(returning: parameters) } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift index 177cc98..9403bdb 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift @@ -91,13 +91,13 @@ open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { } as Void } - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open 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) + try await openURL(model: model, additionalData: additionalData, delegateObject: delegateObject) return } catch { // Log error and continue @@ -107,6 +107,10 @@ open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { } } } + try await openURL(model: model, additionalData: additionalData, delegateObject: delegateObject) + } + + open func openURL(model: ActionOpenUrlModel, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) async throws { try await ActionOpenUrlHandler.open(url: model.browserUrl) } } diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift index f11a8a0..d1eeb17 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift @@ -28,6 +28,9 @@ extension MVMCoreActionHandlerProtocol { @objc open class MVMCoreActionHandler: NSObject { + /// The key used to pass along the json for the legacy handlers in additionalData + public static let originalJSONKey: String = "ORIGINAL_JSON" + enum ActionError: MVMError { case unknownAction(type: String) @@ -37,6 +40,10 @@ extension MVMCoreActionHandlerProtocol { return "Couldn't perform action: \(type)" } } + + public var errorCode: Int { + ErrorCode.unknownActionType.rawValue + } } /// Returns the action handler stored in the MVMCoreObject @@ -156,12 +163,27 @@ extension MVMCoreActionHandlerProtocol { /// Subclass to log the action was fired. open func logAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { // Calls legacy log action function. - delegateObject?.actionDelegate?.logAction?(withActionInformation: model.toJSON(), additionalData: additionalData) + Task { + try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in + delegateObject?.actionDelegate?.logAction?(withActionInformation: json, additionalData: additionalData) + } + } } /// Subclass to handle and any actions where a handler was not registered. Checks with the delegate handlesUnknownAction function open func handleUnregisteredAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws -> Bool { - return try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.handlesUnknownAction(for: model, delegateObject: delegateObject, additionalData: additionalData) ?? false + // Check if the delegate handles the action. + if try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.handlesUnknownAction(for: model, delegateObject: delegateObject, additionalData: additionalData) == true { + return true + } else if let closure = delegateObject?.actionDelegate?.handleUnknownActionType { + // Check if the legacy delegate handles the action. + try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in + closure(model.actionType, json, additionalData) + } + return true + } else { + return false + } } /// Logs the error. @@ -172,11 +194,20 @@ extension MVMCoreActionHandlerProtocol { // MARK: - Legacy Holdovers + public static func getOriginalJSON(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, closure: ([AnyHashable: Any], [AnyHashable: Any]?) async throws -> Void) async throws { + var additionalData = additionalData + let json = try additionalData?[MVMCoreActionHandler.originalJSONKey] as? [AnyHashable: Any] ?? MVMCoreActionHandler.convertActionToJSON(model) + additionalData?.removeValue(forKey: MVMCoreActionHandler.originalJSONKey) + try await closure(json, additionalData) + } + /// 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) { + var additionalData = additionalData ?? [:] + additionalData[MVMCoreActionHandler.originalJSONKey] = json do { guard let json = json else { throw ModelRegistry.Error.keyNotFound @@ -184,9 +215,21 @@ extension MVMCoreActionHandlerProtocol { 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) + switch error { + case ModelRegistry.Error.decoderErrorModelNotMapped: + // If the model is not mapped, give the legacy classes a chance to handle it. + let actionType = json?.optionalStringForKey(KeyActionType) + if let closure = delegateObject?.actionDelegate?.handleUnknownActionType { + additionalData.removeValue(forKey: MVMCoreActionHandler.originalJSONKey) + closure(actionType, json, additionalData) + } else { + fallthrough + } + default: + 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) + } } } } diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index ed0da89..d48d53f 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -24,7 +24,6 @@ #import "MVMCoreHardcodedStringsConstants.h" #import "MVMCoreErrorConstants.h" #import "MVMCoreActionUtility.h" -#import #import "MVMCoreObject.h" #import "MVMCoreConstants.h" #import diff --git a/MVMCore/MVMCore/MVMCore.h b/MVMCore/MVMCore/MVMCore.h index 7ef1d6e..8e1a01f 100644 --- a/MVMCore/MVMCore/MVMCore.h +++ b/MVMCore/MVMCore/MVMCore.h @@ -66,7 +66,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreVersionString[]; #import // Action Handling -#import #import #import diff --git a/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.h b/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.h index dbd71fa..86c056d 100644 --- a/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.h +++ b/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.h @@ -37,6 +37,9 @@ // pops or dimisses as needed - (void)removeCurrentViewController; +/// Dismisses or pops the current view controller. +- (void)removeCurrentViewController:(BOOL)animated completionHandler:(nullable void (^)(void))completionBlock; + #pragma mark - Delegate Handling /// Adds a listener for navigation delegate functions diff --git a/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.m b/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.m index 4fcfb68..15aea59 100644 --- a/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.m +++ b/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.m @@ -118,12 +118,16 @@ } - (void)removeCurrentViewController { + [self removeCurrentViewController:YES completionHandler:NULL]; +} + +- (void)removeCurrentViewController:(BOOL)animated completionHandler:(nullable void (^)(void))completionBlock { [MVMCoreDispatchUtility performBlockOnMainThread:^{ // presentedViewController must be used on main thread if (self.viewControllerToPresentOn.presentedViewController) { - [[MVMCoreNavigationHandler sharedNavigationHandler] dismissTopViewControllerAnimated:YES]; + [[MVMCoreNavigationHandler sharedNavigationHandler] dismissTopViewControllerAnimated:animated delegate:nil completionHandler:completionBlock]; } else { - [[MVMCoreNavigationHandler sharedNavigationHandler] popTopViewControllerAnimated:YES]; + [[MVMCoreNavigationHandler sharedNavigationHandler] popTopViewControllerAnimated:animated navigationController:nil delegate:nil completionHandler:completionBlock]; } }]; } diff --git a/MVMCore/MVMCore/Singletons/MVMCoreObject.h b/MVMCore/MVMCore/Singletons/MVMCoreObject.h index e790fee..be532a7 100644 --- a/MVMCore/MVMCore/Singletons/MVMCoreObject.h +++ b/MVMCore/MVMCore/Singletons/MVMCoreObject.h @@ -10,7 +10,6 @@ #import #import #import -#import #import #import #import