From 9d4caedbe5ea78a11c2239e344b4080806480e09 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Fri, 1 Jul 2022 15:26:09 -0400 Subject: [PATCH 01/16] Modernize actions --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 68 ++++++++-- .../ActionHandling/ActionActionsHandler.swift | 25 ++++ .../ActionActionsModel.swift | 0 .../ActionHandling/ActionBackHandler.swift | 22 ++++ .../ActionBackModel.swift | 0 .../ActionHandling/ActionCallHandler.swift | 18 +++ .../ActionCallModel.swift | 0 .../ActionHandling/ActionCancelHandler.swift | 17 +++ .../ActionCancelModel.swift | 0 .../ActionHandling/ActionNoopHandler.swift | 15 +++ .../ActionNoopModel.swift | 0 .../ActionHandling/ActionOpenSMSHandler.swift | 18 +++ .../ActionOpenSMSModel.swift | 0 .../ActionPreviousSubmitHandler.swift | 24 ++++ .../ActionPreviousSubmitModel.swift | 0 .../ActionRedirectHandler.swift | 18 +++ .../ActionHandling/ActionRedirectModel.swift | 18 +++ .../ActionHandling/ActionRestartHandler.swift | 28 ++++ .../ActionRestartModel.swift | 0 .../ActionHandling/ActionSettingHandler.swift | 17 +++ .../ActionSettingModel.swift | 0 ...CoreActionDelegateProtocol+Extension.swift | 6 + .../MVMCoreActionHandler+Extension.swift | 58 ++++++--- .../ActionHandling/MVMCoreActionHandler.h | 24 ---- .../ActionHandling/MVMCoreActionHandler.m | 122 +----------------- .../ActionType/ActionOpenPageModel.swift | 4 +- MVMCore/MVMCore/Models/ModelMapping.swift | 20 +-- 27 files changed, 337 insertions(+), 185 deletions(-) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionActionsModel.swift (100%) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionBackModel.swift (100%) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionCallModel.swift (100%) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionCancelModel.swift (100%) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionNoopModel.swift (100%) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionOpenSMSModel.swift (100%) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionPreviousSubmitModel.swift (100%) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift create mode 100644 MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift create mode 100644 MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionRestartModel.swift (100%) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionSettingModel.swift (100%) diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index 394e10b..73291ae 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -92,6 +92,17 @@ AF43A7411FC5FA6F008E9347 /* MVMCoreViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF43A74C1FC6109F008E9347 /* MVMCoreSessionObject.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF43A74D1FC6109F008E9347 /* MVMCoreSessionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */; }; + AF69D4E9286E54D500BC6862 /* ActionCallHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */; }; + AF69D4EB286E586200BC6862 /* ActionRestartHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */; }; + AF69D4ED286E5D8C00BC6862 /* ActionCancelHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.swift */; }; + AF69D4EF286E612800BC6862 /* ActionOpenSMSHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EE286E612800BC6862 /* ActionOpenSMSHandler.swift */; }; + AF69D4F1286E9D8000BC6862 /* ActionNoopHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4F0286E9D8000BC6862 /* ActionNoopHandler.swift */; }; + 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 */; }; + 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, ); }; }; AFBB96351FBA34310008D868 /* MVMCoreErrorConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = AFBB96331FBA34310008D868 /* MVMCoreErrorConstants.m */; }; @@ -242,6 +253,17 @@ AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreViewProtocol.h; sourceTree = ""; }; AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreSessionObject.h; sourceTree = ""; }; AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreSessionObject.m; sourceTree = ""; }; + AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCallHandler.swift; sourceTree = ""; }; + AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRestartHandler.swift; sourceTree = ""; }; + AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCancelHandler.swift; sourceTree = ""; }; + AF69D4EE286E612800BC6862 /* ActionOpenSMSHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenSMSHandler.swift; sourceTree = ""; }; + AF69D4F0286E9D8000BC6862 /* ActionNoopHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionNoopHandler.swift; sourceTree = ""; }; + AF69D4F2286E9DCE00BC6862 /* ActionActionsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionActionsHandler.swift; sourceTree = ""; }; + AF69D4F4286E9F5900BC6862 /* ActionSettingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSettingHandler.swift; sourceTree = ""; }; + AF69D4F6286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionPreviousSubmitHandler.swift; sourceTree = ""; }; + AF69D4F8286EA27400BC6862 /* ActionRedirectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRedirectModel.swift; sourceTree = ""; }; + AF69D4FA286EA29300BC6862 /* ActionRedirectHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRedirectHandler.swift; sourceTree = ""; }; + AF787412286DEF8B00670588 /* ActionBackHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBackHandler.swift; sourceTree = ""; }; AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenUrlHandler.swift; sourceTree = ""; }; AFBB96321FBA34310008D868 /* MVMCoreErrorConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreErrorConstants.h; sourceTree = ""; }; AFBB96331FBA34310008D868 /* MVMCoreErrorConstants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreErrorConstants.m; sourceTree = ""; }; @@ -464,16 +486,7 @@ 01F2A03523A80A7300D954D8 /* ActionModelProtocol.swift */, 946EE1BB237B691A0036751F /* ActionOpenPageModel.swift */, 01DB1F2A26444F7F000F1AF4 /* ActionOpenPageProtocol.swift */, - 01F2A04B23A82B1B00D954D8 /* ActionCallModel.swift */, - 01C851D023CF97FE0021F976 /* ActionBackModel.swift */, 0AFF597923FC6E60005C24E8 /* ActionShareModel.swift */, - 94C014D024211869005811A9 /* ActionRestartModel.swift */, - 94C014D2242119E6005811A9 /* ActionPreviousSubmitModel.swift */, - 94C014D424211AF0005811A9 /* ActionCancelModel.swift */, - 94C014D824212360005811A9 /* ActionSettingModel.swift */, - BB780ADE250F8C890030BD2F /* ActionNoopModel.swift */, - D27073B625BB45C4001C7246 /* ActionActionsModel.swift */, - 0AEBB84525FA75C000EA80EE /* ActionOpenSMSModel.swift */, 0ACC81A12613C73800A9C886 /* ActionContactModel.swift */, ); path = ActionType; @@ -626,6 +639,26 @@ AF130B8D2788DF6E00C6C03C /* OpenURLOptionsModel.swift */, 01F2A03823A812DD00D954D8 /* ActionOpenUrlModel.swift */, AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */, + 01C851D023CF97FE0021F976 /* ActionBackModel.swift */, + AF787412286DEF8B00670588 /* ActionBackHandler.swift */, + 01F2A04B23A82B1B00D954D8 /* ActionCallModel.swift */, + AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */, + 94C014D024211869005811A9 /* ActionRestartModel.swift */, + AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */, + 94C014D424211AF0005811A9 /* ActionCancelModel.swift */, + AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.swift */, + 0AEBB84525FA75C000EA80EE /* ActionOpenSMSModel.swift */, + AF69D4EE286E612800BC6862 /* ActionOpenSMSHandler.swift */, + BB780ADE250F8C890030BD2F /* ActionNoopModel.swift */, + AF69D4F0286E9D8000BC6862 /* ActionNoopHandler.swift */, + D27073B625BB45C4001C7246 /* ActionActionsModel.swift */, + AF69D4F2286E9DCE00BC6862 /* ActionActionsHandler.swift */, + 94C014D824212360005811A9 /* ActionSettingModel.swift */, + AF69D4F4286E9F5900BC6862 /* ActionSettingHandler.swift */, + 94C014D2242119E6005811A9 /* ActionPreviousSubmitModel.swift */, + AF69D4F6286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift */, + AF69D4F8286EA27400BC6862 /* ActionRedirectModel.swift */, + AF69D4FA286EA29300BC6862 /* ActionRedirectHandler.swift */, ); path = ActionHandling; sourceTree = ""; @@ -843,7 +876,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "unset TOOLCHAINS #Xcode 7.3 BUG FIX http://stackoverflow.com/questions/36184930/xcodebuild-7-3-cant-enable-bitcode\n\n# define output folder environment variable\nC_PROJECT_NAME=\"MVMCore\"\nPHONE_CONFIGURATION=\"Release\"\nSIMULATOR_CONFIGURATION=\"Debug\"\nSIMULATOR_LIBRARY_PATH=\"${BUILD_DIR}/${SIMULATOR_CONFIGURATION}-iphonesimulator/${C_PROJECT_NAME}.framework\"\nUNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/universal\n\n# Step 1. Build Device and Simulator versions\nxcodebuild -scheme \"${C_PROJECT_NAME}\" ONLY_ACTIVE_ARCH=NO -configuration ${PHONE_CONFIGURATION} -sdk iphoneos -archivePath \"${BUILD_DIR}/${PHONE_CONFIGURATION}-iphoneos/${C_PROJECT_NAME}\" archive SKIP_INSTALL=false\n\nxcodebuild -target \"${C_PROJECT_NAME}\" ONLY_ACTIVE_ARCH=NO -configuration ${SIMULATOR_CONFIGURATION} -sdk iphonesimulator -arch x86_64 BUILD_DIR=\"${BUILD_DIR}\" ALWAYS_SEARCH_USER_PATHS=true\n\nmkdir -p \"${UNIVERSAL_OUTPUTFOLDER}\"\n\nrm -rf ${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}.framework\ncp -R ${BUILD_DIR}/${PHONE_CONFIGURATION}-iphoneos/\"${C_PROJECT_NAME}\".xcarchive/Products/Library/Frameworks/${C_PROJECT_NAME}.framework ${UNIVERSAL_OUTPUTFOLDER}\n\n# Step 2. Create universal binary file using lipo\nlipo -create -output \"${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}\" \"${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}.framework/${C_PROJECT_NAME}\" \"${SIMULATOR_LIBRARY_PATH}/${C_PROJECT_NAME}\"\n\nmv ${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME} ${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}.framework/${C_PROJECT_NAME}\n\n# For Swift framework, Swiftmodule needs to be copied in the universal framework\nif [ -d \"${SIMULATOR_LIBRARY_PATH}/Modules/${C_PROJECT_NAME}.swiftmodule/\" ]; then\ncp -a \"${SIMULATOR_LIBRARY_PATH}/Modules/${C_PROJECT_NAME}.swiftmodule/\" \"${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}.framework/Modules/${C_PROJECT_NAME}.swiftmodule/\"\nfi\n"; + shellScript = "unset TOOLCHAINS #Xcode 7.3 BUG FIX http://stackoverflow.com/questions/36184930/xcodebuild-7-3-cant-enable-bitcode\n\n# define output folder environment variable\nC_PROJECT_NAME=\"MVMCore\"\nPHONE_CONFIGURATION=\"Release\"\nSIMULATOR_CONFIGURATION=\"Debug\"\nSIMULATOR_LIBRARY_PATH=\"${BUILD_DIR}/${SIMULATOR_CONFIGURATION}-iphonesimulator/${C_PROJECT_NAME}\"\nUNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/universal\n\nxcodebuild archive \\\n -scheme \"${C_PROJECT_NAME}\" \\\n -configuration ${PHONE_CONFIGURATION} \\\n -archivePath \"${BUILD_DIR}/${PHONE_CONFIGURATION}-iphoneos/${C_PROJECT_NAME}\" \\\n -sdk iphoneos \\\n -destination 'generic/platform=iOS' \\\n SKIP_INSTALL=NO\n \n exit 0\n\nxcodebuild archive \\\n -scheme \"${C_PROJECT_NAME}\" \\\n -configuration ${SIMULATOR_CONFIGURATION} \\\n -archivePath \"${SIMULATOR_LIBRARY_PATH}\" \\\n -sdk iphonesimulator \\\n -destination 'generic/platform=iOS Simulator' \\\n SKIP_INSTALL=NO\n\n\n\n\n# Step 1. Build Device and Simulator versions\nxcodebuild -scheme \"${C_PROJECT_NAME}\" ONLY_ACTIVE_ARCH=NO -configuration ${PHONE_CONFIGURATION} -sdk iphoneos -archivePath \"${BUILD_DIR}/${PHONE_CONFIGURATION}-iphoneos/${C_PROJECT_NAME}\" archive SKIP_INSTALL=false\n\nxcodebuild -target \"${C_PROJECT_NAME}\" ONLY_ACTIVE_ARCH=NO -configuration ${SIMULATOR_CONFIGURATION} -sdk iphonesimulator -arch x86_64 BUILD_DIR=\"${BUILD_DIR}\" ALWAYS_SEARCH_USER_PATHS=true\n\nmkdir -p \"${UNIVERSAL_OUTPUTFOLDER}\"\n\nrm -rf ${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}.framework\ncp -R ${BUILD_DIR}/${PHONE_CONFIGURATION}-iphoneos/\"${C_PROJECT_NAME}\".xcarchive/Products/Library/Frameworks/${C_PROJECT_NAME}.framework ${UNIVERSAL_OUTPUTFOLDER}\n\n# Step 2. Create universal binary file using lipo\nlipo -create -output \"${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}\" \"${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}.framework/${C_PROJECT_NAME}\" \"${SIMULATOR_LIBRARY_PATH}/${C_PROJECT_NAME}\"\n\nmv ${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME} ${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}.framework/${C_PROJECT_NAME}\n\n# For Swift framework, Swiftmodule needs to be copied in the universal framework\nif [ -d \"${SIMULATOR_LIBRARY_PATH}/Modules/${C_PROJECT_NAME}.swiftmodule/\" ]; then\ncp -a \"${SIMULATOR_LIBRARY_PATH}/Modules/${C_PROJECT_NAME}.swiftmodule/\" \"${UNIVERSAL_OUTPUTFOLDER}/${C_PROJECT_NAME}.framework/Modules/${C_PROJECT_NAME}.swiftmodule/\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -852,12 +885,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AF69D4ED286E5D8C00BC6862 /* ActionCancelHandler.swift in Sources */, AFED77A71FCCA29400BAE689 /* MVMCoreViewControllerProgrammaticMappingObject.m in Sources */, 946EE1A7237B5B1C0036751F /* ModelRegistry.swift in Sources */, AFBB96641FBA3A570008D868 /* MVMCoreLoadHandler.m in Sources */, AFFCFA671FCCC0D700FD0730 /* MVMCoreLoadingOverlayHandler.m in Sources */, AF8D13392774EA1D008AF4A9 /* ActionOpenUrlHandler.swift in Sources */, AF130B8E2788DF6E00C6C03C /* OpenURLOptionsModel.swift in Sources */, + AF69D4EB286E586200BC6862 /* ActionRestartHandler.swift in Sources */, AFBB968E1FBA3A9A0008D868 /* MVMCoreNavigationHandler.m in Sources */, BB780ADF250F8C890030BD2F /* ActionNoopModel.swift in Sources */, D2E1FAD92260C3E400AEFD8C /* DelegateObject.swift in Sources */, @@ -876,6 +911,7 @@ AFBB96901FBA3A9A0008D868 /* MVMCoreNavigationObject.m in Sources */, 1DAD0FFE26AAB40000216E83 /* ActionRunJavaScriptModel.swift in Sources */, 946EE1AB237B5C940036751F /* Decoder.swift in Sources */, + AF69D4F3286E9DCE00BC6862 /* ActionActionsHandler.swift in Sources */, 946EE1BC237B691A0036751F /* ActionOpenPageModel.swift in Sources */, 0A42538F23F3414800554656 /* Codable+Helpers.swift in Sources */, 881D26931FCC9D180079C521 /* MVMCoreErrorObject.m in Sources */, @@ -885,27 +921,33 @@ 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 */, 0ACC81A22613C73800A9C886 /* ActionContactModel.swift in Sources */, AFBB96971FBA3A9A0008D868 /* MVMCorePresentViewControllerOperation.m in Sources */, + AF69D4EF286E612800BC6862 /* ActionOpenSMSHandler.swift in Sources */, AF43A5781FBA5B7C008E9347 /* MVMCoreJSONConstants.m in Sources */, AFBB96691FBA3A570008D868 /* MVMCoreRequestParameters.m in Sources */, AFED77A31FCCA29400BAE689 /* MVMCoreViewControllerNibMappingObject.m in Sources */, 8876D5EB1FB50AB000EB2E3D /* NSDecimalNumber+MFConvenience.m in Sources */, 01F2A03923A812DD00D954D8 /* ActionOpenUrlModel.swift in Sources */, 01934FE725A4FFC2003DCD67 /* ClientParameterActionProtocol.swift in Sources */, + AF69D4E9286E54D500BC6862 /* ActionCallHandler.swift in Sources */, AFBB96351FBA34310008D868 /* MVMCoreErrorConstants.m in Sources */, AF43A5881FBB67D6008E9347 /* MVMCoreActionUtility.m in Sources */, AFED77A61FCCA29400BAE689 /* MVMCoreViewControllerStoryBoardMappingObject.m in Sources */, 016CF36925FA6DD400B82A1F /* ClientParameterHandler.swift in Sources */, + AF69D4F7286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift in Sources */, AF43A57C1FBA5E6A008E9347 /* MVMCoreHardcodedStringsConstants.m in Sources */, 0AFF597A23FC6E60005C24E8 /* ActionShareModel.swift in Sources */, AFEEE81F1FCDF3CA00B5EDD0 /* MVMCoreLoggingHandler.m in Sources */, + AF69D4F5286E9F5900BC6862 /* ActionSettingHandler.swift in Sources */, 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 */, @@ -923,10 +965,12 @@ D27073D125BB844B001C7246 /* MVMCoreActionDelegateProtocol+Extension.swift in Sources */, AFBB96921FBA3A9A0008D868 /* MVMCoreNavigationOperation.m in Sources */, AFBB96611FBA3A570008D868 /* MVMCoreLoadObject.m in Sources */, + AF787413286DEF8B00670588 /* ActionBackHandler.swift in Sources */, 946EE1B4237B619D0036751F /* Encoder.swift in Sources */, AFBB96941FBA3A9A0008D868 /* MVMCorePresentAnimationOperation.m in Sources */, 94C014D524211AF0005811A9 /* ActionCancelModel.swift in Sources */, AF43A5841FBB66DE008E9347 /* MVMCoreConstants.m in Sources */, + AF69D4F1286E9D8000BC6862 /* ActionNoopHandler.swift in Sources */, 016FF6F2259A4FCC00F5E4AA /* ClientParameterModel.swift in Sources */, D2DEDCBB23C65BC300C44CC4 /* Percent.swift in Sources */, AFBB966A1FBA3A570008D868 /* MVMCoreLoadRequestOperation.m in Sources */, @@ -994,7 +1038,6 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -1011,7 +1054,6 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1056,7 +1098,6 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1094,7 +1135,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; MARKETING_VERSION = 2.0; - ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCore; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; diff --git a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift new file mode 100644 index 0000000..1e88940 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift @@ -0,0 +1,25 @@ +// +// ActionActionsHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +open class ActionActionsHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + 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) + } + } + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionActionsModel.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift similarity index 100% rename from MVMCore/MVMCore/Models/ActionType/ActionActionsModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift diff --git a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift new file mode 100644 index 0000000..258a26a --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift @@ -0,0 +1,22 @@ +// +// ActionBackHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +open class ActionBackHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + 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) + } + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionBackModel.swift b/MVMCore/MVMCore/ActionHandling/ActionBackModel.swift similarity index 100% rename from MVMCore/MVMCore/Models/ActionType/ActionBackModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionBackModel.swift diff --git a/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift new file mode 100644 index 0000000..47730a4 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift @@ -0,0 +1,18 @@ +// +// ActionCallHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +open class ActionCallHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + guard let model = model as? ActionCallModel else { return } + MVMCoreActionUtility.linkAway("tel://\(model.actionType)", appURLString: nil) + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionCallModel.swift b/MVMCore/MVMCore/ActionHandling/ActionCallModel.swift similarity index 100% rename from MVMCore/MVMCore/Models/ActionType/ActionCallModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionCallModel.swift diff --git a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift new file mode 100644 index 0000000..d52885c --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift @@ -0,0 +1,17 @@ +// +// ActionCancelHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +open class ActionCancelHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + delegateObject?.actionDelegate?.handleCancel?(model.toJSON(), additionalData: additionalData) + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionCancelModel.swift b/MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift similarity index 100% rename from MVMCore/MVMCore/Models/ActionType/ActionCancelModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift diff --git a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift new file mode 100644 index 0000000..ac3af3e --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift @@ -0,0 +1,15 @@ +// +// ActionNoopHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +open class ActionNoopHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) {} +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionNoopModel.swift b/MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift similarity index 100% rename from MVMCore/MVMCore/Models/ActionType/ActionNoopModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift new file mode 100644 index 0000000..67aa4bd --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift @@ -0,0 +1,18 @@ +// +// ActionOpenSMSHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +open class ActionOpenSMSHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + guard let model = model as? ActionOpenSMSModel else { return } + MVMCoreActionUtility.linkAway("sms:\(model.phoneNumber)&body=\(model.message ?? "")".addingPercentEncoding(withAllowedCharacters: CharacterSet(charactersIn: "0123456789+-.")), appURLString: nil) + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionOpenSMSModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift similarity index 100% rename from MVMCore/MVMCore/Models/ActionType/ActionOpenSMSModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift diff --git a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift new file mode 100644 index 0000000..e803ab7 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift @@ -0,0 +1,24 @@ +// +// ActionPreviousSubmitHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +/// Makes the previous request, needs the delegate for this +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) + }) + }) + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionPreviousSubmitModel.swift b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift similarity index 100% rename from MVMCore/MVMCore/Models/ActionType/ActionPreviousSubmitModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift diff --git a/MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift new file mode 100644 index 0000000..e890617 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift @@ -0,0 +1,18 @@ +// +// ActionRedirectHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +open class ActionRedirectHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + // Have delegate redirect. + MVMCoreSessionObject.sharedGlobal()?.redirect(withInfo: model.toJSON()) + } +} diff --git a/MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift b/MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift new file mode 100644 index 0000000..d36722e --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift @@ -0,0 +1,18 @@ +// +// ActionRedirectModel.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +@objcMembers public class ActionRedirectModel: ActionOpenPageModel { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public override class var identifier: String { KeyActionTypeRedirect } + public override var actionType: String { ActionRedirectModel.identifier } +} diff --git a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift new file mode 100644 index 0000000..c468fe6 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift @@ -0,0 +1,28 @@ +// +// ActionRestartHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +open class ActionRestartHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + 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) + } + }) + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionRestartModel.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift similarity index 100% rename from MVMCore/MVMCore/Models/ActionType/ActionRestartModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift diff --git a/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift new file mode 100644 index 0000000..9d09408 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift @@ -0,0 +1,17 @@ +// +// ActionSettingHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +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) + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionSettingModel.swift b/MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift similarity index 100% rename from MVMCore/MVMCore/Models/ActionType/ActionSettingModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift index 1308ee5..ab572f8 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift @@ -9,6 +9,12 @@ import Foundation public extension MVMCoreActionDelegateProtocol { + + /// Allows the delegate to cancel the action. + func shouldPerform(action: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) -> Bool { + return true + } + /// Handles any action errors. func handleAction(error: MVMCoreErrorObject, action: ActionModelProtocol, additionalData: [AnyHashable: Any]?) { MVMCoreActionHandler.shared()?.defaultHandleActionError(error, additionalData: additionalData) diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift index 8b66d82..4160c61 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift @@ -16,19 +16,37 @@ public protocol MVMCoreActionHandlerProtocol: ModelHandlerProtocol { public extension MVMCoreActionHandler { /// Converts the action to json for old action handler to handle. - func convertActionToJSON(_ model: ActionModelProtocol, delegateObject: DelegateObject?) -> [AnyHashable: Any]? { - do { - let data = try model.encode() - guard let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [AnyHashable: Any] else { - throw ModelRegistry.Error.decoderError - } - return json - } catch { - if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "") { - delegateObject?.actionDelegate?.handleAction(error: errorObject, action: model, additionalData: model.extraParameters) ?? MVMCoreActionHandler.shared()?.defaultHandleActionError(errorObject, additionalData: model.extraParameters) - } - return nil + static 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 } + return json + } + + /// Creates a model from the action json. + static func createModel(with json: [AnyHashable: Any]) 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 { + 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) + } + + /// 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 { @@ -68,14 +86,22 @@ public extension MVMCoreActionHandler { /// Start action on current thread. func syncHandleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - guard let json = convertActionToJSON(model, delegateObject: delegateObject) else { return } - synchronouslyHandleAction(model.actionType, actionInformation: json, additionalData: additionalData, 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) + } } /// Start action on dispatched background thread. func asyncHandleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - guard let json = convertActionToJSON(model, delegateObject: delegateObject) else { return } - handleAction(model.actionType, actionInformation: json, additionalData: additionalData, 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) + } } @objc static func getErrorLocation(with delegate: MVMCoreActionDelegateProtocol?, actionType: String) -> String { diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h index 068b018..1854927 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h @@ -44,33 +44,9 @@ extern NSString * _Nonnull const KeyActionTypeOpen; /// Tries to open a page - (void)openPageAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; -/// restarts the app -- (void)restartAction:(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; -/// Opens Text Message -- (void)smsAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Makes a phone call -- (void)callAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Makes the previous request, needs the delegate for this -- (void)previousSubmitAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Redirects to another experience -- (void)redirectAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Cancels (like in a popup) -- (void)cancelAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Goes to settings app -- (void)settingsAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Performs multiple actions -- (void)actions:(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; diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m index cac409d..f3974f0 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m @@ -59,40 +59,13 @@ NSString * const KeyActionTypeOpen = @"openPage"; if ([actionType isEqualToString:KeyActionTypeOpen]) { [self openPageAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeRestart]) { - [self restartAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeBack]) { - [self backAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeCall]) { - [self callAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeSMS]) { - [self smsAction: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 ([actionType isEqualToString:KeyActionTypePreviousSubmit]) { - [self previousSubmitAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeRedirect]) { - [self redirectAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeCancel]) { - [self cancelAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeSettings]) { - [self settingsAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeNoop]) { - } else if ([actionType isEqualToString:KeyActionTypeActions]) { - [self actions: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]; @@ -227,44 +200,8 @@ NSString * const KeyActionTypeOpen = @"openPage"; } } -- (void)restartAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - // Invalidates the session before restarting. - [[MVMCoreSessionTimeHandler sharedSessionHandler] invalidateSession:^(MVMCoreErrorObject * _Nullable error) { - - // Restarts the app (forcing any passed in page types). - if (error.code != NSURLErrorCancelled) { - - if (error) { - - // Error invalidating. - [self handleActionError:error actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; - } else { - - // Restart the application with the page type. - NSString *pageType = [actionInformation string:KeyPageType]; - NSDictionary *parameters = [actionInformation dict:KeyExtraParameters]; - [[MVMCoreSessionObject sharedGlobal] restartSessionWithPageType:pageType parameters:parameters clearAllVariables:YES]; - } - } - }]; -} - - (void)backAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - // Go back. - if ([delegateObject.actionDelegate respondsToSelector:@selector(handleBackAction:additionalData:)]) { - [delegateObject.actionDelegate handleBackAction:actionInformation additionalData:additionalData]; - } else { - [[MVMCoreNavigationHandler sharedNavigationHandler] removeCurrentViewController]; - } -} - -- (void)smsAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - NSString *phoneNumber = [actionInformation stringForKey:@"phoneNumber"]; - NSString *message = [actionInformation stringForKey:KeyMessage]; - NSString *smsQuery = [NSString stringWithFormat:@"sms:%@&body=%@", phoneNumber, message]; - [MVMCoreActionUtility linkAway:[smsQuery stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] appURLString:nil]; + [self handleActionWithDictionary:actionInformation ?: @{KeyActionType: KeyActionTypeBack} additionalData:additionalData delegateObject:delegateObject]; } - (void)contactAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { @@ -328,61 +265,6 @@ NSString * const KeyActionTypeOpen = @"openPage"; } } -- (void)callAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - // Call - NSString *callNumber = [actionInformation stringForKey:KeyCallNumber]; - [MVMCoreActionUtility linkAway:[@"tel://" stringByAppendingString:callNumber] appURLString:nil]; -} - -- (void)previousSubmitAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - // Perform the previous submission. - __weak typeof(self) weakSelf = self; - if ([delegateObject.actionDelegate respondsToSelector:@selector(prepareRequestForPreviousSubmission:additionalData:submit:)]) { - [delegateObject.actionDelegate prepareRequestForPreviousSubmission:actionInformation additionalData:additionalData submit:^(MVMCoreRequestParameters * _Nonnull requestParameters, NSDictionary * _Nullable dataForPage) { - - [weakSelf updateRequestParametersBeforeHandleOpenPageAction:requestParameters callBack:^(MVMCoreRequestParameters * _Nonnull requestParameters) { - // Give the delegate a chance to alter the request parameters - if ([delegateObject.actionDelegate respondsToSelector:@selector(handleOpenPageForRequestParameters:actionInformation:additionalData:)]) { - [delegateObject.actionDelegate handleOpenPageForRequestParameters:requestParameters actionInformation:actionInformation additionalData:dataForPage]; - } else { - [MVMCoreActionHandler defaultHandleOpenPageForRequestParameters:requestParameters - actionInformation:actionInformation - additionalData:additionalData - delegateObject:delegateObject]; - } - }]; - }]; - } -} - -- (void)redirectAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - // Have delegate redirect. - [[MVMCoreSessionObject sharedGlobal] redirectWithInfo:actionInformation]; -} - -- (void)cancelAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - if ([delegateObject.actionDelegate respondsToSelector:@selector(handleCancel:additionalData:)]) { - [delegateObject.actionDelegate handleCancel:actionInformation additionalData:additionalData]; - } -} - -- (void)settingsAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - [MVMCoreActionUtility linkAway:UIApplicationOpenSettingsURLString appURLString:nil]; -} - -- (void)actions:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - NSArray *actions = [actionInformation array:@"actions"]; - BOOL concurrent = [actionInformation boolForKey:@"concurrent"]; - for (NSDictionary *action in actions) { - NSString *actionType = [action string:KeyActionType]; - if (concurrent) { - [self handleAction:actionType actionInformation:action additionalData:additionalData delegateObject:delegateObject]; - } else { - [self synchronouslyHandleAction:actionType actionInformation:action 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]; } diff --git a/MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift b/MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift index 184be57..a646ef7 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift +++ b/MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift @@ -12,8 +12,8 @@ // MARK: - Properties //-------------------------------------------------- - public static var identifier: String = "openPage" - public var actionType: String = ActionOpenPageModel.identifier + public class var identifier: String { "openPage" } + public var actionType: String { ActionOpenPageModel.identifier } public var pageType: String public var modules: [String]? public var baseURL: String? diff --git a/MVMCore/MVMCore/Models/ModelMapping.swift b/MVMCore/MVMCore/Models/ModelMapping.swift index da05419..605c7ff 100644 --- a/MVMCore/MVMCore/Models/ModelMapping.swift +++ b/MVMCore/MVMCore/Models/ModelMapping.swift @@ -14,16 +14,18 @@ ModelRegistry.register(ActionRunJavaScriptModel.self) ModelRegistry.register(ActionOpenPageModel.self) ModelRegistry.register(handler: ActionOpenUrlHandler.self, for: ActionOpenUrlModel.self) - ModelRegistry.register(ActionCallModel.self) - ModelRegistry.register(ActionBackModel.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(ActionRestartModel.self) - ModelRegistry.register(ActionPreviousSubmitModel.self) - ModelRegistry.register(ActionCancelModel.self) - ModelRegistry.register(ActionSettingModel.self) - ModelRegistry.register(ActionNoopModel.self) - ModelRegistry.register(ActionActionsModel.self) - ModelRegistry.register(ActionOpenSMSModel.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) } } From 606fd93fc33d03590291d151d116ef57eb2b197e Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 18 Jul 2022 18:54:11 -0400 Subject: [PATCH 02/16] Action modernization --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 30 +- .../ActionHandling/ActionActionsHandler.swift | 21 +- .../ActionHandling/ActionBackHandler.swift | 14 +- .../ActionHandling/ActionCallHandler.swift | 5 +- .../ActionHandling/ActionCancelHandler.swift | 2 +- .../ActionHandling/ActionContactHandler.swift | 116 ++++ .../ActionContactModel.swift | 10 +- .../ActionHandling/ActionNoopHandler.swift | 2 +- .../ActionOpenPageHandler.swift | 69 +++ .../ActionOpenPageModel.swift | 2 +- .../ActionHandling/ActionOpenSMSHandler.swift | 19 +- .../ActionHandling/ActionOpenUrlHandler.swift | 128 ++-- .../ActionPreviousSubmitHandler.swift | 26 +- .../ActionRedirectHandler.swift | 18 - .../ActionHandling/ActionRedirectModel.swift | 18 - .../ActionHandling/ActionRestartHandler.swift | 31 +- .../ActionHandling/ActionSettingHandler.swift | 4 +- .../ActionHandling/ActionShareHandler.swift | 54 ++ .../ActionShareModel.swift | 10 +- ...CoreActionDelegateProtocol+Extension.swift | 7 +- .../MVMCoreActionDelegateProtocol.h | 3 - .../MVMCoreActionHandler+Extension.swift | 212 ++++--- .../ActionHandling/MVMCoreActionHandler.h | 127 ++-- .../ActionHandling/MVMCoreActionHandler.m | 549 ++++++------------ .../MVMCore/Constants/MVMCoreJSONConstants.h | 4 + .../MVMCore/Constants/MVMCoreJSONConstants.m | 4 + .../MVMCoreLoadHandler+Extension.swift | 22 + .../MVMCore/LoadHandling/MVMCoreLoadHandler.m | 1 + .../ClientParameterHandler.swift | 12 +- MVMCore/MVMCore/Models/ModelMapping.swift | 7 +- MVMCore/MVMCore/Singletons/MVMCoreObject.h | 1 + 31 files changed, 877 insertions(+), 651 deletions(-) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionContactModel.swift (85%) create mode 100644 MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionOpenPageModel.swift (95%) delete mode 100644 MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift delete mode 100644 MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift create mode 100644 MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift rename MVMCore/MVMCore/{Models/ActionType => ActionHandling}/ActionShareModel.swift (76%) create mode 100644 MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index 7529847..1ae7ffc 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -100,8 +100,10 @@ AF69D4F3286E9DCE00BC6862 /* ActionActionsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4F2286E9DCE00BC6862 /* ActionActionsHandler.swift */; }; AF69D4F5286E9F5900BC6862 /* ActionSettingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4F4286E9F5900BC6862 /* ActionSettingHandler.swift */; }; AF69D4F7286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4F6286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift */; }; - AF69D4F9286EA27400BC6862 /* ActionRedirectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4F8286EA27400BC6862 /* ActionRedirectModel.swift */; }; - AF69D4FB286EA29300BC6862 /* ActionRedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4FA286EA29300BC6862 /* ActionRedirectHandler.swift */; }; + AF70699A287DD02400077CF6 /* ActionContactHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF706999287DD02400077CF6 /* ActionContactHandler.swift */; }; + AF70699E2880D01400077CF6 /* ActionShareHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF70699D2880D01400077CF6 /* ActionShareHandler.swift */; }; + AF7069A02880F0EB00077CF6 /* ActionOpenPageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF70699F2880F0EB00077CF6 /* ActionOpenPageHandler.swift */; }; + AF7069A22882293900077CF6 /* MVMCoreLoadHandler+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF7069A12882293900077CF6 /* MVMCoreLoadHandler+Extension.swift */; }; AF787413286DEF8B00670588 /* ActionBackHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF787412286DEF8B00670588 /* ActionBackHandler.swift */; }; AF8D13392774EA1D008AF4A9 /* ActionOpenUrlHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */; }; AFBB96341FBA34310008D868 /* MVMCoreErrorConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBB96321FBA34310008D868 /* MVMCoreErrorConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -261,8 +263,10 @@ AF69D4F2286E9DCE00BC6862 /* ActionActionsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionActionsHandler.swift; sourceTree = ""; }; AF69D4F4286E9F5900BC6862 /* ActionSettingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSettingHandler.swift; sourceTree = ""; }; AF69D4F6286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionPreviousSubmitHandler.swift; sourceTree = ""; }; - AF69D4F8286EA27400BC6862 /* ActionRedirectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRedirectModel.swift; sourceTree = ""; }; - AF69D4FA286EA29300BC6862 /* ActionRedirectHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRedirectHandler.swift; sourceTree = ""; }; + AF706999287DD02400077CF6 /* ActionContactHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionContactHandler.swift; sourceTree = ""; }; + AF70699D2880D01400077CF6 /* ActionShareHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionShareHandler.swift; sourceTree = ""; }; + AF70699F2880F0EB00077CF6 /* ActionOpenPageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenPageHandler.swift; sourceTree = ""; }; + AF7069A12882293900077CF6 /* MVMCoreLoadHandler+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreLoadHandler+Extension.swift"; sourceTree = ""; }; AF787412286DEF8B00670588 /* ActionBackHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBackHandler.swift; sourceTree = ""; }; AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenUrlHandler.swift; sourceTree = ""; }; AFBB96321FBA34310008D868 /* MVMCoreErrorConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreErrorConstants.h; sourceTree = ""; }; @@ -484,10 +488,7 @@ 1DAD0FFD26AAB3FF00216E83 /* ActionRunJavaScriptModel.swift */, 016FF6EC259A4E3E00F5E4AA /* Client Parameters */, 01F2A03523A80A7300D954D8 /* ActionModelProtocol.swift */, - 946EE1BB237B691A0036751F /* ActionOpenPageModel.swift */, 01DB1F2A26444F7F000F1AF4 /* ActionOpenPageProtocol.swift */, - 0AFF597923FC6E60005C24E8 /* ActionShareModel.swift */, - 0ACC81A12613C73800A9C886 /* ActionContactModel.swift */, ); path = ActionType; sourceTree = ""; @@ -574,6 +575,7 @@ AFBB96371FBA39E70008D868 /* MVMCoreLoadDelegateProtocol.h */, AFBB96391FBA3A550008D868 /* MVMCoreLoadHandler.h */, AFBB964B1FBA3A560008D868 /* MVMCoreLoadHandler.m */, + AF7069A12882293900077CF6 /* MVMCoreLoadHandler+Extension.swift */, AFBB964A1FBA3A560008D868 /* MVMCoreLoadRequestOperation.h */, AFBB96521FBA3A570008D868 /* MVMCoreLoadRequestOperation.m */, AFBB96471FBA3A560008D868 /* MVMCoreLoadObject.h */, @@ -636,6 +638,8 @@ AFBB96B61FBA3CEC0008D868 /* MVMCoreActionHandler.h */, AFBB96B71FBA3CEC0008D868 /* MVMCoreActionHandler.m */, D27073CC25BB4CEF001C7246 /* MVMCoreActionHandler+Extension.swift */, + 946EE1BB237B691A0036751F /* ActionOpenPageModel.swift */, + AF70699F2880F0EB00077CF6 /* ActionOpenPageHandler.swift */, AF130B8D2788DF6E00C6C03C /* OpenURLOptionsModel.swift */, 01F2A03823A812DD00D954D8 /* ActionOpenUrlModel.swift */, AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */, @@ -657,8 +661,10 @@ AF69D4F4286E9F5900BC6862 /* ActionSettingHandler.swift */, 94C014D2242119E6005811A9 /* ActionPreviousSubmitModel.swift */, AF69D4F6286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift */, - AF69D4F8286EA27400BC6862 /* ActionRedirectModel.swift */, - AF69D4FA286EA29300BC6862 /* ActionRedirectHandler.swift */, + 0ACC81A12613C73800A9C886 /* ActionContactModel.swift */, + AF706999287DD02400077CF6 /* ActionContactHandler.swift */, + 0AFF597923FC6E60005C24E8 /* ActionShareModel.swift */, + AF70699D2880D01400077CF6 /* ActionShareHandler.swift */, ); path = ActionHandling; sourceTree = ""; @@ -909,8 +915,10 @@ AF43A74D1FC6109F008E9347 /* MVMCoreSessionObject.m in Sources */, D282AAB62240085300C46919 /* MVMCoreGetterUtility+Extension.swift in Sources */, AFBB96901FBA3A9A0008D868 /* MVMCoreNavigationObject.m in Sources */, + AF7069A22882293900077CF6 /* MVMCoreLoadHandler+Extension.swift in Sources */, 1DAD0FFE26AAB40000216E83 /* ActionRunJavaScriptModel.swift in Sources */, 946EE1AB237B5C940036751F /* Decoder.swift in Sources */, + AF70699A287DD02400077CF6 /* ActionContactHandler.swift in Sources */, AF69D4F3286E9DCE00BC6862 /* ActionActionsHandler.swift in Sources */, 946EE1BC237B691A0036751F /* ActionOpenPageModel.swift in Sources */, 0A42538F23F3414800554656 /* Codable+Helpers.swift in Sources */, @@ -921,7 +929,6 @@ 30349BF21FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m in Sources */, D2DEDCB923C6400600C44CC4 /* UnitInterval.swift in Sources */, 94C014D3242119E6005811A9 /* ActionPreviousSubmitModel.swift in Sources */, - AF69D4FB286EA29300BC6862 /* ActionRedirectHandler.swift in Sources */, 8876D5E91FB50AB000EB2E3D /* NSArray+MFConvenience.m in Sources */, D27073B725BB45C4001C7246 /* ActionActionsModel.swift in Sources */, 946EE1B2237B5F260036751F /* JSONValue.swift in Sources */, @@ -947,13 +954,14 @@ D27073CD25BB4CEF001C7246 /* MVMCoreActionHandler+Extension.swift in Sources */, 01F2A05223A8325100D954D8 /* ModelMapping.swift in Sources */, 8876D5F51FB50AB000EB2E3D /* UILabel+MFCustom.m in Sources */, - AF69D4F9286EA27400BC6862 /* ActionRedirectModel.swift in Sources */, AFBB96B31FBA3B590008D868 /* MVMCoreGetterUtility.m in Sources */, AF43A7071FC4D7A2008E9347 /* MVMCoreObject.m in Sources */, 94C014D924212360005811A9 /* ActionSettingModel.swift in Sources */, D2DEDCB723C63F3B00C44CC4 /* Clamping.swift in Sources */, + AF70699E2880D01400077CF6 /* ActionShareHandler.swift in Sources */, 01DF561421F90ADC00CC099B /* Dictionary+MFConvenience.swift in Sources */, EA3B264C25FC0B7600008074 /* ModelHandlerProtocol.swift in Sources */, + AF7069A02880F0EB00077CF6 /* ActionOpenPageHandler.swift in Sources */, AFBB96B11FBA3B590008D868 /* MVMCoreDispatchUtility.m in Sources */, 946EE1A3237B59C30036751F /* ModelProtocol.swift in Sources */, AFEA17A9209B6A1C00BC6740 /* MVMCoreBlockOperation.m in Sources */, diff --git a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift index 1e88940..3ee233a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift @@ -11,14 +11,21 @@ import Foundation open class ActionActionsHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionActionsModel else { return } - for action in model.actions { - // TODO: Improve to make truly concurrent. - if model.concurrent { - MVMCoreActionHandler.shared()?.asyncHandleAction(with: action, additionalData: additionalData, delegateObject: delegateObject) - } else { - MVMCoreActionHandler.shared()?.syncHandleAction(with: action, additionalData: additionalData, delegateObject: delegateObject) + if model.concurrent { + // TODO: inspect warning. + await withThrowingTaskGroup(of: Void.self) { group in + for action in model.actions { + group.addTask{ + try await MVMCoreActionHandler.shared()?.handleAction(with: action, additionalData: additionalData, delegateObject: delegateObject) + } + } + } + } else { + for action in model.actions { + try Task.checkCancellation() + try await MVMCoreActionHandler.shared()?.handleAction(with: action, additionalData: additionalData, delegateObject: delegateObject) } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift index 258a26a..f93b009 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift @@ -11,12 +11,16 @@ import Foundation open class ActionBackHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionBackModel else { return } - do { - delegateObject?.actionDelegate?.handleBackAction?(try MVMCoreActionHandler.convertActionToJSON(model), additionalData: additionalData) ?? MVMCoreNavigationHandler.shared()?.removeCurrentViewController() - } catch { - MVMCoreActionHandler.shared()?.handle(error: error, model: model, delegateObject: delegateObject, additionalData: additionalData) + let json = try MVMCoreActionHandler.convertActionToJSON(model) + // TODO: Make this actually async properly. + if delegateObject?.actionDelegate?.handleBackAction != nil { + delegateObject?.actionDelegate?.handleBackAction?(json, additionalData: additionalData) + } else { + Task { + await MVMCoreNavigationHandler.shared()?.removeCurrentViewController() + } } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift index 47730a4..644bbb1 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift @@ -11,8 +11,9 @@ import Foundation open class ActionCallHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionCallModel else { return } - MVMCoreActionUtility.linkAway("tel://\(model.actionType)", appURLString: nil) + // https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/PhoneLinks/PhoneLinks.html#//apple_ref/doc/uid/TP40007899-CH6-SW1 + try await ActionOpenUrlHandler.openURL(with: "tel://\(model.callNumber)") } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift index d52885c..6206f4e 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift @@ -11,7 +11,7 @@ import Foundation open class ActionCancelHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { delegateObject?.actionDelegate?.handleCancel?(model.toJSON(), additionalData: additionalData) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift new file mode 100644 index 0000000..18ac4ec --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift @@ -0,0 +1,116 @@ +// +// ActionContactHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 7/12/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation +import ContactsUI + +open class ActionContactHandler: NSObject, MVMCoreActionHandlerProtocol, CNContactPickerDelegate, CNContactViewControllerDelegate { + + /// A continuation to keep the process running until we are finished. + private var continuation: CheckedContinuation? + + required public override init() {} + + /// Sets the continuation and runs the closure. + private func continueInTask(with closure: @escaping () async -> Void) async { + let _: Bool = await withCheckedContinuation { continuation in + self.continuation = continuation + Task { + await closure() + } + } + } + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) async throws { + guard let model = model as? ActionContactModel else { return } + + switch model.approach { + case .add: + await continueInTask { + await MainActor.run { + let controller = CNContactPickerViewController() + + // Setting to accessibilityValue as a workaround to pass data via the delegate function. + controller.view.accessibilityIdentifier = model.phoneNumber + controller.delegate = self + MVMCoreNavigationHandler.shared()?.present(controller, animated: true) + } + } + case .create: + let contact = CNMutableContact() + let phone = CNLabeledValue(label: CNLabelOther, value: CNPhoneNumber(stringValue: model.phoneNumber)) + contact.phoneNumbers = [phone] + if let givenName = model.firstName { + contact.givenName = givenName + } + if let familyName = model.lastName { + contact.familyName = familyName + } + + let store = CNContactStore() + await continueInTask { + await MainActor.run { + let controller = CNContactViewController(forNewContact: contact) + controller.contactStore = store + controller.delegate = self + MVMCoreNavigationHandler.shared()?.push(controller, animated: true) + } + } + case .view: + let symbols = CharacterSet(charactersIn: ".+()-  ") + let contactPhoneNumber = model.phoneNumber.components(separatedBy: symbols).joined(separator: "") + let number = CNPhoneNumber(stringValue: contactPhoneNumber) + let contactPredicate = CNContact.predicateForContacts(matching: number) + let displayedKeys = [await CNContactViewController.descriptorForRequiredKeys()] + let store = CNContactStore() + let viewContact = try store.unifiedContacts(matching: contactPredicate, keysToFetch: displayedKeys).first + await continueInTask { + await MainActor.run { + let controller = CNContactViewController(forNewContact: viewContact) + controller.contactStore = store + controller.delegate = self + MVMCoreNavigationHandler.shared()?.push(controller, animated: true) + } + } + } + } + + public func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) { + MVMCoreNavigationHandler.shared()?.popTopViewController(animated: true, navigationController: nil, delegate: nil, completionHandler: { + self.continuation?.resume(returning: true) + }) + } + + public func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool { + true + } + + public func contactPickerDidCancel(_ picker: CNContactPickerViewController) { + MVMCoreNavigationHandler.shared()?.dismissTopViewController(animated: true, delegate: nil, completionHandler: { + self.continuation?.resume(returning: true) + }) + } + + public func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) { + // This is a means to pass the data to this delegate function. + guard let phoneNumber = picker.view.accessibilityIdentifier else { return } + let store = CNContactStore() + let existingContact = contact.mutableCopy() as! CNMutableContact + let number = CNPhoneNumber(stringValue: phoneNumber) + let labelValue = CNLabeledValue(label: CNLabelOther, value: number) + var phoneNumbers = [labelValue] + phoneNumbers.append(contentsOf: existingContact.phoneNumbers) + existingContact.phoneNumbers = phoneNumbers + MVMCoreDispatchUtility.performBlock(onMainThread: { + let controller = CNContactViewController(forNewContact: existingContact) + controller.contactStore = store + controller.delegate = self + MVMCoreNavigationHandler.shared()?.push(controller, animated: true) + }) + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionContactModel.swift b/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift similarity index 85% rename from MVMCore/MVMCore/Models/ActionType/ActionContactModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionContactModel.swift index f473042..3f4c2f8 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionContactModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift @@ -14,13 +14,19 @@ import ContactsUI // MARK: - Properties //-------------------------------------------------- + public enum Approach: String, Codable { + case add + case create + case view + } + public static var identifier: String = "contact" public var actionType: String = ActionContactModel.identifier public var phoneNumber: String public var firstName: String? public var lastName: String? - public var approach: String = KeyCreate + public var approach: Approach = .create public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? @@ -28,7 +34,7 @@ import ContactsUI // MARK: - Initializer //-------------------------------------------------- - public init(phoneNumber: String, firstName: String? = nil, lastName: String? = nil, approach: String = KeyCreate, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + public init(phoneNumber: String, firstName: String? = nil, lastName: String? = nil, approach: Approach = .create, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { self.phoneNumber = phoneNumber self.firstName = firstName self.lastName = lastName diff --git a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift index ac3af3e..f2608f3 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift @@ -11,5 +11,5 @@ import Foundation open class ActionNoopHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) {} + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {} } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift new file mode 100644 index 0000000..1aa1cc9 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -0,0 +1,69 @@ +// +// OpenPageHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 7/14/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +// TODO: Modernize this. +open class ActionOpenPageHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + guard let model = model as? ActionOpenPageModel else { return } + var requestParameters: MVMCoreRequestParameters + if let _ = delegateObject?.actionDelegate?.getRequestParameters { + requestParameters = try delegateObject!.actionDelegate!.getRequestParameters(for: model, delegateObject: delegateObject, additionData: additionalData) + } else { + let json = try MVMCoreActionHandler.convertActionToJSON(model) + requestParameters = MVMCoreRequestParameters(actionMap: json)! + } + try await performRequestAddingClientParameters(with: requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) + } + + /// Adds client parameters and makes calls performRequest() + open func performRequestAddingClientParameters(with requestParameters: MVMCoreRequestParameters, model: ActionOpenPageModel, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + // Adds any client parameters to the request parameters. + if let parametersToFetch = model.clientParameters, + let fetchedParameters = try await getClientParameters(with: parametersToFetch, requestParameters: requestParameters.parameters as? [String : Any] ?? [:], showLoadingOverlay: !requestParameters.backgroundRequest) { + requestParameters.add(fetchedParameters) + } + + // Makes the request and waits for it. + try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: requestParameters, delegateObject: delegateObject, additionalData: additionalData) + } + + /// Iterates through the clientParameters list. Gets values from the individual handlers and attaches the parameters to extraParameters. + func getClientParameters(with model: ClientParameterModel, requestParameters: [String: Any], showLoadingOverlay: Bool) async throws -> [String: Any]? { + + if showLoadingOverlay { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.startLoading() + } + + do { + let parameters: [String: Any]? = try await withCheckedThrowingContinuation({ continuation in + do { + let handler = ClientParameterHandler() + try handler.getParameters(with: model, requestParameters: requestParameters) { parameters in + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) + continuation.resume(returning: parameters) + } + } catch { + continuation.resume(throwing: error) + } + }) + if showLoadingOverlay { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) + } + return parameters + } catch { + if showLoadingOverlay { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) + } + throw error + } + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift similarity index 95% rename from MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift index a646ef7..3e8620b 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift @@ -13,7 +13,7 @@ //-------------------------------------------------- public class var identifier: String { "openPage" } - public var actionType: String { ActionOpenPageModel.identifier } + public var actionType: String = identifier public var pageType: String public var modules: [String]? public var baseURL: String? diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift index 67aa4bd..0adbe69 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift @@ -8,11 +8,26 @@ import Foundation +extension String { + public enum StringError: Error { + case addingPercentEncoding(string: String) + } + + func addingPercentEncodingThrowable(withAllowedCharacters characterSet: CharacterSet) throws -> String { + guard let string = addingPercentEncoding(withAllowedCharacters: characterSet) else { + throw StringError.addingPercentEncoding(string: self) + } + return string + } +} + open class ActionOpenSMSHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenSMSModel else { return } - MVMCoreActionUtility.linkAway("sms:\(model.phoneNumber)&body=\(model.message ?? "")".addingPercentEncoding(withAllowedCharacters: CharacterSet(charactersIn: "0123456789+-.")), appURLString: nil) + // https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/SMSLinks/SMSLinks.html#:~:text=Note%3A%20SMS%20text%20links%20are,number%20of%20the%20SMS%20message. + let string = try "sms:\(model.phoneNumber)&body=\(model.message ?? "")".addingPercentEncodingThrowable(withAllowedCharacters: CharacterSet.urlQueryAllowed) + try await ActionOpenUrlHandler.openURL(with: string) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift index 991ed89..177cc98 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift @@ -8,43 +8,105 @@ import Foundation -open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { - required public init() {} - - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - guard let model = model as? ActionOpenUrlModel else { return } - MVMCoreDispatchUtility.performBlock(onMainThread: { [self] in - // Try loading the app url first, otherwise fall back to browser url. - guard let appURL = model.appURL else { - openURL(model: model, additionalData: additionalData, delegateObject: delegateObject) - return +public extension URL { + private enum URLError: MVMError, CustomStringConvertible { + case invalid(string: String) + + public var description: String { + switch self { + case URLError.invalid(let string): + return "Failed to create url with string: \(string)" } - UIApplication.shared.open(appURL, options: model.appURLOptions?.options ?? [:]) { loaded in - guard !loaded else { return } - MVMCoreLoggingHandler.shared()?.handleDebugMessage("Failed to open app url: \(appURL)") - openURL(model: model, additionalData: additionalData, delegateObject: delegateObject) - } - }) - } - - /// Opens the url. - open func openURL(model: ActionOpenUrlModel, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - UIApplication.shared.open(model.browserUrl, options: [:]) { [self] loaded in - guard !loaded else { return } - handleError(model: model, additionalData: additionalData, delegateObject: delegateObject) } } - /// Handles any url loading errors. - open func handleError(model: ActionOpenUrlModel, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - if let error = MVMCoreErrorObject(title: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorTitle), - message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), - messageToLog: "Unable to load URL: \(String(describing: model.browserUrl))", code: ErrorCode.linkawayFailed.rawValue, - domain: ErrorDomainNative, - location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: ActionOpenUrlModel.identifier)) { - MVMCoreDispatchUtility.performBlock(inBackground: { - MVMCoreActionHandler.shared()?.handleActionError(error, actionInformation: model.toJSON(), additionalData: additionalData, delegateObject: delegateObject) - }) + static func createURL(with string: String) throws -> URL { + guard let url = URL(string: string) else { + throw URL.URLError.invalid(string: string) + } + return url + } +} + +public enum MVMCoreError: MVMError { + case error(code: Int, messageToDisplay: String, location: String) + case errorObject(_ object: MVMCoreErrorObject) + + public var errorCode: Int { + switch self { + case MVMCoreError.error(let code, _, _): + return code + case MVMCoreError.errorObject(let object): + return object.code + } + } + + public var description: String { + switch self { + case MVMCoreError.error(_, let message, _): + return message + case MVMCoreError.errorObject(let object): + return object.messageToDisplay ?? "Error" } } } + +protocol MVMError: LocalizedError, CustomNSError {} +extension MVMError { + public var errorDescription: String? { return MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) } + + public static var errorDomain: String { + return ErrorDomainNative + } +} + +open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { + required public init() {} + + public enum URLError: MVMError, CustomStringConvertible { + case failedToOpen(url: URL) + + public var description: String { + switch self { + case ActionOpenUrlHandler.URLError.failedToOpen(let url): + return "Failed to open url: \(url.absoluteString)" + } + } + } + + public static func openURL(with string: String) async throws { + let url = try URL.createURL(with: string) + try await ActionOpenUrlHandler.open(url: url) + } + + @MainActor public static func open(url: URL) async throws { + try await withCheckedThrowingContinuation { continuation in + UIApplication.shared.open(url, options: [:]) { successful in + if successful { + continuation.resume() + } else { + continuation.resume(throwing: ActionOpenUrlHandler.URLError.failedToOpen(url: url)) + } + } + } as Void + } + + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + guard let model = model as? ActionOpenUrlModel else { return } + + // Try loading the app url first, otherwise fall back to browser url. + if let appURL = model.appURL { + do { + try await ActionOpenUrlHandler.open(url: appURL) + return + } catch { + // Log error and continue + MVMCoreLoggingHandler.shared()?.handleDebugMessage("Failed to open app url: \(appURL)") + if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) { + MVMCoreLoggingHandler.addError(toLog: errorObject) + } + } + } + try await ActionOpenUrlHandler.open(url: model.browserUrl) + } +} diff --git a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift index e803ab7..1717446 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift @@ -12,13 +12,23 @@ import Foundation open class ActionPreviousSubmitHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - guard let model = model as? ActionPreviousSubmitModel else { return } - let json = model.toJSON() - delegateObject?.actionDelegate?.prepareRequest?(forPreviousSubmission: json, additionalData: additionalData, submit: { requestParameters, dataForPage in - MVMCoreActionHandler.shared()?.updateRequestParameters(beforeHandleOpenPageAction: requestParameters, callBack: { requestParameters in - delegateObject?.actionDelegate?.handleOpenPage?(for: requestParameters, actionInformation: json, additionalData: dataForPage) ?? MVMCoreActionHandler.defaultHandleOpenPage(for: requestParameters, actionInformation: json, additionalData: additionalData, delegateObject: delegateObject) - }) - }) + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + guard let loadObject = (delegateObject?.actionDelegate as? MVMCoreViewControllerProtocol)?.loadObject, + let previousRequest = loadObject?.requestParameters else { return } + + // Combines data + var data = loadObject?.dataForPage + if let previousData = data, + let additionalData = additionalData { + data = previousData.merging(additionalData) {(new,_) in new} + } + + if let _ = delegateObject?.actionDelegate?.handleOpenPage { + // Legacy handling. Will lose the task. + let json = try MVMCoreActionHandler.convertActionToJSON(model) + delegateObject?.actionDelegate?.handleOpenPage?(for: previousRequest, actionInformation: json, additionalData: additionalData) + } else { + try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: previousRequest, delegateObject: delegateObject, additionalData: additionalData) + } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift deleted file mode 100644 index e890617..0000000 --- a/MVMCore/MVMCore/ActionHandling/ActionRedirectHandler.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// ActionRedirectHandler.swift -// MVMCore -// -// Created by Scott Pfeil on 6/30/22. -// Copyright © 2022 myverizon. All rights reserved. -// - -import Foundation - -open class ActionRedirectHandler: MVMCoreActionHandlerProtocol { - required public init() {} - - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - // Have delegate redirect. - MVMCoreSessionObject.sharedGlobal()?.redirect(withInfo: model.toJSON()) - } -} diff --git a/MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift b/MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift deleted file mode 100644 index d36722e..0000000 --- a/MVMCore/MVMCore/ActionHandling/ActionRedirectModel.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// ActionRedirectModel.swift -// MVMCore -// -// Created by Scott Pfeil on 6/30/22. -// Copyright © 2022 myverizon. All rights reserved. -// - -import Foundation - -@objcMembers public class ActionRedirectModel: ActionOpenPageModel { - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - - public override class var identifier: String { KeyActionTypeRedirect } - public override var actionType: String { ActionRedirectModel.identifier } -} diff --git a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift index c468fe6..e8bbd7b 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift @@ -11,18 +11,25 @@ import Foundation open class ActionRestartHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionRestartModel else { return } - - // Invalidates the session before restarting. - MVMCoreSessionTimeHandler.sharedSession()?.invalidateSession({ error in - guard error?.code != NSURLErrorCancelled else { return } - if let error = error { - MVMCoreActionHandler.shared()?.handle(errorObject: error, model: model, delegateObject: delegateObject, additionalData: additionalData) - } else { - // Restarts the app (forcing any passed in page types). - MVMCoreSessionObject.sharedGlobal()?.restartSession(withPageType: model.pageType, parameters: model.extraParameters, clearAllVariables: true) - } - }) + + let _: Bool = try await withCheckedThrowingContinuation { continuation in + + // Invalidates the session before restarting. + MVMCoreSessionTimeHandler.sharedSession()?.invalidateSession({ error in + if let error = error { + guard error.code != NSURLErrorCancelled else { + continuation.resume(returning: false) + return + } + continuation.resume(throwing: MVMCoreError.errorObject(error)) + } else { + // Restarts the app (forcing any passed in page types). + MVMCoreSessionObject.sharedGlobal()?.restartSession(withPageType: model.pageType, parameters: model.extraParameters, clearAllVariables: true) + continuation.resume(returning: true) + } + }) + } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift index 9d09408..bea0fd1 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift @@ -11,7 +11,7 @@ import Foundation open class ActionSettingHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - MVMCoreActionUtility.linkAway(UIApplication.openSettingsURLString, appURLString: nil) + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + try await ActionOpenUrlHandler.openURL(with: await UIApplication.openSettingsURLString) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift new file mode 100644 index 0000000..7573434 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift @@ -0,0 +1,54 @@ +// +// ActionShareHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 7/14/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation +open class ActionShareHandler: MVMCoreActionHandlerProtocol { + + required public init() {} + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) async throws { + guard let model = model as? ActionShareModel else { return } + var shareData: [Any] + switch model.sharedType { + case .text: + shareData = [model.sharedText] + case .url: + let url = try URL.createURL(with: model.sharedText) + shareData = [model.sharedText, url] + } + try await shareWith(activityItems: shareData, model: model) + } + + @MainActor public func shareWith(activityItems: [Any], model: ActionShareModel) async throws { + try await withCheckedThrowingContinuation { continuation in + let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + controller.popoverPresentationController?.sourceView = MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn?.view + controller.completionWithItemsHandler = {(activityType: UIActivity.ActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in + if completed { + // Activity was completed, considered finished. + if activityType == .copyToPasteboard { + // Allow copy + MVMCoreSessionObject.sharedGlobal()?.copyString(toClipboard: model.sharedText) + } + continuation.resume() + } else if let _ = activityType { + // If a specific type of activity failed, the activity controller is still presented, cannot continue yet. + if let error = error, + let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: #function) { + MVMCoreLoggingHandler.addError(toLog: errorObject) + } + } else if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume() + } + } + MVMCoreNavigationHandler.shared()?.present(controller, animated: true) + } as Void + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionShareModel.swift b/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift similarity index 76% rename from MVMCore/MVMCore/Models/ActionType/ActionShareModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionShareModel.swift index 893c638..ed7cbea 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionShareModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift @@ -8,6 +8,12 @@ @objcMembers public class ActionShareModel: ActionModelProtocol { + + public enum SharedType: String, Codable { + case text + case url + } + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -15,7 +21,7 @@ public static var identifier: String = "share" public var actionType: String = ActionShareModel.identifier - public var sharedType: String + public var sharedType: SharedType public var sharedText: String public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? @@ -24,7 +30,7 @@ // MARK: - Initializer //-------------------------------------------------- - public init(sharedText: String, sharedType: String, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + public init(sharedText: String, sharedType: SharedType, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { self.sharedType = sharedType self.sharedText = sharedText self.extraParameters = extraParameters diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift index ab572f8..e857e84 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift @@ -15,8 +15,9 @@ public extension MVMCoreActionDelegateProtocol { return true } - /// Handles any action errors. - func handleAction(error: MVMCoreErrorObject, action: ActionModelProtocol, additionalData: [AnyHashable: Any]?) { - MVMCoreActionHandler.shared()?.defaultHandleActionError(error, additionalData: additionalData) + /// Allows the delegate to create the request parameters as desired. + func getRequestParameters(for model: ActionOpenPageModel, delegateObject: DelegateObject? = nil, additionData: [AnyHashable : Any]? = nil) throws -> MVMCoreRequestParameters { + let json = try MVMCoreActionHandler.convertActionToJSON(model) + return MVMCoreRequestParameters(actionMap: json)! } } diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h index 85616b7..e29a184 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h @@ -24,9 +24,6 @@ // Handles the back actions. Can overwrite for special loading. - (void)handleBackAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData; -// Prepares to call the previous submit request again. Can overwrite for special loading. Be sure to call submit() block to perform the actual load. -- (void)prepareRequestForPreviousSubmission:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData submit:(nonnull void (^)(MVMCoreRequestParameters * _Nonnull requestParameters, NSDictionary * _Nullable dataForPage))submit; - // Handles the linkaway action. Call the block to continue to linkaway. - (void)shouldLinkAwayWithURL:(nullable NSURL *)URL appURL:(nullable NSURL *)appURL actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData linkAwayBlock:(nonnull void (^)(NSURL * _Nullable appURL, NSURL * _Nullable URL, NSDictionary * _Nullable actionInformation, NSDictionary * _Nullable additionalData))linkAwayBlock; diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift index 4160c61..89759ce 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift @@ -8,15 +8,47 @@ import Foundation +/// Handlers that can be registered and used by the MVMCoreActionHandler to handle actions. public protocol MVMCoreActionHandlerProtocol: ModelHandlerProtocol { init() + /// Legacy function to handle actions. func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) + func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws } -public extension MVMCoreActionHandler { +extension MVMCoreActionHandlerProtocol { - /// Converts the action to json for old action handler to handle. - static func convertActionToJSON(_ model: ActionModelProtocol) throws -> [AnyHashable: Any] { + public func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + } + + public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + handleAction(model, additionalData: additionalData, delegateObject: delegateObject) + } +} + +@objc open class MVMCoreActionHandler: NSObject { + + enum ActionError: MVMError { + case unknownAction(type: String) + + public var description: String { + switch self { + case MVMCoreActionHandler.ActionError.unknownAction(let type): + return "Couldn't perform action: \(type)" + } + } + } + + /// Returns the action handler stored in the MVMCoreObject + @objc(sharedActionHandler) + public static func shared() -> Self? { + return MVMCoreActionUtility.initializerClassCheck(MVMCoreObject.sharedInstance()?.actionHandler, classToVerify: self) as? Self + } + + // MARK: - Conversions + + /// Converts the action to json for legacy functions. + static public func convertActionToJSON(_ model: ActionModelProtocol) throws -> [AnyHashable: Any] { let data = try model.encode() guard let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [AnyHashable: Any] else { throw ModelRegistry.Error.decoderError @@ -25,86 +57,134 @@ public extension MVMCoreActionHandler { } /// Creates a model from the action json. - static func createModel(with json: [AnyHashable: Any]) throws -> ActionModelProtocol { + static public func createModel(with json: [AnyHashable: Any], delegateObject: DelegateObject? = nil) throws -> ActionModelProtocol { guard let castedSelf = json as? [String: Any] else { throw ModelRegistry.Error.decoderOther(message: "Dictionary is not of type [String: Any]") } guard let actionType = ModelRegistry.getType(for: castedSelf.stringForkey(KeyActionType), with: ActionModelProtocol.self) else { throw ModelRegistry.Error.decoderErrorModelNotMapped() } - guard let actionModel = try actionType.decode(jsonDict: castedSelf) as? ActionModelProtocol else { + guard let actionModel = try actionType.decode(jsonDict: castedSelf, delegateObject: delegateObject) as? ActionModelProtocol else { throw ModelRegistry.Error.decoderOther(message: "Could not decode to ActionModelProtocol") } return actionModel } - /// Converts the Error to an ErrorObject and calls handle(errorObject - func handle(error: Error, model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) { - guard let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) else { return } - handle(errorObject: errorObject, model: model, delegateObject: delegateObject, additionalData: additionalData) - } + // MARK: - Error Handling - /// Handles the error by calling actionDelegate handleAction, else ActionHandler defaultHandleActionError. - func handle(errorObject: MVMCoreErrorObject, model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) { - delegateObject?.actionDelegate?.handleAction(error: errorObject, action: model, additionalData: additionalData) ?? defaultHandleActionError(errorObject, additionalData: additionalData) - } - - @objc func hasActionHandler(actionType: String?, actionInformation: [String: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> Bool { - - guard //ensure there is a actinType - let actionType = actionType, - //ensure there is a serialized version of the Action - let actionInformation = actionInformation, - //esnure the actionModelType - let actionModelType = ModelRegistry.getType(for: actionType, with: ActionModelProtocol.self), - //ensure there is handlerType for the action of MVMCoreActionHandlerProtocol - let actionHandlerType = try? ModelRegistry.getHandlerType(for: actionModelType) as? MVMCoreActionHandlerProtocol.Type - else { return false } - - do { - //ensure the decoded actionModel is of ActionModelProtocol - guard let actionModel = try actionModelType.decode(jsonDict: actionInformation) as? ActionModelProtocol else { - throw ModelRegistry.Error.decoderOther(message: "Could not decode to ActionModelProtocol") - } - - //create the handler since we know it can initialize - let actionHandler = actionHandlerType.init() - - //call the handleAction of the handler - actionHandler.handleAction(actionModel, additionalData: additionalData, delegateObject: delegateObject) - - } catch { - //log the error - if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "") { - MVMCoreActionHandler.shared()?.defaultHandleActionError(errorObject, additionalData: additionalData) - } - } - - //found the handler, returning true no matter if there was a failure in the do...catch - return true - } - - /// Start action on current thread. - func syncHandleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - do { - let json = try MVMCoreActionHandler.convertActionToJSON(model) - synchronouslyHandleAction(model.actionType, actionInformation: json, additionalData: additionalData, delegateObject: delegateObject) - } catch { - handle(error: error, model: model, delegateObject: delegateObject, additionalData: additionalData) + /// Converts the Error into an ErrorObject. + open func getActionErrorObject(for error: Error, actionType: String, delegateObject: DelegateObject? = nil) -> MVMCoreErrorObject { + switch error { + case MVMCoreError.errorObject(let object): + return object + default: + return MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: actionType))! } } - /// Start action on dispatched background thread. - func asyncHandleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - do { - let json = try MVMCoreActionHandler.convertActionToJSON(model) - handleAction(model.actionType, actionInformation: json, additionalData: additionalData, delegateObject: delegateObject) - } catch { - handle(error: error, model: model, delegateObject: delegateObject, additionalData: additionalData) - } + /// Handles the error by calling actionDelegate handleActionError, else ActionHandler defaultHandleActionError. + open func handle(errorObject: MVMCoreErrorObject, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) { + delegateObject?.actionDelegate?.handleActionError?(errorObject, additionalData: additionalData) ?? MVMCoreActionHandler.shared()?.defaultHandleActionError(errorObject, additionalData: additionalData) } - @objc static func getErrorLocation(with delegate: MVMCoreActionDelegateProtocol?, actionType: String) -> String { + /// Returns a common description for the error location. + @objc public static func getErrorLocation(with delegate: MVMCoreActionDelegateProtocol?, actionType: String) -> String { return "\(String(describing: delegate))_\(actionType)" } + + // MARK: - Action Handling + + /// Legacy handle action with json. + @objc(handleActionWithDictionary:additionalData:delegateObject:) + open func handleAction(with json: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: json \(String(describing: json))") + Task(priority: .userInitiated) { + do { + guard let json = json else { + throw ModelRegistry.Error.keyNotFound + } + let model = try MVMCoreActionHandler.createModel(with: json, delegateObject: delegateObject) + _ = asyncHandleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + } catch { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Error \(error)") + let errorObject = getActionErrorObject(for: error, actionType: json?.stringForkey(KeyActionType) ?? "noAction", delegateObject: delegateObject) + handle(errorObject: errorObject, delegateObject: delegateObject, additionalData: additionalData) + } + } + } + + /// Handle an action with the given model. + open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { + try Task.checkCancellation() + // Allow the delegate to intercept. + guard delegateObject?.actionDelegate?.shouldPerform(action: model, additionalData: additionalData, delegateObject: delegateObject) ?? true else { return } + + // Log the action + logAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + let uuid = UUID() + do { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Begin Action \(model.actionType) \(uuid)") + let handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type + let handler = handlerType.init() + try await handler.performAction(model, delegateObject: delegateObject, additionalData: additionalData) + } catch ModelRegistry.Error.handlerNotMapped { + try Task.checkCancellation() + // Allows custom handling if there no handler for the action. + guard try await handleUnregisteredAction(with: model, additionalData: additionalData, delegateObject: delegateObject) else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Failed Action Unknown \(model.actionType) \(uuid)") + throw ActionError.unknownAction(type: model.actionType) + } + } catch { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Failed Action \(error) \(model.actionType) \(uuid)") + throw error + } + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Successful Action \(model.actionType) \(uuid)") + } + + /// Performs the action as a task and returns immediately. + open func asyncHandleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> Task { + let task = Task(priority: .userInitiated) { + try Task.checkCancellation() + do { + try await handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + } catch { + try Task.checkCancellation() + let errorObject = getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) + handle(errorObject: errorObject, delegateObject: delegateObject, additionalData: additionalData) + } + } + + Task { + //try await Task.sleep(nanoseconds: 1 * 1_000_000_000) + //task.cancel() + } + Task { + let result = await task.result + do { + try result.get() + print("ActionHandler: task done") + } catch { + print("ActionHandler: \(error)") + } + } + return task + } + + // MARK: - Subclassables + + /// Subclass to log the action was fired. + open func logAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + delegateObject?.actionDelegate?.logAction?(withActionInformation: model.toJSON(), additionalData: additionalData) + } + + /// Subclass to handle and any actions where a handler was not registered. Return if it was handled or not. + open func handleUnregisteredAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws -> Bool { + return false + } + + /// Logs the error. + @objc open func defaultHandleActionError(_ error: MVMCoreErrorObject, additionalData: [AnyHashable: Any]?) { + guard error.logError else { return } + MVMCoreLoggingHandler.addError(toLog: error) + } } diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h index 1854927..434a083 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h @@ -7,72 +7,61 @@ // // Can be subclassed to handle app specific actions as well. -#import -#import -#import -#import -@class DelegateObject; - -extern NSString * _Nonnull const KeyActionType; -extern NSString * _Nonnull const KeyActionTypeLinkAway; -extern NSString * _Nonnull const KeyActionTypeOpen; - -@interface MVMCoreActionHandler : NSObject - -/// Returns the shared action handler -+ (nullable instancetype)sharedActionHandler; - -/// Convenience function for handling actions. This will pull action and pageInfo out of the dictionary and call handleAction: actionInformation: with those values -- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Asynchronously handles action (dispatches and calls below function). Used by server driven user actions.. -- (void)handleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Synchronously handles action. Used by server driven user actions.. -- (void)synchronouslyHandleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Iterates through the clientParameters list. Gets values from the individual handlers and attaches the parameters to extraParameters. -- (void)getClientParameter:(nullable NSDictionary *)clientParametersMap requestParameters:(nullable NSDictionary *)requestParameters showLoadingOverlay:(BOOL)showLoadingOverlay completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler; - -#pragma mark - Actions -/// by default, returns the original RequestParameter that passed in. Can be overriden for some generic updates to the RequestParameter before handle open page action gets called. -- (void)updateRequestParametersBeforeHandleOpenPageAction:(nonnull MVMCoreRequestParameters *)requestParameters callBack:(void (^_Nonnull)(MVMCoreRequestParameters * _Nonnull requestParameters))callback; - -/// Logs the action. Currently is not action information driven... depends on delegate. -- (void)logAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Tries to open a page -- (void)openPageAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Goes back -- (void)backAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Subclass this to handle other custom actions. Return YES if handled, and NO if not. -- (BOOL)handleOtherActions:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Last chance to handle unknown actions before throwing an error -- (void)unknownAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Handles action errors. -- (void)handleActionError:(nullable MVMCoreErrorObject *)error actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -#pragma mark - Default Action Protocol Functions - -/// Currently no default log action but this will eventually be server driven. -+ (void)defaultLogAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Sends the request to the load handler. -+ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// By default, throws an error, calling defaultHandleActionError. -+ (void)defaultHandleUnknownActionType:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; - -/// Logs the error. -- (void)defaultHandleActionError:(nonnull MVMCoreErrorObject *)error additionalData:(nullable NSDictionary *)additionalData; - -#pragma mark - Deprecated - -/// Convenience function for handling actions. This will pull action and pageInfo out of the dictionary and call handleAction: actionInformation: with those values -- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate __deprecated; - -@end +//#import +//#import +//#import +//#import +//@class DelegateObject; +// +//extern NSString * _Nonnull const KeyActionType; +//extern NSString * _Nonnull const KeyActionTypeLinkAway; +//extern NSString * _Nonnull const KeyActionTypeOpen; +// +//@interface MVMCoreActionHandler : NSObject +// +///// Returns the shared action handler +//+ (nullable instancetype)sharedActionHandler; +// +///// Convenience function for handling actions. This will pull action and pageInfo out of the dictionary and call handleAction: actionInformation: with those values +//- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Asynchronously handles action (dispatches and calls below function). Used by server driven user actions.. +//- (void)handleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Iterates through the clientParameters list. Gets values from the individual handlers and attaches the parameters to extraParameters. +//- (void)getClientParameter:(nullable NSDictionary *)clientParametersMap requestParameters:(nullable NSDictionary *)requestParameters showLoadingOverlay:(BOOL)showLoadingOverlay completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler; +// +//#pragma mark - Actions +// +///// Logs the action. Currently is not action information driven... depends on delegate. +//- (void)logAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Subclass this to handle other custom actions. Return YES if handled, and NO if not. +//- (BOOL)handleOtherActions:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Last chance to handle unknown actions before throwing an error +//- (void)unknownAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Handles action errors. +//- (void)handleActionError:(nullable MVMCoreErrorObject *)error actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +//#pragma mark - Default Action Protocol Functions +// +///// Currently no default log action but this will eventually be server driven. +//+ (void)defaultLogAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Sends the request to the load handler. +//+ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// By default, throws an error, calling defaultHandleActionError. +//+ (void)defaultHandleUnknownActionType:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// +///// Logs the error. +//- (void)defaultHandleActionError:(nonnull MVMCoreErrorObject *)error additionalData:(nullable NSDictionary *)additionalData; +// +//#pragma mark - Deprecated +// +///// Convenience function for handling actions. This will pull action and pageInfo out of the dictionary and call handleAction: actionInformation: with those values +//- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate __deprecated; +// +//@end diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m index f3974f0..37a6a02 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m @@ -1,379 +1,176 @@ +//// +//// MVMCoreActionHandler.m +//// myverizon +//// +//// Created by Scott Pfeil on 11/20/15. +//// Copyright © 2015 Verizon Wireless. All rights reserved. +//// // -// MVMCoreActionHandler.m -// myverizon +//#import +//#import "MVMCoreLoggingHandler.h" +//#import "MVMCoreCache.h" +//#import "MVMCoreSessionTimeHandler.h" +//#import "MVMCoreLoadHandler.h" +//#import "MVMCoreNavigationHandler.h" +//#import "MVMCoreDispatchUtility.h" +//#import "NSDictionary+MFConvenience.h" +//#import "MVMCoreGetterUtility.h" +//#import "MVMCoreRequestParameters.h" +//#import "MVMCoreErrorObject.h" +//#import "MVMCoreJSONConstants.h" +//#import "MVMCoreHardcodedStringsConstants.h" +//#import "MVMCoreErrorConstants.h" +//#import "MVMCoreActionUtility.h" +//#import "MVMCoreSessionObject.h" +//#import "MVMCoreObject.h" +//#import "MVMCorePresentationDelegateProtocol.h" +//#import +//#import +//#import "MVMCoreLoadingOverlayHandler.h" +//#import // -// Created by Scott Pfeil on 11/20/15. -// Copyright © 2015 Verizon Wireless. All rights reserved. +//NSString * const KeyActionType = @"actionType"; +//NSString * const KeyActionTypeLinkAway = @"openURL"; +//NSString * const KeyActionTypeOpen = @"openPage"; // - -#import -#import "MVMCoreLoggingHandler.h" -#import "MVMCoreCache.h" -#import "MVMCoreSessionTimeHandler.h" -#import "MVMCoreLoadHandler.h" -#import "MVMCoreNavigationHandler.h" -#import "MVMCoreDispatchUtility.h" -#import "NSDictionary+MFConvenience.h" -#import "MVMCoreGetterUtility.h" -#import "MVMCoreRequestParameters.h" -#import "MVMCoreErrorObject.h" -#import "MVMCoreJSONConstants.h" -#import "MVMCoreHardcodedStringsConstants.h" -#import "MVMCoreErrorConstants.h" -#import "MVMCoreActionUtility.h" -#import "MVMCoreSessionObject.h" -#import "MVMCoreObject.h" -#import "MVMCorePresentationDelegateProtocol.h" -#import -#import -#import "MVMCoreLoadingOverlayHandler.h" -#import - -NSString * const KeyActionType = @"actionType"; -NSString * const KeyActionTypeLinkAway = @"openURL"; -NSString * const KeyActionTypeOpen = @"openPage"; - -@interface MVMCoreActionHandler() -@end - -@implementation MVMCoreActionHandler -+ (nullable instancetype)sharedActionHandler { - return [MVMCoreActionUtility initializerClassCheck:[MVMCoreObject sharedInstance].actionHandler classToVerify:self]; -} - -- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - NSString *action = [dictionary stringForKey:KeyActionType]; - [self handleAction:action actionInformation:dictionary additionalData:additionalData delegateObject:delegateObject]; -} - -- (void)handleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self synchronouslyHandleAction:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; - }); -} - -- (void)synchronouslyHandleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - // Logs the action. - [self logAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - if ([actionType isEqualToString:KeyActionTypeOpen]) { - [self openPageAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeContact]) { - [self contactAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if ([actionType isEqualToString:KeyActionTypeShare]) { - [self shareAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - - } else if (![self handleOtherActions:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]) { - // not a known action type. - [self unknownAction:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; - } -} - -- (void)getClientParameter:(nullable NSDictionary *)clientParametersMap requestParameters:(nullable NSDictionary *)requestParameters showLoadingOverlay:(BOOL)showLoadingOverlay completionHandler:(nonnull void (^)(NSDictionary * _Nullable parameters))completionHandler { - - if (!clientParametersMap) { - completionHandler(nil); - return; - } - - if (showLoadingOverlay) { - [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading]; - } - - void (^stopLoadingOverlay)(void) = ^(void) { - if (showLoadingOverlay) { - [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:true]; - } - }; - - NSError *error = nil; - [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"Fetching client parameters"]; - - ClientParameterHandler *clientParameterHandler = [[ClientParameterHandler alloc] init]; - [clientParameterHandler getParametersWith:clientParametersMap - requestParameters:requestParameters - error:&error - completionHandler:^(NSDictionary * _Nullable clientParameters) { - [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"Finshed fetching client parameters"]; - if (clientParameters) { - stopLoadingOverlay(); - completionHandler(clientParameters); - } else { - [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"No client parameters"]; - stopLoadingOverlay(); - completionHandler(nil); - } - }]; - - if (error) { - stopLoadingOverlay(); - completionHandler(nil); - [MVMCoreLoggingHandler addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:@"MVMCoreActionHandler->setClientParameter"]]; - } -} - -#pragma mark - Actions - -- (void)updateRequestParametersBeforeHandleOpenPageAction:(nonnull MVMCoreRequestParameters *)requestParameters callBack:(void (^_Nonnull)(MVMCoreRequestParameters * _Nonnull requestParameters))callback { - //does not do anything by default, can be override - callback(requestParameters); -} - -- (void)logAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - if ([delegateObject.actionDelegate respondsToSelector:@selector(logActionWithActionInformation:additionalData:)]) { - [delegateObject.actionDelegate logActionWithActionInformation:actionInformation additionalData:additionalData]; - } else { - [[self class] defaultLogAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; - } -} - -- (void)openPageAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - // Loads the given page type. - NSString *pageType = [actionInformation stringForKey:KeyPageType]; - - if (pageType.length == 0) { - // No page type to load, show error. - MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess] code:ErrorCodeNoPageType domain:ErrorDomainNative location:[NSString stringWithFormat:@"%@_%@",NSStringFromClass([delegateObject.actionDelegate class]),KeyActionTypeOpen]]; - [self handleActionError:error actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; - return; - } - - MVMCoreRequestParameters *requestParameters = [[MVMCoreRequestParameters alloc] initWithActionMap:actionInformation]; - [self updateRequestParametersBeforeHandleOpenPageAction:requestParameters callBack:^(MVMCoreRequestParameters * _Nonnull requestParameters) { - if ([delegateObject.actionDelegate respondsToSelector:@selector(handleOpenPageForRequestParameters:actionInformation:additionalData:)]) { - [delegateObject.actionDelegate handleOpenPageForRequestParameters:requestParameters actionInformation:actionInformation additionalData:additionalData]; - } else { - [MVMCoreActionHandler defaultHandleOpenPageForRequestParameters:requestParameters - actionInformation:actionInformation - additionalData:additionalData - delegateObject:delegateObject]; - } - }]; -} - -- (void)shareAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - NSString *shareType = [actionInformation string:KeyShareType]; - NSString *shareText = [actionInformation string:KeyShareText]; - - if (!shareText || !shareType) { - return; - } - - NSArray *shareData = nil; - - if ([shareType isEqualToString:@"text"]) { - shareData = @[shareText]; - - } else if ([shareType isEqualToString:@"url"]) { - NSURL *url = [NSURL URLWithString:shareText]; - if (url) { - shareData = @[shareText, url]; - } else { - shareData = @[shareText]; - } - } else if ([shareType isEqualToString:@"image"]) { - // TODO: Implement image parsing. 🏂 - - } else if ([shareType isEqualToString:@"file"]) { - // TODO: Implement file parsing. 🌋 - } - - if (shareData.count > 0) { - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:shareData applicationActivities:nil]; - - void(^activityCompletion)(UIActivityType, BOOL, NSArray*, NSError*) = ^(UIActivityType activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { - if (activityType == UIActivityTypeCopyToPasteboard) { - [[MVMCoreSessionObject sharedGlobal] copyStringToClipboard:shareText]; - } - }; - - activityViewController.completionWithItemsHandler = activityCompletion; - activityViewController.popoverPresentationController.sourceView = [MVMCoreNavigationHandler sharedNavigationHandler].viewControllerToPresentOn.view; - [[MVMCoreNavigationHandler sharedNavigationHandler] presentViewController:activityViewController animated:YES]; - }]; - } -} - -- (void)backAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - [self handleActionWithDictionary:actionInformation ?: @{KeyActionType: KeyActionTypeBack} additionalData:additionalData delegateObject:delegateObject]; -} - -- (void)contactAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - __weak typeof(self) weakSelf = self; - - NSString *phoneNumber = [actionInformation string:@"phoneNumber"]; - - if (!phoneNumber) { return; } - CNMutableContact *contact = [[CNMutableContact alloc] init]; - NSString *approach = [actionInformation stringForKey:@"approach"]; - - CNLabeledValue *phone = [[CNLabeledValue alloc] initWithLabel:CNLabelOther value:[[CNPhoneNumber alloc] initWithStringValue:phoneNumber]]; - contact.phoneNumbers = @[phone]; - - if ([approach isEqualToString:KeyAdd]) { - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - CNContactPickerViewController *controller = [[CNContactPickerViewController alloc] init]; - // Setting to accessibilityValue as a workaround to pass data via the delegate function. - [controller.view setAccessibilityIdentifier:phoneNumber]; - controller.delegate = weakSelf; - - [[MVMCoreNavigationHandler sharedNavigationHandler] presentViewController:controller animated:YES]; - }]; - } else if ([approach isEqualToString:KeyCreate]) { - contact.givenName = [actionInformation string:@"firstName"]; - contact.familyName = [actionInformation string:@"lastName"]; - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - CNContactStore *store = [[CNContactStore alloc] init]; - CNContactViewController *controller = [CNContactViewController viewControllerForNewContact:contact]; - controller.contactStore = store; - controller.delegate = weakSelf; - - [[MVMCoreNavigationHandler sharedNavigationHandler] pushViewController:controller animated:YES]; - }]; - } else if ([approach isEqualToString:KeyView]) { - NSCharacterSet *symbols = [NSCharacterSet characterSetWithCharactersInString:@".+()-  "]; - NSString *contactPhoneNumber = [actionInformation string:@"phoneNumber"]; - contactPhoneNumber = [[contactPhoneNumber componentsSeparatedByCharactersInSet:symbols] componentsJoinedByString:@""]; - CNPhoneNumber *number = [[CNPhoneNumber alloc] initWithStringValue:contactPhoneNumber]; - NSPredicate *contactPredicate = [CNContact predicateForContactsMatchingPhoneNumber:number]; - NSArray *displayedKeys = @[[CNContactViewController descriptorForRequiredKeys]]; - CNContactStore *store = [[CNContactStore alloc] init]; - NSError *error; - CNContact *viewContact = [[store unifiedContactsMatchingPredicate:contactPredicate keysToFetch:displayedKeys error:&error] firstObject]; - - if (viewContact) { - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - CNContactViewController *controller = [CNContactViewController viewControllerForNewContact:viewContact]; - controller.contactStore = store; - controller.delegate = weakSelf; - - [[MVMCoreNavigationHandler sharedNavigationHandler] pushViewController:controller animated:YES]; - }]; - } else { - // No contacts found, show an alert - MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorContactUnAvailable] code:ErrorCodeDefault domain:ErrorDomainNative location:[NSString stringWithFormat:@"%@_%@",NSStringFromClass([delegateObject.actionDelegate class]),KeyActionTypeContact]]; - error.silentError = NO; - [self handleActionError:error actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; - } - } -} - -- (BOOL)handleOtherActions:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - return [self hasActionHandlerWithActionType:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; -} - -- (void)unknownAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - if ([delegateObject.actionDelegate respondsToSelector:@selector(handleUnknownActionType:actionInformation:additionalData:)]) { - [delegateObject.actionDelegate handleUnknownActionType:actionType actionInformation:actionInformation additionalData:additionalData]; - } else { - [MVMCoreActionHandler defaultHandleUnknownActionType:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; - } -} - -- (void)handleActionError:(nullable MVMCoreErrorObject *)error actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - if (error) { - if ([delegateObject.actionDelegate respondsToSelector:@selector(handleActionError:additionalData:)]) { - [delegateObject.actionDelegate handleActionError:error additionalData:additionalData]; - } else { - [self defaultHandleActionError:error additionalData:additionalData]; - } - } -} - -#pragma mark - CNContactViewControllerDelegate - -- (void)contactViewController:(CNContactViewController *)viewController didCompleteWithContact:(CNContact *)contact { - [[MVMCoreNavigationHandler sharedNavigationHandler] removeCurrentViewController]; -} - -- (BOOL)contactViewController:(CNContactViewController *)viewController shouldPerformDefaultActionForContactProperty:(CNContactProperty *)property { - return YES; -} - -#pragma mark - CNContactPickerDelegate - -- (void)contactPickerDidCancel:(CNContactPickerViewController *)picker { - [[MVMCoreNavigationHandler sharedNavigationHandler] removeCurrentViewController]; -} - -- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact { - - // This is a means to pass the data to this delegate function. - NSString *phoneNumber = picker.view.accessibilityIdentifier; - - if (!phoneNumber) { return; } - CNContactStore *store = [[CNContactStore alloc] init]; - CNMutableContact *existingContact = [(CNMutableContact *)contact mutableCopy]; - CNPhoneNumber *number = [[CNPhoneNumber alloc] initWithStringValue:phoneNumber]; - CNLabeledValue *labelValue = [[CNLabeledValue alloc] initWithLabel:CNLabelOther value:number]; - NSMutableArray *phoneNumbers = [NSMutableArray new]; - [phoneNumbers addObject:labelValue]; - [phoneNumbers addObjectsFromArray:existingContact.phoneNumbers]; - existingContact.phoneNumbers = phoneNumbers; - - __weak typeof(self) weakSelf = self; - - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - CNContactViewController *controller = [CNContactViewController viewControllerForNewContact:existingContact]; - controller.contactStore = store; - controller.delegate = weakSelf; - - [[MVMCoreNavigationHandler sharedNavigationHandler] pushViewController:controller animated:YES]; - }]; -} - -#pragma mark - Default Action Protocol - -+ (void)defaultLogAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject{ - // Currently no default log action but this will eventually be server driven. -} - -+ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - NSDictionary *clientParamters = [actionInformation dict:KeyClientParameters]; - if (clientParamters) { - [[MVMCoreActionHandler sharedActionHandler] getClientParameter:clientParamters - requestParameters: requestParameters.parameters - showLoadingOverlay: !requestParameters.backgroundRequest - completionHandler: ^(NSDictionary * _Nullable jsonDictionary) { - [requestParameters addRequestParameters:jsonDictionary]; - [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegateObject:delegateObject]; - }]; - } else { - [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegateObject:delegateObject]; - } -} - -+ (void)defaultHandleUnknownActionType:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { - - MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess] code:ErrorCodeUnknownActionType domain:ErrorDomainNative location:[NSString stringWithFormat:@"%@Requests_%@",NSStringFromClass([delegateObject.actionDelegate class]),actionType]]; - [[self sharedActionHandler] defaultHandleActionError:error additionalData:additionalData]; -} - -- (void)defaultHandleActionError:(nonnull MVMCoreErrorObject *)error additionalData:(nullable NSDictionary *)additionalData { - - // Logs the error. - if (error.logError) { - [MVMCoreLoggingHandler addErrorToLog:error]; - } -} - -#pragma mark - Deprecated - -- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate { - - DelegateObject *delegateObject = [[DelegateObject alloc] init]; - delegateObject.actionDelegate = delegate; - delegateObject.presentationDelegate = delegate; - delegateObject.loadDelegate = delegate; - [self handleActionWithDictionary:dictionary additionalData:additionalData delegateObject:delegateObject]; -} - -@end +//@interface MVMCoreActionHandler() +//@end +// +//@implementation MVMCoreActionHandler +//+ (nullable instancetype)sharedActionHandler { +// return [MVMCoreActionUtility initializerClassCheck:[MVMCoreObject sharedInstance].actionHandler classToVerify:self]; +//} +// +//- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// [self handleActionWith:dictionary additionalData:additionalData delegateObject:delegateObject]; +//} +// +//- (void)handleAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// [self handleActionWith:actionInformation additionalData:additionalData delegateObject:delegateObject]; +//} +// +//- (void)getClientParameter:(nullable NSDictionary *)clientParametersMap requestParameters:(nullable NSDictionary *)requestParameters showLoadingOverlay:(BOOL)showLoadingOverlay completionHandler:(nonnull void (^)(NSDictionary * _Nullable parameters))completionHandler { +// +// if (!clientParametersMap) { +// completionHandler(nil); +// return; +// } +// +// if (showLoadingOverlay) { +// [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading]; +// } +// +// void (^stopLoadingOverlay)(void) = ^(void) { +// if (showLoadingOverlay) { +// [[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:true]; +// } +// }; +// +// NSError *error = nil; +// [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"Fetching client parameters"]; +// +// ClientParameterHandler *clientParameterHandler = [[ClientParameterHandler alloc] init]; +// [clientParameterHandler getParametersWith:clientParametersMap +// requestParameters:requestParameters +// error:&error +// completionHandler:^(NSDictionary * _Nullable clientParameters) { +// [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"Finshed fetching client parameters"]; +// if (clientParameters) { +// stopLoadingOverlay(); +// completionHandler(clientParameters); +// } else { +// [MVMCoreLoggingHandler logDebugMessageWithDelegate:@"No client parameters"]; +// stopLoadingOverlay(); +// completionHandler(nil); +// } +// }]; +// +// if (error) { +// stopLoadingOverlay(); +// completionHandler(nil); +// [MVMCoreLoggingHandler addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:@"MVMCoreActionHandler->setClientParameter"]]; +// } +//} +// +//#pragma mark - Actions +// +//- (void)logAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// if ([delegateObject.actionDelegate respondsToSelector:@selector(logActionWithActionInformation:additionalData:)]) { +// [delegateObject.actionDelegate logActionWithActionInformation:actionInformation additionalData:additionalData]; +// } else { +// [[self class] defaultLogAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; +// } +//} +// +// +// +//- (BOOL)handleOtherActions:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// return NO; +//} +// +//- (void)unknownAction:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// if ([delegateObject.actionDelegate respondsToSelector:@selector(handleUnknownActionType:actionInformation:additionalData:)]) { +// [delegateObject.actionDelegate handleUnknownActionType:actionType actionInformation:actionInformation additionalData:additionalData]; +// } else { +// [MVMCoreActionHandler defaultHandleUnknownActionType:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; +// } +//} +// +//- (void)handleActionError:(nullable MVMCoreErrorObject *)error actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// if (error) { +// if ([delegateObject.actionDelegate respondsToSelector:@selector(handleActionError:additionalData:)]) { +// [delegateObject.actionDelegate handleActionError:error additionalData:additionalData]; +// } else { +// [self defaultHandleActionError:error additionalData:additionalData]; +// } +// } +//} +// +//#pragma mark - Default Action Protocol +// +//+ (void)defaultLogAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject{ +// // Currently no default log action but this will eventually be server driven. +//} +// +//+ (void)defaultHandleOpenPageForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// +// NSDictionary *clientParamters = [actionInformation dict:KeyClientParameters]; +// if (clientParamters) { +// [[MVMCoreActionHandler sharedActionHandler] getClientParameter:clientParamters +// requestParameters: requestParameters.parameters +// showLoadingOverlay: !requestParameters.backgroundRequest +// completionHandler: ^(NSDictionary * _Nullable jsonDictionary) { +// [requestParameters addRequestParameters:jsonDictionary]; +// [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegateObject:delegateObject]; +// }]; +// } else { +// [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegateObject:delegateObject]; +// } +//} +// +//+ (void)defaultHandleUnknownActionType:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { +// +// MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess] code:ErrorCodeUnknownActionType domain:ErrorDomainNative location:[NSString stringWithFormat:@"%@Requests_%@",NSStringFromClass([delegateObject.actionDelegate class]),actionType]]; +// [[self sharedActionHandler] defaultHandleActionError:error additionalData:additionalData]; +//} +// +//- (void)defaultHandleActionError:(nonnull MVMCoreErrorObject *)error additionalData:(nullable NSDictionary *)additionalData { +// +// // Logs the error. +// if (error.logError) { +// [MVMCoreLoggingHandler addErrorToLog:error]; +// } +//} +// +//#pragma mark - Deprecated +// +//- (void)handleActionWithDictionary:(nullable NSDictionary *)dictionary additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate { +// +// DelegateObject *delegateObject = [[DelegateObject alloc] init]; +// delegateObject.actionDelegate = delegate; +// delegateObject.presentationDelegate = delegate; +// delegateObject.loadDelegate = delegate; +// [self handleActionWithDictionary:dictionary additionalData:additionalData delegateObject:delegateObject]; +//} +// +//@end diff --git a/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.h b/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.h index 2fdc9de..3a47d8b 100644 --- a/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.h +++ b/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.h @@ -33,6 +33,10 @@ extern NSString * const KeyView; extern NSString * const KeyLinks; extern NSString * const KeyTitle; extern NSString * const KeyMessage; + +extern NSString * const KeyActionType; +extern NSString * const KeyActionTypeLinkAway; +extern NSString * const KeyActionTypeOpen; extern NSString * const KeyActionTypeRestart; extern NSString * const KeyActionTypeBack; extern NSString * const KeyActionTypeShare; diff --git a/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.m b/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.m index 772fd05..3ad5e93 100644 --- a/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.m +++ b/MVMCore/MVMCore/Constants/MVMCoreJSONConstants.m @@ -35,6 +35,10 @@ NSString * const KeyView = @"view"; NSString * const KeyLinks = @"Links"; NSString * const KeyTitle = @"title"; NSString * const KeyMessage = @"message"; + +NSString * const KeyActionType = @"actionType"; +NSString * const KeyActionTypeLinkAway = @"openURL"; +NSString * const KeyActionTypeOpen = @"openPage"; NSString * const KeyActionTypeRestart = @"restart"; NSString * const KeyActionTypeBack = @"back"; NSString * const KeyActionTypeShare = @"share"; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift new file mode 100644 index 0000000..e382d4e --- /dev/null +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift @@ -0,0 +1,22 @@ +// +// MVMCoreLoadHandler+Extension.swift +// MVMCore +// +// Created by Scott Pfeil on 7/15/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +public extension MVMCoreLoadHandler { + /// Performs the request. + func performRequest(with requestParameters: MVMCoreRequestParameters, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + // Makes the request and waits for it. + guard let operation = MVMCoreLoadHandler.sharedGlobal()?.loadRequest(requestParameters, dataForPage: additionalData, delegateObject: delegateObject) else { return } + await withCheckedContinuation { continuation in + operation.completionBlock = { + continuation.resume() + } + } + } +} diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m index 27043bf..b226ce9 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m @@ -48,6 +48,7 @@ self.registeredLoadQueues = [[NSMutableArray alloc] initWithCapacity:3]; [self registerLoadQueue:self.blockingLoadQueue]; [self registerLoadQueue:self.backgroundLoadQueue]; + } return self; } diff --git a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift index 67c738a..8ead4e1 100644 --- a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift +++ b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift @@ -44,21 +44,23 @@ ///} /// completionHandler can return flat dictinary or a map. It depends on the paramters handler open func getParameters(with clientParameters: [String: Any], requestParameters: [String: Any], completionHandler:@escaping ([String: Any]?) -> ()) throws { - guard let clientParameterModel = try getClientParameterModel(clientParameters) else { completionHandler(nil) return } + try getParameters(with: clientParameterModel, requestParameters: requestParameters, completionHandler: completionHandler) + } + + open func getParameters(with model: ClientParameterModel, requestParameters: [String: Any], completionHandler:@escaping ([String: Any]?) -> ()) throws { - let timeout = clientParameterModel.timeout ?? 30.0 - + let timeout = model.timeout ?? 30.0 // Dispatch setup on queue to ensure setup is complete before completion callbacks. // Don't use [weak self]. Object is deallocated in the dispatch queue. parametersWorkQueue.async(group: group, qos: .userInitiated) { // Create the handler list so that same object can be used when merging. Merging needs default value in case of timeout - for parameterModel in clientParameterModel.list { + for parameterModel in model.list { if let parameterHandler = self.createParametersHandler(parameterModel) { self.parameterHandlerList.append(parameterHandler) } @@ -101,7 +103,7 @@ // Queue the results for merge. self.parametersWorkQueue.async { if (returnedList[index] != nil) { - MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, message: "Client parameter \(parameterType) has already executed. The completion handler should only be called once!", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: String(describing: ClientParameterHandler.self))!) + MVMCoreLoggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, message: "Client parameter \(parameterType) has already executed. The completion handler should only be called once!", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: String(describing: ClientParameterHandler.self))!) } else { returnedList[index] = receivedParameter self.group.leave() // Leaving is only done after setup (barriered). diff --git a/MVMCore/MVMCore/Models/ModelMapping.swift b/MVMCore/MVMCore/Models/ModelMapping.swift index 605c7ff..f587849 100644 --- a/MVMCore/MVMCore/Models/ModelMapping.swift +++ b/MVMCore/MVMCore/Models/ModelMapping.swift @@ -12,20 +12,19 @@ open class func registerActions() { ModelRegistry.register(ActionRunJavaScriptModel.self) - ModelRegistry.register(ActionOpenPageModel.self) + ModelRegistry.register(handler: ActionOpenPageHandler.self, for: ActionOpenPageModel.self) ModelRegistry.register(handler: ActionOpenUrlHandler.self, for: ActionOpenUrlModel.self) ModelRegistry.register(handler: ActionCallHandler.self, for: ActionCallModel.self) ModelRegistry.register(handler: ActionCallHandler.self, for: ActionCallModel.self) ModelRegistry.register(handler: ActionBackHandler.self, for: ActionBackModel.self) - ModelRegistry.register(ActionShareModel.self) + ModelRegistry.register(handler: ActionShareHandler.self, for: ActionShareModel.self) ModelRegistry.register(handler: ActionRestartHandler.self, for: ActionRestartModel.self) - ModelRegistry.register(handler: ActionRedirectHandler.self, for: ActionRedirectModel.self) ModelRegistry.register(handler: ActionPreviousSubmitHandler.self, for: ActionPreviousSubmitModel.self) ModelRegistry.register(handler: ActionCancelHandler.self, for: ActionCancelModel.self) ModelRegistry.register(handler: ActionSettingHandler.self, for: ActionSettingModel.self) ModelRegistry.register(handler: ActionNoopHandler.self, for: ActionNoopModel.self) ModelRegistry.register(handler: ActionActionsHandler.self, for: ActionActionsModel.self) ModelRegistry.register(handler: ActionOpenSMSHandler.self, for: ActionOpenSMSModel.self) - ModelRegistry.register(ActionContactModel.self) + ModelRegistry.register(handler: ActionContactHandler.self, for: ActionContactModel.self) } } diff --git a/MVMCore/MVMCore/Singletons/MVMCoreObject.h b/MVMCore/MVMCore/Singletons/MVMCoreObject.h index 03a8411..e790fee 100644 --- a/MVMCore/MVMCore/Singletons/MVMCoreObject.h +++ b/MVMCore/MVMCore/Singletons/MVMCoreObject.h @@ -17,6 +17,7 @@ #import #import #import +@class MVMCoreActionHandler; @interface MVMCoreObject : NSObject From f123be046c393721c9c3701344d23be4a1b8a068 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 21 Jul 2022 12:39:43 -0400 Subject: [PATCH 03/16] Modernization migrations and json passing --- .../ActionHandling/ActionBackHandler.swift | 17 +++--- .../ActionHandling/ActionCancelHandler.swift | 4 +- .../ActionOpenPageHandler.swift | 39 ++++++++------ .../ActionHandling/ActionOpenUrlHandler.swift | 8 ++- .../ActionHandling/MVMCoreActionHandler.swift | 53 +++++++++++++++++-- .../MVMCoreLoadRequestOperation.m | 1 - MVMCore/MVMCore/MVMCore.h | 1 - .../MVMCoreNavigationHandler.h | 3 ++ .../MVMCoreNavigationHandler.m | 8 ++- MVMCore/MVMCore/Singletons/MVMCoreObject.h | 1 - 10 files changed, 101 insertions(+), 34 deletions(-) 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 From 71b214686228742a676715b8b078fd732cd909f5 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Fri, 22 Jul 2022 10:20:46 -0400 Subject: [PATCH 04/16] Minor cleanup --- .../ActionOpenPageHandler.swift | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift index e1de127..a3f00e8 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -49,33 +49,26 @@ open class ActionOpenPageHandler: MVMCoreActionHandlerProtocol { } 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]? { - if showLoadingOverlay { MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.startLoading() } - - do { - let parameters: [String: Any]? = try await withCheckedThrowingContinuation({ continuation in - do { - try getParameters(with: model, requestParameters: requestParameters) { parameters in - MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) - continuation.resume(returning: parameters) - } - } catch { - continuation.resume(throwing: error) - } - }) + defer { if showLoadingOverlay { MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) } - return parameters - } catch { - if showLoadingOverlay { - MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) - } - throw error } + return try await withCheckedThrowingContinuation({ continuation in + do { + try getParameters(with: model, requestParameters: requestParameters) { parameters in + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) + continuation.resume(returning: parameters) + } + } catch { + continuation.resume(throwing: error) + } + }) } } From e6dca10d87fd8842a1b90b652c8687dd26038ae5 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 28 Jul 2022 18:22:00 -0400 Subject: [PATCH 05/16] modernization. Adding json protocol to continue to support legacy (unfortunately) --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 8 + .../ActionHandling/ActionActionsHandler.swift | 23 ++- .../ActionHandling/ActionActionsModel.swift | 2 +- .../ActionHandling/ActionBackHandler.swift | 25 +-- .../ActionHandling/ActionBackModel.swift | 2 +- .../ActionHandling/ActionCallHandler.swift | 2 +- .../ActionHandling/ActionCallModel.swift | 2 +- .../ActionHandling/ActionCancelHandler.swift | 9 +- .../ActionHandling/ActionCancelModel.swift | 2 +- .../ActionHandling/ActionContactHandler.swift | 2 +- .../ActionHandling/ActionContactModel.swift | 2 +- .../ActionDelegateProtocol.swift | 8 - .../ActionHandling/ActionNoopHandler.swift | 2 +- .../ActionHandling/ActionNoopModel.swift | 2 +- .../ActionOpenPageHandler.swift | 58 +++++-- .../ActionHandling/ActionOpenPageModel.swift | 28 ++-- .../ActionHandling/ActionOpenSMSHandler.swift | 2 +- .../ActionHandling/ActionOpenSMSModel.swift | 2 +- .../ActionHandling/ActionOpenUrlHandler.swift | 44 +---- .../ActionHandling/ActionOpenUrlModel.swift | 16 +- .../ActionPreviousSubmitHandler.swift | 14 +- .../ActionPreviousSubmitModel.swift | 2 +- .../ActionHandling/ActionRestartHandler.swift | 10 +- .../ActionHandling/ActionRestartModel.swift | 2 +- .../ActionHandling/ActionSettingHandler.swift | 2 +- .../ActionHandling/ActionSettingModel.swift | 2 +- .../ActionHandling/ActionShareHandler.swift | 4 +- .../ActionHandling/ActionShareModel.swift | 2 +- .../ActionHandling/MVMCoreActionHandler.swift | 155 +++++++++++++----- MVMCore/MVMCore/Utility/MVMCoreError.swift | 41 +++++ MVMCore/MVMCore/Utility/MVMError.swift | 18 ++ 31 files changed, 329 insertions(+), 164 deletions(-) create mode 100644 MVMCore/MVMCore/Utility/MVMCoreError.swift create mode 100644 MVMCore/MVMCore/Utility/MVMError.swift diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index a928cbe..d14b68c 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -92,6 +92,8 @@ AF43A7411FC5FA6F008E9347 /* MVMCoreViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF43A74C1FC6109F008E9347 /* MVMCoreSessionObject.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF43A74D1FC6109F008E9347 /* MVMCoreSessionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */; }; + AF60A7F2289212CA00919EEB /* MVMError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F1289212CA00919EEB /* MVMError.swift */; }; + AF60A7F4289212EB00919EEB /* MVMCoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F3289212EB00919EEB /* MVMCoreError.swift */; }; AF69D4E9286E54D500BC6862 /* ActionCallHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */; }; AF69D4EB286E586200BC6862 /* ActionRestartHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */; }; AF69D4ED286E5D8C00BC6862 /* ActionCancelHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.swift */; }; @@ -253,6 +255,8 @@ AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreViewProtocol.h; sourceTree = ""; }; AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreSessionObject.h; sourceTree = ""; }; AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreSessionObject.m; sourceTree = ""; }; + AF60A7F1289212CA00919EEB /* MVMError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMError.swift; sourceTree = ""; }; + AF60A7F3289212EB00919EEB /* MVMCoreError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreError.swift; sourceTree = ""; }; AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCallHandler.swift; sourceTree = ""; }; AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRestartHandler.swift; sourceTree = ""; }; AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCancelHandler.swift; sourceTree = ""; }; @@ -403,6 +407,8 @@ 8876D5D41FB50AAB00EB2E3D /* Utility */ = { isa = PBXGroup; children = ( + AF60A7F1289212CA00919EEB /* MVMError.swift */, + AF60A7F3289212EB00919EEB /* MVMCoreError.swift */, 881D26911FCC9D180079C521 /* MVMCoreErrorObject.h */, 881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */, 881D26921FCC9D180079C521 /* MVMCoreOperation.h */, @@ -921,6 +927,7 @@ 30349BF21FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m in Sources */, D2DEDCB923C6400600C44CC4 /* UnitInterval.swift in Sources */, 94C014D3242119E6005811A9 /* ActionPreviousSubmitModel.swift in Sources */, + AF60A7F2289212CA00919EEB /* MVMError.swift in Sources */, 8876D5E91FB50AB000EB2E3D /* NSArray+MFConvenience.m in Sources */, D27073B725BB45C4001C7246 /* ActionActionsModel.swift in Sources */, 946EE1B2237B5F260036751F /* JSONValue.swift in Sources */, @@ -967,6 +974,7 @@ AFBB96611FBA3A570008D868 /* MVMCoreLoadObject.m in Sources */, AF787413286DEF8B00670588 /* ActionBackHandler.swift in Sources */, 946EE1B4237B619D0036751F /* Encoder.swift in Sources */, + AF60A7F4289212EB00919EEB /* MVMCoreError.swift in Sources */, AFBB96941FBA3A9A0008D868 /* MVMCorePresentAnimationOperation.m in Sources */, 94C014D524211AF0005811A9 /* ActionCancelModel.swift in Sources */, AF43A5841FBB66DE008E9347 /* MVMCoreConstants.m in Sources */, diff --git a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift index 3ee233a..59095e2 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift @@ -8,10 +8,29 @@ import Foundation -open class ActionActionsHandler: MVMCoreActionHandlerProtocol { +open class ActionActionsHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + guard let model = model as? ActionActionsModel else { return } + let actions = JSON.arrayForKey("actions") + if model.concurrent { + await withThrowingTaskGroup(of: Void.self) { group in + for case let (index, action as [AnyHashable: Any]) in actions.enumerated() { + group.addTask{ + try await MVMCoreActionHandler.shared()?.handleAction(with: model.actions[index], json: action, additionalData: additionalData, delegateObject: delegateObject) + } + } + } + } else { + for case let (index, action as [AnyHashable: Any]) in actions.enumerated() { + try Task.checkCancellation() + try await MVMCoreActionHandler.shared()?.handleAction(with: model.actions[index], json: action, additionalData: additionalData, delegateObject: delegateObject) + } + } + } + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionActionsModel else { return } if model.concurrent { // TODO: inspect warning. diff --git a/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift index 0638f3e..33444c0 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift @@ -8,7 +8,7 @@ import Foundation -@objcMembers open class ActionActionsModel: ActionModelProtocol { +open class ActionActionsModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift index 14cefcb..30f2594 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift @@ -8,23 +8,24 @@ import Foundation -open class ActionBackHandler: MVMCoreActionHandlerProtocol { +open class ActionBackHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { - guard let model = model as? ActionBackModel else { return } + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { 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) - } + closure(JSON, additionalData) } else { - await withCheckedContinuation { (continuation: CheckedContinuation) in - Task { - await MVMCoreNavigationHandler.shared()?.removeCurrentViewController(true, completionHandler: { - continuation.resume() - }) - } + try await performAction(model, delegateObject: delegateObject, additionalData: additionalData) + } + } + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + await withCheckedContinuation { (continuation: CheckedContinuation) in + Task(priority: .userInitiated) { + await MVMCoreNavigationHandler.shared()?.removeCurrentViewController(true, completionHandler: { + continuation.resume() + }) } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionBackModel.swift b/MVMCore/MVMCore/ActionHandling/ActionBackModel.swift index 3ecdfe4..fea8eea 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionBackModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionBackModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionBackModel: ActionModelProtocol { +public struct ActionBackModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift index 644bbb1..7010746 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift @@ -11,7 +11,7 @@ import Foundation open class ActionCallHandler: MVMCoreActionHandlerProtocol { required public init() {} - 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? ActionCallModel else { return } // https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/PhoneLinks/PhoneLinks.html#//apple_ref/doc/uid/TP40007899-CH6-SW1 try await ActionOpenUrlHandler.openURL(with: "tel://\(model.callNumber)") diff --git a/MVMCore/MVMCore/ActionHandling/ActionCallModel.swift b/MVMCore/MVMCore/ActionHandling/ActionCallModel.swift index e5dba2b..dc6a32e 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCallModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCallModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionCallModel: ActionModelProtocol { +public struct ActionCallModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift index d8aa646..7f5169c 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift @@ -8,12 +8,13 @@ import Foundation -open class ActionCancelHandler: MVMCoreActionHandlerProtocol { +open class ActionCancelHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + delegateObject?.actionDelegate?.handleCancel?(JSON, additionalData: additionalData) + } + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { - try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in - delegateObject?.actionDelegate?.handleCancel?(json, additionalData: additionalData) - } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift b/MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift index 8a77c11..83ae013 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionCancelModel: ActionModelProtocol { +public struct ActionCancelModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift index 18ac4ec..7231eb2 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift @@ -20,7 +20,7 @@ open class ActionContactHandler: NSObject, MVMCoreActionHandlerProtocol, CNConta private func continueInTask(with closure: @escaping () async -> Void) async { let _: Bool = await withCheckedContinuation { continuation in self.continuation = continuation - Task { + Task(priority: .userInitiated) { await closure() } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift b/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift index 3f4c2f8..9b975fc 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift @@ -9,7 +9,7 @@ import ContactsUI -@objcMembers public class ActionContactModel: ActionModelProtocol { +public struct ActionContactModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift index 001a2b5..eeb5734 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift @@ -12,9 +12,6 @@ public protocol ActionDelegateProtocol: MVMCoreActionDelegateProtocol { /// Allows the delegate to cancel the action. func shouldPerform(action: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) -> Bool - /// Allows the delegate to create the request parameters as desired. - func getRequestParameters(for model: ActionOpenPageModel, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) throws -> (MVMCoreRequestParameters,[AnyHashable : Any]?) - /// Allows the delegate to handle any custom actions that are not registered with the Action Handler. func handlesUnknownAction(for model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> Bool } @@ -25,11 +22,6 @@ public extension ActionDelegateProtocol { return true } - func getRequestParameters(for model: ActionOpenPageModel, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable : Any]? = nil) throws -> (MVMCoreRequestParameters,[AnyHashable : Any]?) { - let json = try MVMCoreActionHandler.convertActionToJSON(model) - return (MVMCoreRequestParameters(actionMap: json)!,additionalData) - } - func handlesUnknownAction(for model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> Bool { return false } diff --git a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift index f2608f3..30e5780 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift @@ -11,5 +11,5 @@ import Foundation open class ActionNoopHandler: MVMCoreActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {} + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {} } diff --git a/MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift b/MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift index 25f3ded..941e80a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift @@ -6,7 +6,7 @@ // Copyright © 2020 myverizon. All rights reserved. // -@objcMembers public class ActionNoopModel: ActionModelProtocol { +public struct ActionNoopModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift index a3f00e8..f4f2e98 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -8,30 +8,54 @@ import Foundation -open class ActionOpenPageHandler: MVMCoreActionHandlerProtocol { +public protocol ActionOpenPageDelegateProtocol { + /// Allows the delegate to create the request parameters as desired. + func getRequestParameters(for model: ActionOpenPageModel, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) throws -> (MVMCoreRequestParameters,[AnyHashable : Any]?) +} + +open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenPageModel else { return } - try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in - var additionalData = additionalData - + if model.background != true { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.startLoading() + } + defer { + if model.background != true { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) + } + } + + do { // 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) + var additionalData = additionalData + if let value = try (delegateObject?.actionDelegate as? ActionOpenPageDelegateProtocol)?.getRequestParameters(for: model, delegateObject: delegateObject, additionalData: additionalData) { requestParameters = value.0 additionalData = value.1 } else { - requestParameters = MVMCoreRequestParameters(actionMap: json)! + 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) + closure(requestParameters, JSON, additionalData) } else { try await performRequestAddingClientParameters(with: requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) } + } catch { + try handle(error: error, model: model, delegateObject: delegateObject) + } + } + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + guard let model = model as? ActionOpenPageModel else { return } + do { + let json = try MVMCoreActionHandler.convertActionToJSON(model) + try await performAction(with: json, model: model, delegateObject: delegateObject, additionalData: additionalData) + } catch { + try handle(error: error, model: model, delegateObject: delegateObject) } } @@ -46,6 +70,19 @@ open class ActionOpenPageHandler: MVMCoreActionHandlerProtocol { // Makes the request and waits for it. try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: requestParameters, delegateObject: delegateObject, additionalData: additionalData) } + + /// Ensures background requests do not have showing errors. + private func handle(error: Error, model: ActionOpenPageModel, delegateObject: DelegateObject?) throws { + switch error { + case MVMCoreError.errorObject(let errorObject): + errorObject.silentError = model.background == true + throw MVMCoreError.errorObject(errorObject) + default: + let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType))! + errorObject.silentError = model.background == true + throw MVMCoreError.errorObject(errorObject) + } + } } public extension ClientParameterHandler { @@ -63,7 +100,6 @@ public extension ClientParameterHandler { return try await withCheckedThrowingContinuation({ continuation in do { try getParameters(with: model, requestParameters: requestParameters) { parameters in - MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) continuation.resume(returning: parameters) } } catch { diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift index 3e8620b..9a17245 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift @@ -7,24 +7,24 @@ // -@objcMembers open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, ClientParameterActionProtocol { +open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, ClientParameterActionProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public class var identifier: String { "openPage" } - public var actionType: String = identifier - public var pageType: String - public var modules: [String]? - public var baseURL: String? - public var appContext: String? - public var requestURL: String? - public var extraParameters: JSONValueDictionary? - public var analyticsData: JSONValueDictionary? - public var presentationStyle: String? - public var tabBarIndex: Int? - public var background: Bool? - public var clientParameters: ClientParameterModel? + open class var identifier: String { "openPage" } + open var actionType: String = identifier + open var pageType: String + open var modules: [String]? + open var baseURL: String? + open var appContext: String? + open var requestURL: String? + open var extraParameters: JSONValueDictionary? + open var analyticsData: JSONValueDictionary? + open var presentationStyle: String? + open var tabBarIndex: Int? + open var background: Bool? + open var clientParameters: ClientParameterModel? //-------------------------------------------------- // MARK: - Initialzier diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift index 0adbe69..904e712 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift @@ -24,7 +24,7 @@ extension String { open class ActionOpenSMSHandler: MVMCoreActionHandlerProtocol { required public init() {} - 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? ActionOpenSMSModel else { return } // 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) diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift index ead5d68..88f709d 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionOpenSMSModel: ActionModelProtocol { +public struct ActionOpenSMSModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift index 9403bdb..5f8dbf1 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift @@ -9,7 +9,7 @@ import Foundation public extension URL { - private enum URLError: MVMError, CustomStringConvertible { + enum URLError: MVMError, CustomStringConvertible { case invalid(string: String) public var description: String { @@ -27,40 +27,8 @@ public extension URL { 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 { +open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} public enum URLError: MVMError, CustomStringConvertible { @@ -74,11 +42,13 @@ open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { } } + /// Creates a url and calls open(url: URL) public static func openURL(with string: String) async throws { let url = try URL.createURL(with: string) try await ActionOpenUrlHandler.open(url: url) } + /// Opens the url using UIApplication open(url:). Throws URLError.failedToOpen if it fails. @MainActor public static func open(url: URL) async throws { try await withCheckedThrowingContinuation { continuation in UIApplication.shared.open(url, options: [:]) { successful in @@ -91,6 +61,10 @@ open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { } as Void } + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + try await performAction(model, delegateObject: delegateObject, additionalData: additionalData) + } + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenUrlModel else { return } @@ -102,7 +76,7 @@ open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { } 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)) { + if let errorObject = MVMCoreActionHandler.shared()?.getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) { MVMCoreLoggingHandler.addError(toLog: errorObject) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlModel.swift index 9133883..c9f6f8a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlModel.swift @@ -8,18 +8,18 @@ import Foundation -@objcMembers open class ActionOpenUrlModel: ActionModelProtocol { +open class ActionOpenUrlModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public static var identifier: String = "openURL" - public var actionType: String = ActionOpenUrlModel.identifier - public var browserUrl: URL - public var appURL: URL? - public var appURLOptions: OpenUrlOptionsModel? - public var extraParameters: JSONValueDictionary? - public var analyticsData: JSONValueDictionary? + open class var identifier: String { "openURL" } + open var actionType: String = ActionOpenUrlModel.identifier + open var browserUrl: URL + open var appURL: URL? + open var appURLOptions: OpenUrlOptionsModel? + open var extraParameters: JSONValueDictionary? + open var analyticsData: JSONValueDictionary? //-------------------------------------------------- // MARK: - Initialzier diff --git a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift index 1717446..4a56e7a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift @@ -9,10 +9,18 @@ import Foundation /// Makes the previous request, needs the delegate for this -open class ActionPreviousSubmitHandler: MVMCoreActionHandlerProtocol { +open class ActionPreviousSubmitHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + private var json: [AnyHashable: Any]? + + // Conform to MVMCoreJSONActionHandlerProtocol To allow for legacy handleOpenPage delegate + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + json = JSON + try await performAction(model, delegateObject: delegateObject, additionalData: additionalData) + } + + open 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 } @@ -25,7 +33,7 @@ open class ActionPreviousSubmitHandler: MVMCoreActionHandlerProtocol { if let _ = delegateObject?.actionDelegate?.handleOpenPage { // Legacy handling. Will lose the task. - let json = try MVMCoreActionHandler.convertActionToJSON(model) + let json = try json ?? MVMCoreActionHandler.convertActionToJSON(model) delegateObject?.actionDelegate?.handleOpenPage?(for: previousRequest, actionInformation: json, additionalData: additionalData) } else { try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: previousRequest, delegateObject: delegateObject, additionalData: additionalData) diff --git a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift index 506f056..31290dc 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionPreviousSubmitModel: ActionModelProtocol { +public struct ActionPreviousSubmitModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift index e8bbd7b..d2744b6 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift @@ -11,23 +11,23 @@ import Foundation open class ActionRestartHandler: MVMCoreActionHandlerProtocol { required public init() {} - 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? ActionRestartModel else { return } - let _: Bool = try await withCheckedThrowingContinuation { continuation in + let _: Void = 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) + continuation.resume() 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) + MVMCoreSessionObject.sharedGlobal()?.restartSession(withPageType: model.pageType, parameters: model.extraParameters.toJSON(), clearAllVariables: true) + continuation.resume() } }) } diff --git a/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift index da785ac..7cc2d26 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionRestartModel: ActionModelProtocol { +public struct ActionRestartModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift index bea0fd1..e1c718d 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift @@ -11,7 +11,7 @@ import Foundation open class ActionSettingHandler: MVMCoreActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { try await ActionOpenUrlHandler.openURL(with: await UIApplication.openSettingsURLString) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift b/MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift index 96e95d3..b2fd687 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionSettingModel: ActionModelProtocol { +public struct ActionSettingModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift index 7573434..77dbebf 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift @@ -24,7 +24,7 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol { try await shareWith(activityItems: shareData, model: model) } - @MainActor public func shareWith(activityItems: [Any], model: ActionShareModel) async throws { + @MainActor open func shareWith(activityItems: [Any], model: ActionShareModel, delegateObject: DelegateObject? = nil) async throws { try await withCheckedThrowingContinuation { continuation in let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) controller.popoverPresentationController?.sourceView = MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn?.view @@ -39,7 +39,7 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol { } 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) { + let errorObject = MVMCoreActionHandler.shared()?.getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) { MVMCoreLoggingHandler.addError(toLog: errorObject) } } else if let error = error { diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift b/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift index ed7cbea..2203a8c 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionShareModel: ActionModelProtocol { +public struct ActionShareModel: ActionModelProtocol { public enum SharedType: String, Codable { case text diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift index d1eeb17..9ecd2fd 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift @@ -13,9 +13,16 @@ 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 } +/// Protocol used to bridge legacy, non model based code. Allows us to keep the original json intact and not lose key values during decode/encode. +public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { + /// Perform the function using the original json and model. + func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws +} + extension MVMCoreActionHandlerProtocol { public func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { @@ -27,11 +34,8 @@ 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 { + enum ActionError: MVMError, CustomStringConvertible { case unknownAction(type: String) public var description: String { @@ -104,29 +108,34 @@ extension MVMCoreActionHandlerProtocol { /// Handle an action with the given model. open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { try Task.checkCancellation() + + let additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) + // Allow the delegate to intercept. guard (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.shouldPerform(action: model, additionalData: additionalData, delegateObject: delegateObject) ?? true else { return } + defer { + MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData) + } + // Log the action logAction(with: model, additionalData: additionalData, delegateObject: delegateObject) - let uuid = UUID() do { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Begin Action \(model.actionType) \(uuid)") + MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData) 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)") + guard try await handleUnregisteredAction(with: model, json: model.toJSON()!, additionalData: additionalData, delegateObject: delegateObject) else { + MVMCoreActionHandler.log(string: "Failed Action Unknown", additionalData: additionalData) throw ActionError.unknownAction(type: model.actionType) } } catch { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Failed Action \(error) \(model.actionType) \(uuid)") + MVMCoreActionHandler.log(string: "Failed Action \(error)", additionalData: additionalData) throw error } - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Successful Action \(model.actionType) \(uuid)") } /// Performs the action as a task and returns immediately. @@ -134,7 +143,8 @@ extension MVMCoreActionHandlerProtocol { let task = Task(priority: .userInitiated) { try Task.checkCancellation() do { - try await handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + let json = try MVMCoreActionHandler.convertActionToJSON(model) + try await handleAction(with: model, json: json, additionalData: additionalData, delegateObject: delegateObject) } catch { try Task.checkCancellation() let errorObject = getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) @@ -164,25 +174,7 @@ extension MVMCoreActionHandlerProtocol { open func logAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { // Calls legacy log action function. 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 { - // 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 + delegateObject?.actionDelegate?.logAction?(withActionInformation: model.toJSON(), additionalData: additionalData) } } @@ -194,43 +186,118 @@ 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) + static public func setUUID(additionalData: [AnyHashable: Any]?) -> [AnyHashable: Any]? { + var additionalData = additionalData ?? [:] + if additionalData.optionalStringForKey("Action-UUID") == nil { + additionalData["Action-UUID"] = UUID().uuidString + } + return additionalData + } + + static public func getUUID(additionalData: [AnyHashable: Any]?) -> String? { + return additionalData?.optionalStringForKey("Action-UUID") + } + + static public func log(string: String, additionalData: [AnyHashable: Any]?) { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: UUID: \(String(describing: getUUID(additionalData: additionalData))), \(string)") } /// 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 + let additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) + let task = Task(priority: .userInitiated) { + try Task.checkCancellation() 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) + try await handleAction(with: model, json: json, additionalData: additionalData, delegateObject: delegateObject) } catch { + let actionType = json?.optionalStringForKey(KeyActionType) 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) + MVMCoreActionHandler.log(string: "Unknown handled (Model not registered): \(String(describing: actionType)) \(String(describing: delegateObject?.actionDelegate))", additionalData: additionalData) 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) + MVMCoreActionHandler.log(string: "Failed Action: Error \(error)", additionalData: additionalData) + let errorObject = getActionErrorObject(for: error, actionType: actionType ?? "noAction", delegateObject: delegateObject) handle(errorObject: errorObject, delegateObject: delegateObject, additionalData: additionalData) } } } + Task { + let result = await task.result + do { + try result.get() + print("ActionHandler: task done") + } catch { + print("ActionHandler: \(error)") + } + } + } + + /// Bridges the legacy json using functions and the new model using functions. + open func handleAction(with model: ActionModelProtocol, json: [AnyHashable: Any], additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { + try Task.checkCancellation() + let additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) + + MVMCoreActionHandler.log(string: "Begin Action: type: \(model.actionType) json: \(String(describing: json))", additionalData: additionalData) + defer { + MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData) + } + + // Allow the delegate to intercept. + guard (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.shouldPerform(action: model, additionalData: additionalData, delegateObject: delegateObject) ?? true else { + MVMCoreActionHandler.log(string: "Action should not be performed: \(model.actionType)", additionalData: additionalData) + return + } + try Task.checkCancellation() + + // Log the action + delegateObject?.actionDelegate?.logAction?(withActionInformation: json, additionalData: additionalData) + + do { + MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData) + let handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type + let handler = handlerType.init() + if let handler = handler as? MVMCoreJSONActionHandlerProtocol { + try await handler.performAction(with: json, model: model, delegateObject: delegateObject, additionalData: additionalData) + } else { + 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, json: json, additionalData: additionalData, delegateObject: delegateObject) else { + MVMCoreActionHandler.log(string: "Failed Action Unknown", additionalData: additionalData) + throw ActionError.unknownAction(type: model.actionType) + } + } catch { + MVMCoreActionHandler.log(string: "Failed Action \(error)", additionalData: additionalData) + throw error + } + } + + /// Subclass to handle and any actions where a handler was not registered. Checks with the delegate handlesUnknownAction function + open func handleUnregisteredAction(with model: ActionModelProtocol, json: [AnyHashable: Any], additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws -> Bool { + // Check if the delegate handles the action. + if try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.handlesUnknownAction(for: model, delegateObject: delegateObject, additionalData: additionalData) == true { + MVMCoreActionHandler.log(string: "Unknown handled (Handler not registered): \(model.actionType) \(String(describing: delegateObject?.actionDelegate))", additionalData: additionalData) + return true + } else if let closure = delegateObject?.actionDelegate?.handleUnknownActionType { + MVMCoreActionHandler.log(string: "Unknown handled (Handler not registered): \(model.actionType) \(String(describing: delegateObject?.actionDelegate))", additionalData: additionalData) + // Check if the legacy delegate handles the action. + closure(model.actionType, json, additionalData) + return true + } else { + return false + } } } diff --git a/MVMCore/MVMCore/Utility/MVMCoreError.swift b/MVMCore/MVMCore/Utility/MVMCoreError.swift new file mode 100644 index 0000000..61c1034 --- /dev/null +++ b/MVMCore/MVMCore/Utility/MVMCoreError.swift @@ -0,0 +1,41 @@ +// +// MVMCoreError.swift +// MVMCore +// +// Created by Scott Pfeil on 7/27/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +public enum MVMCoreError: MVMError, CustomStringConvertible { + case error(code: Int, messageToDisplay: String? = nil, messageToLog: 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 messageToLog): + return messageToLog + case MVMCoreError.errorObject(let object): + return object.messageToLog ?? "Error" + } + } + + public var errorDescription: String? { + switch self { + case MVMCoreError.error(_, let message, _): + return message ?? MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) + case MVMCoreError.errorObject(let object): + return object.messageToDisplay ?? MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) + } + } +} diff --git a/MVMCore/MVMCore/Utility/MVMError.swift b/MVMCore/MVMCore/Utility/MVMError.swift new file mode 100644 index 0000000..ee1662b --- /dev/null +++ b/MVMCore/MVMCore/Utility/MVMError.swift @@ -0,0 +1,18 @@ +// +// MVMError.swift +// MVMCore +// +// Created by Scott Pfeil on 7/27/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +protocol MVMError: LocalizedError, CustomNSError {} +extension MVMError { + public var errorDescription: String? { return MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) } + + public static var errorDomain: String { + return ErrorDomainNative + } +} From 36ec79f353dd95fca81cc7ed75d4c5a1c76aa124 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Fri, 29 Jul 2022 16:20:21 -0400 Subject: [PATCH 06/16] Descriptor changes --- MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift | 2 +- MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift | 3 ++- MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift | 3 ++- MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift | 5 ++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift index 30f2594..aa80a9a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift @@ -21,7 +21,7 @@ open class ActionBackHandler: MVMCoreJSONActionHandlerProtocol { } open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { - await withCheckedContinuation { (continuation: CheckedContinuation) in + await withCheckedContinuation { continuation in Task(priority: .userInitiated) { await MVMCoreNavigationHandler.shared()?.removeCurrentViewController(true, completionHandler: { continuation.resume() diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift index 5f8dbf1..b731613 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift @@ -49,7 +49,8 @@ open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol { } /// Opens the url using UIApplication open(url:). Throws URLError.failedToOpen if it fails. - @MainActor public static func open(url: URL) async throws { + @MainActor + public static func open(url: URL) async throws { try await withCheckedThrowingContinuation { continuation in UIApplication.shared.open(url, options: [:]) { successful in if successful { diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift index 77dbebf..64fff19 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift @@ -24,7 +24,8 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol { try await shareWith(activityItems: shareData, model: model) } - @MainActor open func shareWith(activityItems: [Any], model: ActionShareModel, delegateObject: DelegateObject? = nil) async throws { + @MainActor + open func shareWith(activityItems: [Any], model: ActionShareModel, delegateObject: DelegateObject? = nil) async throws { try await withCheckedThrowingContinuation { continuation in let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) controller.popoverPresentationController?.sourceView = MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn?.view diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift index 9ecd2fd..a5490de 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift @@ -139,6 +139,7 @@ extension MVMCoreActionHandlerProtocol { } /// Performs the action as a task and returns immediately. + @discardableResult open func asyncHandleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> Task { let task = Task(priority: .userInitiated) { try Task.checkCancellation() @@ -173,9 +174,7 @@ 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. - Task { - delegateObject?.actionDelegate?.logAction?(withActionInformation: model.toJSON(), additionalData: additionalData) - } + delegateObject?.actionDelegate?.logAction?(withActionInformation: model.toJSON(), additionalData: additionalData) } /// Logs the error. From dcc81a2c08b4e088a3083f7dc4ef366c6f0f6c6d Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Tue, 2 Aug 2022 11:18:30 -0400 Subject: [PATCH 07/16] Clean the load request --- MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift index cfc55fb..48c626f 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift @@ -12,7 +12,7 @@ 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. TODO: Any way to take errors into account? - guard let operation = MVMCoreLoadHandler.sharedGlobal()?.loadRequest(requestParameters, dataForPage: additionalData, delegateObject: delegateObject) else { return } + let operation = loadRequest(requestParameters, dataForPage: additionalData, delegateObject: delegateObject) await withCheckedContinuation { continuation in operation.completionBlock = { continuation.resume() From bf14282eb568197447752847cf67e3ac3638208a Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Tue, 2 Aug 2022 11:19:56 -0400 Subject: [PATCH 08/16] remove added line --- MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m | 1 - 1 file changed, 1 deletion(-) diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m index 75b134f..842d032 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m @@ -48,7 +48,6 @@ self.registeredLoadQueues = [[NSMutableArray alloc] initWithCapacity:3]; [self registerLoadQueue:self.blockingLoadQueue]; [self registerLoadQueue:self.backgroundLoadQueue]; - } return self; } From 4d72b31a51d248d04f0e2c8f452d013f67ceaf1b Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 8 Aug 2022 17:45:27 -0400 Subject: [PATCH 09/16] Button -> Delegate -> ActionHandler. Components will go through delegate. Delegate will choose how to handle it. --- .../ActionHandling/ActionActionsHandler.swift | 6 +- .../ActionHandling/ActionBackHandler.swift | 4 +- .../ActionHandling/ActionCallHandler.swift | 2 +- .../ActionHandling/ActionCancelHandler.swift | 2 +- .../ActionHandling/ActionContactHandler.swift | 2 +- .../ActionDelegateProtocol.swift | 8 +- .../ActionHandling/ActionNoopHandler.swift | 2 +- .../ActionOpenPageHandler.swift | 2 +- .../ActionHandling/ActionOpenSMSHandler.swift | 2 +- .../ActionHandling/ActionOpenUrlHandler.swift | 4 +- .../ActionPreviousSubmitHandler.swift | 4 +- .../ActionHandling/ActionRestartHandler.swift | 2 +- .../ActionHandling/ActionSettingHandler.swift | 2 +- .../ActionHandling/ActionShareHandler.swift | 2 +- .../ActionHandling/MVMCoreActionHandler.swift | 153 ++++++------------ 15 files changed, 71 insertions(+), 126 deletions(-) diff --git a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift index 59095e2..67a6a9b 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift @@ -30,21 +30,21 @@ open class ActionActionsHandler: MVMCoreJSONActionHandlerProtocol { } } - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionActionsModel else { return } 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) + try await MVMCoreActionHandler.handleActionCheckingDelegate(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) + try await MVMCoreActionHandler.handleActionCheckingDelegate(with: action, additionalData: additionalData, delegateObject: delegateObject) } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift index aa80a9a..6e93fd4 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift @@ -16,11 +16,11 @@ open class ActionBackHandler: MVMCoreJSONActionHandlerProtocol { // Legacy code will use the old handler function and break the task chain here. closure(JSON, additionalData) } else { - try await performAction(model, delegateObject: delegateObject, additionalData: additionalData) + try await execute(with: model, delegateObject: delegateObject, additionalData: additionalData) } } - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { await withCheckedContinuation { continuation in Task(priority: .userInitiated) { await MVMCoreNavigationHandler.shared()?.removeCurrentViewController(true, completionHandler: { diff --git a/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift index 7010746..2d5d530 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift @@ -11,7 +11,7 @@ import Foundation open class ActionCallHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionCallModel else { return } // https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/PhoneLinks/PhoneLinks.html#//apple_ref/doc/uid/TP40007899-CH6-SW1 try await ActionOpenUrlHandler.openURL(with: "tel://\(model.callNumber)") diff --git a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift index 7f5169c..6dbe413 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift @@ -15,6 +15,6 @@ open class ActionCancelHandler: MVMCoreJSONActionHandlerProtocol { delegateObject?.actionDelegate?.handleCancel?(JSON, additionalData: additionalData) } - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift index 7231eb2..5e73ba3 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift @@ -26,7 +26,7 @@ open class ActionContactHandler: NSObject, MVMCoreActionHandlerProtocol, CNConta } } - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) async throws { + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) async throws { guard let model = model as? ActionContactModel else { return } switch model.approach { diff --git a/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift index eeb5734..fd9ad8c 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift @@ -9,8 +9,8 @@ import Foundation public protocol ActionDelegateProtocol: MVMCoreActionDelegateProtocol { - /// Allows the delegate to cancel the action. - func shouldPerform(action: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) -> Bool + /// Asks the delegate to perform the action. + func performAction(with model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) async throws /// Allows the delegate to handle any custom actions that are not registered with the Action Handler. func handlesUnknownAction(for model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> Bool @@ -18,8 +18,8 @@ public protocol ActionDelegateProtocol: MVMCoreActionDelegateProtocol { public extension ActionDelegateProtocol { - func shouldPerform(action: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) -> Bool { - return true + func performAction(with model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) async throws { + try await MVMCoreActionHandler.shared()?.handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) } func handlesUnknownAction(for model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> Bool { diff --git a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift index 30e5780..c46afcd 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift @@ -11,5 +11,5 @@ import Foundation open class ActionNoopHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {} + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {} } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift index f4f2e98..8c747c9 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -49,7 +49,7 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { } } - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenPageModel else { return } do { let json = try MVMCoreActionHandler.convertActionToJSON(model) diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift index 904e712..f917fb5 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift @@ -24,7 +24,7 @@ extension String { open class ActionOpenSMSHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenSMSModel else { return } // 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) diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift index b731613..b38de51 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift @@ -63,10 +63,10 @@ open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol { } open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { - try await performAction(model, delegateObject: delegateObject, additionalData: additionalData) + try await execute(with: model, delegateObject: delegateObject, additionalData: additionalData) } - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func execute(with 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. diff --git a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift index 4a56e7a..9fd12d4 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift @@ -17,10 +17,10 @@ open class ActionPreviousSubmitHandler: MVMCoreJSONActionHandlerProtocol { // Conform to MVMCoreJSONActionHandlerProtocol To allow for legacy handleOpenPage delegate open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { json = JSON - try await performAction(model, delegateObject: delegateObject, additionalData: additionalData) + try await execute(with: model, delegateObject: delegateObject, additionalData: additionalData) } - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let loadObject = (delegateObject?.actionDelegate as? MVMCoreViewControllerProtocol)?.loadObject, let previousRequest = loadObject?.requestParameters else { return } diff --git a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift index d2744b6..20e7a90 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift @@ -11,7 +11,7 @@ import Foundation open class ActionRestartHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionRestartModel else { return } let _: Void = try await withCheckedThrowingContinuation { continuation in diff --git a/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift index e1c718d..0e468ce 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift @@ -11,7 +11,7 @@ import Foundation open class ActionSettingHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { try await ActionOpenUrlHandler.openURL(with: await UIApplication.openSettingsURLString) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift index 64fff19..9ae2c94 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift @@ -11,7 +11,7 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol { required public init() {} - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) async throws { + open func execute(with 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 { diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift index a5490de..2dae006 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift @@ -11,10 +11,8 @@ 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 + + func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws } /// Protocol used to bridge legacy, non model based code. Allows us to keep the original json intact and not lose key values during decode/encode. @@ -23,16 +21,6 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws } -extension MVMCoreActionHandlerProtocol { - - 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, CustomStringConvertible { @@ -50,6 +38,8 @@ extension MVMCoreActionHandlerProtocol { } } + private let jsonKey = "MVMCore.JSON" + /// Returns the action handler stored in the MVMCoreObject @objc(sharedActionHandler) public static func shared() -> Self? { @@ -93,11 +83,6 @@ extension MVMCoreActionHandlerProtocol { } } - /// 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) ?? defaultHandleActionError(errorObject, additionalData: additionalData) - } - /// Returns a common description for the error location. @objc public static func getErrorLocation(with delegate: MVMCoreActionDelegateProtocol?, actionType: String) -> String { return "\(String(describing: delegate))_\(actionType)" @@ -105,30 +90,49 @@ extension MVMCoreActionHandlerProtocol { // MARK: - Action Handling + /// Convenience function for letting the actionDelegate handle the action, else the handler handles the action. + public static func handleActionCheckingDelegate(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { + if let closure = (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction { + try await closure(model, additionalData, delegateObject) + } else { + try await MVMCoreActionHandler.shared()?.handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + } + } + + /// Convenience function for letting the actionDelegate handle the action, else the handler handles the action. + @discardableResult + public static func asyncHandleActionCheckingDelegate(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> Task { + return Task(priority: .userInitiated) { + try await handleActionCheckingDelegate(with: model, additionalData: additionalData, delegateObject: delegateObject) + } + } + /// Handle an action with the given model. open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { try Task.checkCancellation() + var additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) + let json = try additionalData?.removeValue(forKey: jsonKey) as? [AnyHashable : Any] ?? MVMCoreActionHandler.convertActionToJSON(model) - let additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) - - // Allow the delegate to intercept. - guard (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.shouldPerform(action: model, additionalData: additionalData, delegateObject: delegateObject) ?? true else { return } + // Log the action + delegateObject?.actionDelegate?.logAction?(withActionInformation: json, additionalData: additionalData) + MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData) defer { MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData) } - - // Log the action - logAction(with: model, additionalData: additionalData, delegateObject: delegateObject) do { - MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData) let handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type let handler = handlerType.init() - try await handler.performAction(model, delegateObject: delegateObject, additionalData: additionalData) + if let handler = handler as? MVMCoreJSONActionHandlerProtocol { + // Needed until we can remove legacy delegate functions. + try await handler.performAction(with: json, model: model, delegateObject: delegateObject, additionalData: additionalData) + } else { + try await handler.execute(with: 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, json: model.toJSON()!, additionalData: additionalData, delegateObject: delegateObject) else { + guard try await handleUnregisteredAction(with: model, json: json, additionalData: additionalData, delegateObject: delegateObject) else { MVMCoreActionHandler.log(string: "Failed Action Unknown", additionalData: additionalData) throw ActionError.unknownAction(type: model.actionType) } @@ -138,37 +142,6 @@ extension MVMCoreActionHandlerProtocol { } } - /// Performs the action as a task and returns immediately. - @discardableResult - open func asyncHandleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> Task { - let task = Task(priority: .userInitiated) { - try Task.checkCancellation() - do { - let json = try MVMCoreActionHandler.convertActionToJSON(model) - try await handleAction(with: model, json: json, 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. @@ -177,6 +150,11 @@ extension MVMCoreActionHandlerProtocol { delegateObject?.actionDelegate?.logAction?(withActionInformation: model.toJSON(), additionalData: additionalData) } + open func defaultHandle(error: Error, model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { + let errorObject = getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) + defaultHandleActionError(errorObject, additionalData: additionalData) + } + /// Logs the error. @objc open func defaultHandleActionError(_ error: MVMCoreErrorObject, additionalData: [AnyHashable: Any]?) { guard error.logError else { return } @@ -186,11 +164,8 @@ extension MVMCoreActionHandlerProtocol { // MARK: - Legacy Holdovers static public func setUUID(additionalData: [AnyHashable: Any]?) -> [AnyHashable: Any]? { - var additionalData = additionalData ?? [:] - if additionalData.optionalStringForKey("Action-UUID") == nil { - additionalData["Action-UUID"] = UUID().uuidString - } - return additionalData + guard getUUID(additionalData: additionalData) == nil else { return additionalData } + return additionalData.dictionaryAdding(key: "Action-UUID", value: UUID().uuidString) } static public func getUUID(additionalData: [AnyHashable: Any]?) -> String? { @@ -212,7 +187,12 @@ extension MVMCoreActionHandlerProtocol { throw ModelRegistry.Error.keyNotFound } let model = try MVMCoreActionHandler.createModel(with: json, delegateObject: delegateObject) - try await handleAction(with: model, json: json, additionalData: additionalData, delegateObject: delegateObject) + if let closure = (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction { + // Allow newer delegates to handle calls from legacy functions + try await closure(model, additionalData, delegateObject) + } else { + try await handleAction(with: model, json: json, additionalData: additionalData, delegateObject: delegateObject) + } } catch { let actionType = json?.optionalStringForKey(KeyActionType) switch error { @@ -227,7 +207,7 @@ extension MVMCoreActionHandlerProtocol { default: MVMCoreActionHandler.log(string: "Failed Action: Error \(error)", additionalData: additionalData) let errorObject = getActionErrorObject(for: error, actionType: actionType ?? "noAction", delegateObject: delegateObject) - handle(errorObject: errorObject, delegateObject: delegateObject, additionalData: additionalData) + delegateObject?.actionDelegate?.handleActionError?(errorObject, additionalData: additionalData) ?? defaultHandleActionError(errorObject, additionalData: additionalData) } } } @@ -245,43 +225,8 @@ extension MVMCoreActionHandlerProtocol { /// Bridges the legacy json using functions and the new model using functions. open func handleAction(with model: ActionModelProtocol, json: [AnyHashable: Any], additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { try Task.checkCancellation() - let additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) - - MVMCoreActionHandler.log(string: "Begin Action: type: \(model.actionType) json: \(String(describing: json))", additionalData: additionalData) - defer { - MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData) - } - - // Allow the delegate to intercept. - guard (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.shouldPerform(action: model, additionalData: additionalData, delegateObject: delegateObject) ?? true else { - MVMCoreActionHandler.log(string: "Action should not be performed: \(model.actionType)", additionalData: additionalData) - return - } - try Task.checkCancellation() - - // Log the action - delegateObject?.actionDelegate?.logAction?(withActionInformation: json, additionalData: additionalData) - - do { - MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData) - let handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type - let handler = handlerType.init() - if let handler = handler as? MVMCoreJSONActionHandlerProtocol { - try await handler.performAction(with: json, model: model, delegateObject: delegateObject, additionalData: additionalData) - } else { - 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, json: json, additionalData: additionalData, delegateObject: delegateObject) else { - MVMCoreActionHandler.log(string: "Failed Action Unknown", additionalData: additionalData) - throw ActionError.unknownAction(type: model.actionType) - } - } catch { - MVMCoreActionHandler.log(string: "Failed Action \(error)", additionalData: additionalData) - throw error - } + let additionalData = additionalData.dictionaryAdding(key: jsonKey, value: json) + try await handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) } /// Subclass to handle and any actions where a handler was not registered. Checks with the delegate handlesUnknownAction function From e6164026f6eb2298eb04d93227cedce53589e688 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Wed, 10 Aug 2022 22:19:00 -0400 Subject: [PATCH 10/16] Drive through delegate up front. Remove some legacy delegate back and forth --- .../ActionHandling/ActionActionsHandler.swift | 4 +- .../ActionHandling/ActionContactHandler.swift | 4 +- .../ActionDelegateProtocol.swift | 7 -- .../ActionOpenPageHandler.swift | 34 ++------- .../ActionHandling/ActionOpenPageModel.swift | 76 ++++++++++++++++++- .../ActionHandling/ActionOpenUrlHandler.swift | 2 +- .../ActionHandling/ActionShareHandler.swift | 2 +- .../MVMCoreActionDelegateProtocol.h | 3 - .../ActionHandling/MVMCoreActionHandler.swift | 65 ++++------------ MVMCore/MVMCore/Utility/MVMCoreError.swift | 11 +++ MVMCore/MVMCore/Utility/MVMCoreErrorObject.m | 8 +- 11 files changed, 117 insertions(+), 99 deletions(-) diff --git a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift index 67a6a9b..6a0d34c 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift @@ -37,14 +37,14 @@ open class ActionActionsHandler: MVMCoreJSONActionHandlerProtocol { await withThrowingTaskGroup(of: Void.self) { group in for action in model.actions { group.addTask{ - try await MVMCoreActionHandler.handleActionCheckingDelegate(with: action, additionalData: additionalData, delegateObject: delegateObject) + try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction(with: action, additionalData: additionalData, delegateObject: delegateObject) } } } } else { for action in model.actions { try Task.checkCancellation() - try await MVMCoreActionHandler.handleActionCheckingDelegate(with: action, additionalData: additionalData, delegateObject: delegateObject) + try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction(with: action, additionalData: additionalData, delegateObject: delegateObject) } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift index 5e73ba3..513bd2d 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift @@ -106,11 +106,11 @@ open class ActionContactHandler: NSObject, MVMCoreActionHandlerProtocol, CNConta var phoneNumbers = [labelValue] phoneNumbers.append(contentsOf: existingContact.phoneNumbers) existingContact.phoneNumbers = phoneNumbers - MVMCoreDispatchUtility.performBlock(onMainThread: { + Task { @MainActor in let controller = CNContactViewController(forNewContact: existingContact) controller.contactStore = store controller.delegate = self MVMCoreNavigationHandler.shared()?.push(controller, animated: true) - }) + } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift index fd9ad8c..6788d08 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift @@ -11,9 +11,6 @@ import Foundation public protocol ActionDelegateProtocol: MVMCoreActionDelegateProtocol { /// Asks the delegate to perform the action. func performAction(with model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) async throws - - /// Allows the delegate to handle any custom actions that are not registered with the Action Handler. - func handlesUnknownAction(for model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> Bool } public extension ActionDelegateProtocol { @@ -21,8 +18,4 @@ public extension ActionDelegateProtocol { func performAction(with model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) async throws { try await MVMCoreActionHandler.shared()?.handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) } - - func handlesUnknownAction(for model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> Bool { - return false - } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift index 8c747c9..7011008 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -8,41 +8,17 @@ import Foundation -public protocol ActionOpenPageDelegateProtocol { - /// Allows the delegate to create the request parameters as desired. - func getRequestParameters(for model: ActionOpenPageModel, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) throws -> (MVMCoreRequestParameters,[AnyHashable : Any]?) -} - open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} - + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenPageModel else { return } - if model.background != true { - MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.startLoading() - } - defer { - if model.background != true { - MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) - } - } - do { - // Allows the delegate a chance to create and modify request parameters. - var requestParameters: MVMCoreRequestParameters - var additionalData = additionalData - if let value = try (delegateObject?.actionDelegate as? ActionOpenPageDelegateProtocol)?.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) + closure(model.requestParameters, JSON, additionalData) } else { - try await performRequestAddingClientParameters(with: requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) + try await performRequestAddingClientParameters(with: model.requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) } } catch { try handle(error: error, model: model, delegateObject: delegateObject) @@ -52,6 +28,7 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenPageModel else { return } do { + // Pass through the old function for legacy open page. let json = try MVMCoreActionHandler.convertActionToJSON(model) try await performAction(with: json, model: model, delegateObject: delegateObject, additionalData: additionalData) } catch { @@ -66,8 +43,7 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { 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 Task.checkCancellation() try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: requestParameters, delegateObject: delegateObject, additionalData: additionalData) } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift index 9a17245..c6a2b9a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift @@ -6,6 +6,7 @@ // Copyright © 2019 Suresh, Kamlesh. All rights reserved. // +import Foundation open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, ClientParameterActionProtocol { //-------------------------------------------------- @@ -16,15 +17,18 @@ open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, Cli open var actionType: String = identifier open var pageType: String open var modules: [String]? - open var baseURL: String? + open var baseURL: URL? open var appContext: String? - open var requestURL: String? + open var requestURL: URL? open var extraParameters: JSONValueDictionary? open var analyticsData: JSONValueDictionary? open var presentationStyle: String? open var tabBarIndex: Int? open var background: Bool? open var clientParameters: ClientParameterModel? + open var customTimeoutTime: TimeInterval? + + open var requestParameters: MVMCoreRequestParameters //-------------------------------------------------- // MARK: - Initialzier @@ -37,5 +41,73 @@ open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, Cli self.analyticsData = analyticsData self.tabBarIndex = tabBarIndex self.background = background + + requestParameters = MVMCoreRequestParameters(pageType: pageType, extraParameters: extraParameters.toJSON())! + if let background = background { + requestParameters.backgroundRequest = background + } + requestParameters.actionMap = toJSON() + } + + //-------------------------------------------------- + // MARK: - Codable + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case actionType + case pageType + case modules + case baseURL + case appContext + case requestURL + case extraParameters + case analyticsData + case presentationStyle + case tabBarIndex + case background + case clientParameters + case customTimeoutTime + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + pageType = try typeContainer.decode(String.self, forKey: .pageType) + baseURL = try typeContainer.decodeIfPresent(URL.self, forKey: .baseURL) + appContext = try typeContainer.decodeIfPresent(String.self, forKey: .appContext) + requestURL = try typeContainer.decodeIfPresent(URL.self, forKey: .requestURL) + modules = try typeContainer.decodeIfPresent([String].self, forKey: .modules) + presentationStyle = try typeContainer.decodeIfPresent(String.self, forKey: .presentationStyle) + tabBarIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .tabBarIndex) + background = try typeContainer.decodeIfPresent(Bool.self, forKey: .background) + clientParameters = try typeContainer.decodeIfPresent(ClientParameterModel.self, forKey: .clientParameters) + customTimeoutTime = try typeContainer.decodeIfPresent(TimeInterval.self, forKey: .customTimeoutTime) + extraParameters = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .extraParameters) + analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData) + + requestParameters = MVMCoreRequestParameters(pageType: pageType, additionalModules: modules ?? [], extraParameters: extraParameters.toJSON())! + requestParameters.contextRoot = appContext + requestParameters.alternateBaseURL = baseURL + requestParameters.url = requestURL + requestParameters.customTimeoutTime = customTimeoutTime as NSNumber? + if let background = background { + requestParameters.backgroundRequest = background + } + requestParameters.actionMap = toJSON() + } + + open func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(pageType, forKey: .pageType) + try container.encodeIfPresent(baseURL, forKey: .baseURL) + try container.encodeIfPresent(appContext, forKey: .appContext) + try container.encodeIfPresent(requestURL, forKey: .requestURL) + try container.encodeIfPresent(modules, forKey: .modules) + try container.encodeIfPresent(presentationStyle, forKey: .presentationStyle) + try container.encodeIfPresent(tabBarIndex, forKey: .tabBarIndex) + try container.encodeIfPresent(background, forKey: .background) + try container.encodeIfPresent(clientParameters, forKey: .clientParameters) + try container.encodeIfPresent(customTimeoutTime, forKey: .customTimeoutTime) + try container.encodeIfPresent(extraParameters, forKey: .extraParameters) + try container.encodeIfPresent(analyticsData, forKey: .analyticsData) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift index b38de51..4961fa3 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift @@ -77,7 +77,7 @@ open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol { } catch { // Log error and continue MVMCoreLoggingHandler.shared()?.handleDebugMessage("Failed to open app url: \(appURL)") - if let errorObject = MVMCoreActionHandler.shared()?.getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) { + if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) { MVMCoreLoggingHandler.addError(toLog: errorObject) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift index 9ae2c94..3f8cb11 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift @@ -40,7 +40,7 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol { } 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 = MVMCoreActionHandler.shared()?.getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) { + let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) { MVMCoreLoggingHandler.addError(toLog: errorObject) } } else if let error = error { diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h index e29a184..53cde7f 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h @@ -33,9 +33,6 @@ // Handles any unknown action types. Can overwrite for more specific handling. - (void)handleUnknownActionType:(nullable NSString *)actionType actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData; -// Handles any action errors. Can overwrite for more specific handling. -- (void)handleActionError:(nonnull MVMCoreErrorObject *)error additionalData:(nullable NSDictionary *)additionalData; - // Lets the delegate know that another internal module app is about to be launched - (void)prepareForOpenOtherAppModule:(nullable NSString *)module; diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift index 2dae006..a64d4f6 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift @@ -15,7 +15,7 @@ public protocol MVMCoreActionHandlerProtocol: ModelHandlerProtocol { func execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws } -/// Protocol used to bridge legacy, non model based code. Allows us to keep the original json intact and not lose key values during decode/encode. +/// Protocol used to bridge legacy, non model based code. Allows us to keep the original json intact and not lose key values during decode/encode. Should not be used for new actions. public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { /// Perform the function using the original json and model. func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws @@ -38,6 +38,7 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { } } + /// Used to temporarily store the json in additionalData. private let jsonKey = "MVMCore.JSON" /// Returns the action handler stored in the MVMCoreObject @@ -73,16 +74,6 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { // MARK: - Error Handling - /// 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))! - } - } - /// Returns a common description for the error location. @objc public static func getErrorLocation(with delegate: MVMCoreActionDelegateProtocol?, actionType: String) -> String { return "\(String(describing: delegate))_\(actionType)" @@ -90,36 +81,19 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { // MARK: - Action Handling - /// Convenience function for letting the actionDelegate handle the action, else the handler handles the action. - public static func handleActionCheckingDelegate(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { - if let closure = (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction { - try await closure(model, additionalData, delegateObject) - } else { - try await MVMCoreActionHandler.shared()?.handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) - } - } - - /// Convenience function for letting the actionDelegate handle the action, else the handler handles the action. - @discardableResult - public static func asyncHandleActionCheckingDelegate(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> Task { - return Task(priority: .userInitiated) { - try await handleActionCheckingDelegate(with: model, additionalData: additionalData, delegateObject: delegateObject) - } - } - /// Handle an action with the given model. open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { try Task.checkCancellation() var additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) + MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData) + defer { + MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData) + } let json = try additionalData?.removeValue(forKey: jsonKey) as? [AnyHashable : Any] ?? MVMCoreActionHandler.convertActionToJSON(model) // Log the action delegateObject?.actionDelegate?.logAction?(withActionInformation: json, additionalData: additionalData) - MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData) - defer { - MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData) - } do { let handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type let handler = handlerType.init() @@ -150,11 +124,6 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { delegateObject?.actionDelegate?.logAction?(withActionInformation: model.toJSON(), additionalData: additionalData) } - open func defaultHandle(error: Error, model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { - let errorObject = getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) - defaultHandleActionError(errorObject, additionalData: additionalData) - } - /// Logs the error. @objc open func defaultHandleActionError(_ error: MVMCoreErrorObject, additionalData: [AnyHashable: Any]?) { guard error.logError else { return } @@ -198,16 +167,14 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { switch error { case ModelRegistry.Error.decoderErrorModelNotMapped: // If the model is not mapped, give the legacy classes a chance to handle it. - if let closure = delegateObject?.actionDelegate?.handleUnknownActionType { - MVMCoreActionHandler.log(string: "Unknown handled (Model not registered): \(String(describing: actionType)) \(String(describing: delegateObject?.actionDelegate))", additionalData: additionalData) - closure(actionType, json, additionalData) - } else { + if try await handleUnregisteredAction(with: nil, json: json!, additionalData: additionalData, delegateObject: delegateObject) == false { fallthrough } default: MVMCoreActionHandler.log(string: "Failed Action: Error \(error)", additionalData: additionalData) - let errorObject = getActionErrorObject(for: error, actionType: actionType ?? "noAction", delegateObject: delegateObject) - delegateObject?.actionDelegate?.handleActionError?(errorObject, additionalData: additionalData) ?? defaultHandleActionError(errorObject, additionalData: additionalData) + if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: actionType ?? "noAction")) { + defaultHandleActionError(errorObject, additionalData: additionalData) + } } } } @@ -230,15 +197,13 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { } /// Subclass to handle and any actions where a handler was not registered. Checks with the delegate handlesUnknownAction function - open func handleUnregisteredAction(with model: ActionModelProtocol, json: [AnyHashable: Any], additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws -> Bool { + open func handleUnregisteredAction(with model: ActionModelProtocol?, json: [AnyHashable: Any], additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws -> Bool { // Check if the delegate handles the action. - if try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.handlesUnknownAction(for: model, delegateObject: delegateObject, additionalData: additionalData) == true { - MVMCoreActionHandler.log(string: "Unknown handled (Handler not registered): \(model.actionType) \(String(describing: delegateObject?.actionDelegate))", additionalData: additionalData) - return true - } else if let closure = delegateObject?.actionDelegate?.handleUnknownActionType { - MVMCoreActionHandler.log(string: "Unknown handled (Handler not registered): \(model.actionType) \(String(describing: delegateObject?.actionDelegate))", additionalData: additionalData) + let actionType = json.optionalStringForKey(KeyActionType) + if let closure = delegateObject?.actionDelegate?.handleUnknownActionType { + MVMCoreActionHandler.log(string: "Unknown handled (Handler not registered): \(String(describing: actionType)) \(String(describing: delegateObject?.actionDelegate))", additionalData: additionalData) // Check if the legacy delegate handles the action. - closure(model.actionType, json, additionalData) + closure(actionType, json, additionalData) return true } else { return false diff --git a/MVMCore/MVMCore/Utility/MVMCoreError.swift b/MVMCore/MVMCore/Utility/MVMCoreError.swift index 61c1034..31fe2f8 100644 --- a/MVMCore/MVMCore/Utility/MVMCoreError.swift +++ b/MVMCore/MVMCore/Utility/MVMCoreError.swift @@ -39,3 +39,14 @@ public enum MVMCoreError: MVMError, CustomStringConvertible { } } } + +@objc public extension NSError { + @objc func checkForMVMCoreError() -> MVMCoreErrorObject? { + switch self as Error { + case MVMCoreError.errorObject(let object): + return object + default: + return nil + } + } +} diff --git a/MVMCore/MVMCore/Utility/MVMCoreErrorObject.m b/MVMCore/MVMCore/Utility/MVMCoreErrorObject.m index d61075b..09f76b4 100644 --- a/MVMCore/MVMCore/Utility/MVMCoreErrorObject.m +++ b/MVMCore/MVMCore/Utility/MVMCoreErrorObject.m @@ -14,6 +14,7 @@ #import "MVMCoreJSONConstants.h" #import "MVMCoreHardcodedStringsConstants.h" #import "MVMCoreDispatchUtility.h" +#import @implementation MVMCoreErrorObject @@ -106,8 +107,11 @@ } + (nullable instancetype)createErrorObjectForNSError:(nonnull NSError *)error location:(nullable NSString *)location { - - MVMCoreErrorObject *errorObject = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[error localizedDescription] messageToLog:[error description] code:[error code] domain:ErrorDomainSystem location:location]; + MVMCoreErrorObject *errorObject = [error checkForMVMCoreError]; + if (errorObject) { + return errorObject; + } + errorObject = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[error localizedDescription] messageToLog:[error description] code:[error code] domain:ErrorDomainSystem location:location]; if ([error.domain isEqualToString:NSURLErrorDomain]) { errorObject.systemDomain = error.domain; if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(getNativeScreenForRequestError:requestObject:)]) { From 0196d5c190d1db356142a41e32120e5025132f78 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 11 Aug 2022 22:34:35 -0400 Subject: [PATCH 11/16] bug fixes --- .../ActionHandling/ActionOpenUrlHandler.swift | 2 +- .../ActionHandling/MVMCoreActionHandler.swift | 31 ++++++++++--------- MVMCore/MVMCore/Utility/MVMError.swift | 2 +- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift index 4961fa3..5ed9d2d 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift @@ -72,7 +72,7 @@ open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol { // Try loading the app url first, otherwise fall back to browser url. if let appURL = model.appURL { do { - try await openURL(model: model, additionalData: additionalData, delegateObject: delegateObject) + try await ActionOpenUrlHandler.open(url: appURL) return } catch { // Log error and continue diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift index a64d4f6..223ce20 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift @@ -89,10 +89,10 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { defer { MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData) } - let json = try additionalData?.removeValue(forKey: jsonKey) as? [AnyHashable : Any] ?? MVMCoreActionHandler.convertActionToJSON(model) + let json = try additionalData.removeValue(forKey: jsonKey) as? [AnyHashable : Any] ?? MVMCoreActionHandler.convertActionToJSON(model) // Log the action - delegateObject?.actionDelegate?.logAction?(withActionInformation: json, additionalData: additionalData) + logAction(with: json, additionalData: additionalData, delegateObject: delegateObject) do { let handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type @@ -119,9 +119,9 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { // MARK: - Subclassables /// Subclass to log the action was fired. - open func logAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + open func logAction(with JSON: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { // Calls legacy log action function. - delegateObject?.actionDelegate?.logAction?(withActionInformation: model.toJSON(), additionalData: additionalData) + delegateObject?.actionDelegate?.logAction?(withActionInformation: JSON, additionalData: additionalData) } /// Logs the error. @@ -132,8 +132,8 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { // MARK: - Legacy Holdovers - static public func setUUID(additionalData: [AnyHashable: Any]?) -> [AnyHashable: Any]? { - guard getUUID(additionalData: additionalData) == nil else { return additionalData } + static public func setUUID(additionalData: [AnyHashable: Any]?, force: Bool = false) -> [AnyHashable: Any] { + if !force && getUUID(additionalData: additionalData) != nil { return additionalData! } return additionalData.dictionaryAdding(key: "Action-UUID", value: UUID().uuidString) } @@ -148,7 +148,6 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { /// Legacy handle action with json. @objc(handleActionWithDictionary:additionalData:delegateObject:) open func handleAction(with json: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - let additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) let task = Task(priority: .userInitiated) { try Task.checkCancellation() do { @@ -156,12 +155,7 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { throw ModelRegistry.Error.keyNotFound } let model = try MVMCoreActionHandler.createModel(with: json, delegateObject: delegateObject) - if let closure = (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction { - // Allow newer delegates to handle calls from legacy functions - try await closure(model, additionalData, delegateObject) - } else { - try await handleAction(with: model, json: json, additionalData: additionalData, delegateObject: delegateObject) - } + try await handleAction(with: model, json: json, additionalData: additionalData, delegateObject: delegateObject) } catch { let actionType = json?.optionalStringForKey(KeyActionType) switch error { @@ -192,8 +186,15 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { /// Bridges the legacy json using functions and the new model using functions. open func handleAction(with model: ActionModelProtocol, json: [AnyHashable: Any], additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { try Task.checkCancellation() - let additionalData = additionalData.dictionaryAdding(key: jsonKey, value: json) - try await handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + var additionalData = additionalData.dictionaryAdding(key: jsonKey, value: json) + additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData, force: true) + MVMCoreActionHandler.log(string: "JSON \(json)", additionalData: additionalData) + if let closure = (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction { + // Allow newer delegates to handle calls from legacy functions + try await closure(model, additionalData, delegateObject) + } else { + try await handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + } } /// Subclass to handle and any actions where a handler was not registered. Checks with the delegate handlesUnknownAction function diff --git a/MVMCore/MVMCore/Utility/MVMError.swift b/MVMCore/MVMCore/Utility/MVMError.swift index ee1662b..e048840 100644 --- a/MVMCore/MVMCore/Utility/MVMError.swift +++ b/MVMCore/MVMCore/Utility/MVMError.swift @@ -8,7 +8,7 @@ import Foundation -protocol MVMError: LocalizedError, CustomNSError {} +public protocol MVMError: LocalizedError, CustomNSError {} extension MVMError { public var errorDescription: String? { return MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) } From 5c53466dac1321b14ed05f3221fa8b756ea6eb1d Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Fri, 12 Aug 2022 16:17:00 -0400 Subject: [PATCH 12/16] Centralize SoureModel --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 4 ---- .../ActionOpenPageHandler.swift | 12 ++++++---- .../ActionPreviousSubmitHandler.swift | 7 +++++- .../MVMCoreLoadHandler+Extension.swift | 22 ------------------- 4 files changed, 14 insertions(+), 31 deletions(-) delete mode 100644 MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index d14b68c..5bfb648 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -105,7 +105,6 @@ 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, ); }; }; @@ -268,7 +267,6 @@ AF706999287DD02400077CF6 /* ActionContactHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionContactHandler.swift; sourceTree = ""; }; AF70699D2880D01400077CF6 /* ActionShareHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionShareHandler.swift; sourceTree = ""; }; AF70699F2880F0EB00077CF6 /* ActionOpenPageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenPageHandler.swift; sourceTree = ""; }; - AF7069A12882293900077CF6 /* MVMCoreLoadHandler+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreLoadHandler+Extension.swift"; sourceTree = ""; }; AF787412286DEF8B00670588 /* ActionBackHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBackHandler.swift; sourceTree = ""; }; AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenUrlHandler.swift; sourceTree = ""; }; AFBB96321FBA34310008D868 /* MVMCoreErrorConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreErrorConstants.h; sourceTree = ""; }; @@ -577,7 +575,6 @@ AFBB96371FBA39E70008D868 /* MVMCoreLoadDelegateProtocol.h */, AFBB96391FBA3A550008D868 /* MVMCoreLoadHandler.h */, AFBB964B1FBA3A560008D868 /* MVMCoreLoadHandler.m */, - AF7069A12882293900077CF6 /* MVMCoreLoadHandler+Extension.swift */, AFBB964A1FBA3A560008D868 /* MVMCoreLoadRequestOperation.h */, AFBB96521FBA3A570008D868 /* MVMCoreLoadRequestOperation.m */, AFBB96471FBA3A560008D868 /* MVMCoreLoadObject.h */, @@ -913,7 +910,6 @@ 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 */, diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift index 7011008..7bdc502 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -17,8 +17,12 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { if let closure = delegateObject?.actionDelegate?.handleOpenPage { // Legacy code will use the old handler function and break the task chain here. closure(model.requestParameters, JSON, additionalData) - } else { - try await performRequestAddingClientParameters(with: model.requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) + } else if let operation = try await performRequestAddingClientParameters(with: model.requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) { + await withCheckedContinuation { continuation in + operation.completionBlock = { + continuation.resume() + } + } } } catch { try handle(error: error, model: model, delegateObject: delegateObject) @@ -37,14 +41,14 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { } /// Adds client parameters and makes calls performRequest() - open func performRequestAddingClientParameters(with requestParameters: MVMCoreRequestParameters, model: ActionOpenPageModel, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func performRequestAddingClientParameters(with requestParameters: MVMCoreRequestParameters, model: ActionOpenPageModel, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> MVMCoreLoadRequestOperation? { // Adds any client parameters to the request parameters. if let parametersToFetch = model.clientParameters, let fetchedParameters = try await ClientParameterHandler().getClientParameters(with: parametersToFetch, requestParameters: requestParameters.parameters as? [String : Any] ?? [:], showLoadingOverlay: !requestParameters.backgroundRequest) { requestParameters.add(fetchedParameters) } try Task.checkCancellation() - try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: requestParameters, delegateObject: delegateObject, additionalData: additionalData) + return MVMCoreLoadHandler.sharedGlobal()?.loadRequest(requestParameters, dataForPage: additionalData, delegateObject: delegateObject) } /// Ensures background requests do not have showing errors. diff --git a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift index 9fd12d4..a3bded2 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift @@ -36,7 +36,12 @@ open class ActionPreviousSubmitHandler: MVMCoreJSONActionHandlerProtocol { let json = try json ?? MVMCoreActionHandler.convertActionToJSON(model) delegateObject?.actionDelegate?.handleOpenPage?(for: previousRequest, actionInformation: json, additionalData: additionalData) } else { - try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: previousRequest, delegateObject: delegateObject, additionalData: additionalData) + guard let operation = MVMCoreLoadHandler.sharedGlobal()?.loadRequest(previousRequest, dataForPage: additionalData, delegateObject: delegateObject) else { return } + await withCheckedContinuation { continuation in + operation.completionBlock = { + continuation.resume() + } + } } } } diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift deleted file mode 100644 index 48c626f..0000000 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler+Extension.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// 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. TODO: Any way to take errors into account? - let operation = loadRequest(requestParameters, dataForPage: additionalData, delegateObject: delegateObject) - await withCheckedContinuation { continuation in - operation.completionBlock = { - continuation.resume() - } - } - } -} From 17b2a4368f2a0951d97de3ff2618631ada208d69 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 15 Aug 2022 10:33:15 -0400 Subject: [PATCH 13/16] cleanup --- .../ActionHandling/ActionActionsModel.swift | 4 +-- .../ActionHandling/ActionOpenPageModel.swift | 34 +++++++++---------- .../ActionHandling/MVMCoreActionHandler.swift | 11 +----- 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift index 33444c0..c7e00fc 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift @@ -8,7 +8,7 @@ import Foundation -open class ActionActionsModel: ActionModelProtocol { +public struct ActionActionsModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -46,7 +46,7 @@ open class ActionActionsModel: ActionModelProtocol { // MARK: - Codec //-------------------------------------------------- - required public init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) actions = try typeContainer.decodeModels(codingKey: .actions) if let concurrent = try typeContainer.decodeIfPresent(Bool.self, forKey: .concurrent) { diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift index c6a2b9a..c51b3fd 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift @@ -8,27 +8,27 @@ import Foundation -open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, ClientParameterActionProtocol { +public struct ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, ClientParameterActionProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - open class var identifier: String { "openPage" } - open var actionType: String = identifier - open var pageType: String - open var modules: [String]? - open var baseURL: URL? - open var appContext: String? - open var requestURL: URL? - open var extraParameters: JSONValueDictionary? - open var analyticsData: JSONValueDictionary? - open var presentationStyle: String? - open var tabBarIndex: Int? - open var background: Bool? - open var clientParameters: ClientParameterModel? - open var customTimeoutTime: TimeInterval? + public static var identifier: String = "openPage" + public var actionType: String = identifier + public var pageType: String + public var modules: [String]? + public var baseURL: URL? + public var appContext: String? + public var requestURL: URL? + public var extraParameters: JSONValueDictionary? + public var analyticsData: JSONValueDictionary? + public var presentationStyle: String? + public var tabBarIndex: Int? + public var background: Bool? + public var clientParameters: ClientParameterModel? + public var customTimeoutTime: TimeInterval? - open var requestParameters: MVMCoreRequestParameters + public var requestParameters: MVMCoreRequestParameters //-------------------------------------------------- // MARK: - Initialzier @@ -95,7 +95,7 @@ open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, Cli requestParameters.actionMap = toJSON() } - open func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(pageType, forKey: .pageType) try container.encodeIfPresent(baseURL, forKey: .baseURL) diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift index 223ce20..398a590 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift @@ -148,7 +148,7 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { /// Legacy handle action with json. @objc(handleActionWithDictionary:additionalData:delegateObject:) open func handleAction(with json: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - let task = Task(priority: .userInitiated) { + _ = Task(priority: .userInitiated) { try Task.checkCancellation() do { guard let json = json else { @@ -172,15 +172,6 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { } } } - Task { - let result = await task.result - do { - try result.get() - print("ActionHandler: task done") - } catch { - print("ActionHandler: \(error)") - } - } } /// Bridges the legacy json using functions and the new model using functions. From 27c239cab2ad6566508d7f032dc1235c69ea4bb9 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 15 Aug 2022 10:45:34 -0400 Subject: [PATCH 14/16] remove duplicate declaration --- MVMCore/MVMCore/Models/ModelMapping.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/MVMCore/MVMCore/Models/ModelMapping.swift b/MVMCore/MVMCore/Models/ModelMapping.swift index f587849..a7f546c 100644 --- a/MVMCore/MVMCore/Models/ModelMapping.swift +++ b/MVMCore/MVMCore/Models/ModelMapping.swift @@ -15,7 +15,6 @@ 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(handler: ActionShareHandler.self, for: ActionShareModel.self) ModelRegistry.register(handler: ActionRestartHandler.self, for: ActionRestartModel.self) From c4f8bd955b4fb9dfdb32ea43d813f7ed663265ca Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 15 Aug 2022 15:30:51 -0400 Subject: [PATCH 15/16] Version Bump --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index 5bfb648..bdd9165 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -1146,7 +1146,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; - MARKETING_VERSION = 3.0; + MARKETING_VERSION = 3.1; PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCore; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1174,7 +1174,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; - MARKETING_VERSION = 3.0; + MARKETING_VERSION = 3.1; PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCore; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; From 3739812379a93eb936ca2384d7e04fa838f67ba9 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 15 Aug 2022 17:46:02 -0400 Subject: [PATCH 16/16] class to struct --- MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift index c51b3fd..611b7b7 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift @@ -69,7 +69,7 @@ public struct ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, case customTimeoutTime } - required public init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) pageType = try typeContainer.decode(String.self, forKey: .pageType) baseURL = try typeContainer.decodeIfPresent(URL.self, forKey: .baseURL)