diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index 85210c7..bdd9165 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -92,6 +92,20 @@ 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 */; }; + 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 */; }; + 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 */; }; + 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 */; }; @@ -124,8 +138,6 @@ AFBB96B21FBA3B590008D868 /* MVMCoreGetterUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBB96AE1FBA3B590008D868 /* MVMCoreGetterUtility.h */; settings = {ATTRIBUTES = (Public, ); }; }; AFBB96B31FBA3B590008D868 /* MVMCoreGetterUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = AFBB96AF1FBA3B590008D868 /* MVMCoreGetterUtility.m */; }; AFBB96B81FBA3CEC0008D868 /* MVMCoreActionDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBB96B51FBA3CEC0008D868 /* MVMCoreActionDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AFBB96B91FBA3CEC0008D868 /* MVMCoreActionHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBB96B61FBA3CEC0008D868 /* MVMCoreActionHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AFBB96BA1FBA3CEC0008D868 /* MVMCoreActionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = AFBB96B71FBA3CEC0008D868 /* MVMCoreActionHandler.m */; }; AFBB96EC1FBA4A260008D868 /* MFHardCodedServerResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBB96E91FBA4A260008D868 /* MFHardCodedServerResponse.h */; }; AFBB96ED1FBA4A260008D868 /* MFHardCodedServerResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = AFBB96EA1FBA4A260008D868 /* MFHardCodedServerResponse.m */; }; AFEA17A8209B6A1C00BC6740 /* MVMCoreBlockOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = AFEA17A6209B6A1C00BC6740 /* MVMCoreBlockOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -149,8 +161,8 @@ D268D82B26700292008BD413 /* MVMCoreViewManagerViewControllerProtocolHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = D268D82926700292008BD413 /* MVMCoreViewManagerViewControllerProtocolHelper.h */; settings = {ATTRIBUTES = (Public, ); }; }; D268D82C26700292008BD413 /* MVMCoreViewManagerViewControllerProtocolHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = D268D82A26700292008BD413 /* MVMCoreViewManagerViewControllerProtocolHelper.m */; }; D27073B725BB45C4001C7246 /* ActionActionsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27073B625BB45C4001C7246 /* ActionActionsModel.swift */; }; - D27073CD25BB4CEF001C7246 /* MVMCoreActionHandler+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27073CC25BB4CEF001C7246 /* MVMCoreActionHandler+Extension.swift */; }; - D27073D125BB844B001C7246 /* MVMCoreActionDelegateProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27073D025BB844B001C7246 /* MVMCoreActionDelegateProtocol+Extension.swift */; }; + D27073CD25BB4CEF001C7246 /* MVMCoreActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27073CC25BB4CEF001C7246 /* MVMCoreActionHandler.swift */; }; + D27073D125BB844B001C7246 /* ActionDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27073D025BB844B001C7246 /* ActionDelegateProtocol.swift */; }; D282AAB62240085300C46919 /* MVMCoreGetterUtility+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB52240085300C46919 /* MVMCoreGetterUtility+Extension.swift */; }; D282AAB82240342D00C46919 /* NSNumber+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB72240342D00C46919 /* NSNumber+Extension.swift */; }; D288D5F526C6EFE000A5C365 /* MVMCoreLoggingHandler+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D288D5F426C6EFE000A5C365 /* MVMCoreLoggingHandler+Extension.swift */; }; @@ -242,6 +254,20 @@ 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 = ""; }; + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; @@ -274,8 +300,6 @@ AFBB96AE1FBA3B590008D868 /* MVMCoreGetterUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreGetterUtility.h; sourceTree = ""; }; AFBB96AF1FBA3B590008D868 /* MVMCoreGetterUtility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreGetterUtility.m; sourceTree = ""; }; AFBB96B51FBA3CEC0008D868 /* MVMCoreActionDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreActionDelegateProtocol.h; sourceTree = ""; }; - AFBB96B61FBA3CEC0008D868 /* MVMCoreActionHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreActionHandler.h; sourceTree = ""; }; - AFBB96B71FBA3CEC0008D868 /* MVMCoreActionHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreActionHandler.m; sourceTree = ""; }; AFBB96D21FBA44420008D868 /* VZWAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VZWAuthentication.framework; path = ../../SharedFrameworks/VZWAuthentication.framework; sourceTree = ""; }; AFBB96E91FBA4A260008D868 /* MFHardCodedServerResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFHardCodedServerResponse.h; sourceTree = ""; }; AFBB96EA1FBA4A260008D868 /* MFHardCodedServerResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFHardCodedServerResponse.m; sourceTree = ""; }; @@ -300,8 +324,8 @@ D268D82926700292008BD413 /* MVMCoreViewManagerViewControllerProtocolHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreViewManagerViewControllerProtocolHelper.h; sourceTree = ""; }; D268D82A26700292008BD413 /* MVMCoreViewManagerViewControllerProtocolHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreViewManagerViewControllerProtocolHelper.m; sourceTree = ""; }; D27073B625BB45C4001C7246 /* ActionActionsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionActionsModel.swift; sourceTree = ""; }; - D27073CC25BB4CEF001C7246 /* MVMCoreActionHandler+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreActionHandler+Extension.swift"; sourceTree = ""; }; - D27073D025BB844B001C7246 /* MVMCoreActionDelegateProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreActionDelegateProtocol+Extension.swift"; sourceTree = ""; }; + D27073CC25BB4CEF001C7246 /* MVMCoreActionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreActionHandler.swift; sourceTree = ""; }; + D27073D025BB844B001C7246 /* ActionDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionDelegateProtocol.swift; sourceTree = ""; }; D282AAB52240085300C46919 /* MVMCoreGetterUtility+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreGetterUtility+Extension.swift"; sourceTree = ""; }; D282AAB72240342D00C46919 /* NSNumber+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNumber+Extension.swift"; sourceTree = ""; }; D288D5F426C6EFE000A5C365 /* MVMCoreLoggingHandler+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreLoggingHandler+Extension.swift"; sourceTree = ""; }; @@ -381,6 +405,8 @@ 8876D5D41FB50AAB00EB2E3D /* Utility */ = { isa = PBXGroup; children = ( + AF60A7F1289212CA00919EEB /* MVMError.swift */, + AF60A7F3289212EB00919EEB /* MVMCoreError.swift */, 881D26911FCC9D180079C521 /* MVMCoreErrorObject.h */, 881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */, 881D26921FCC9D180079C521 /* MVMCoreOperation.h */, @@ -462,19 +488,7 @@ 1DAD0FFD26AAB3FF00216E83 /* ActionRunJavaScriptModel.swift */, 016FF6EC259A4E3E00F5E4AA /* Client Parameters */, 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; sourceTree = ""; @@ -619,13 +633,35 @@ isa = PBXGroup; children = ( AFBB96B51FBA3CEC0008D868 /* MVMCoreActionDelegateProtocol.h */, - D27073D025BB844B001C7246 /* MVMCoreActionDelegateProtocol+Extension.swift */, - AFBB96B61FBA3CEC0008D868 /* MVMCoreActionHandler.h */, - AFBB96B71FBA3CEC0008D868 /* MVMCoreActionHandler.m */, - D27073CC25BB4CEF001C7246 /* MVMCoreActionHandler+Extension.swift */, + D27073D025BB844B001C7246 /* ActionDelegateProtocol.swift */, + D27073CC25BB4CEF001C7246 /* MVMCoreActionHandler.swift */, + 946EE1BB237B691A0036751F /* ActionOpenPageModel.swift */, + AF70699F2880F0EB00077CF6 /* ActionOpenPageHandler.swift */, 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 */, + 0ACC81A12613C73800A9C886 /* ActionContactModel.swift */, + AF706999287DD02400077CF6 /* ActionContactHandler.swift */, + 0AFF597923FC6E60005C24E8 /* ActionShareModel.swift */, + AF70699D2880D01400077CF6 /* ActionShareHandler.swift */, ); path = ActionHandling; sourceTree = ""; @@ -749,7 +785,6 @@ AF43A5831FBB66DE008E9347 /* MVMCoreConstants.h in Headers */, AFED77A81FCCA29400BAE689 /* MVMCoreViewControllerStoryBoardMappingObject.h in Headers */, AFEEE8191FCDEB8D00B5EDD0 /* MVMCoreLoggingDelegateProtocol.h in Headers */, - AFBB96B91FBA3CEC0008D868 /* MVMCoreActionHandler.h in Headers */, AF43A74C1FC6109F008E9347 /* MVMCoreSessionObject.h in Headers */, 8876D5F41FB50AB000EB2E3D /* UILabel+MFCustom.h in Headers */, AFFCFA681FCCC0D700FD0730 /* MVMCoreLoadingViewControllerProtocol.h in Headers */, @@ -852,12 +887,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 */, @@ -869,13 +906,14 @@ 01F2A04C23A82B1B00D954D8 /* ActionCallModel.swift in Sources */, 8876D5ED1FB50AB000EB2E3D /* NSDictionary+MFConvenience.m in Sources */, AFBB968C1FBA3A9A0008D868 /* MVMCoreDismissViewControllerOperation.m in Sources */, - AFBB96BA1FBA3CEC0008D868 /* MVMCoreActionHandler.m in Sources */, AFBB96ED1FBA4A260008D868 /* MFHardCodedServerResponse.m in Sources */, AF43A74D1FC6109F008E9347 /* MVMCoreSessionObject.m in Sources */, D282AAB62240085300C46919 /* MVMCoreGetterUtility+Extension.swift in Sources */, AFBB96901FBA3A9A0008D868 /* MVMCoreNavigationObject.m 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 */, 881D26931FCC9D180079C521 /* MVMCoreErrorObject.m in Sources */, @@ -885,33 +923,40 @@ 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 */, 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 */, - D27073CD25BB4CEF001C7246 /* MVMCoreActionHandler+Extension.swift in Sources */, + AF69D4F5286E9F5900BC6862 /* ActionSettingHandler.swift in Sources */, + D27073CD25BB4CEF001C7246 /* MVMCoreActionHandler.swift in Sources */, 01F2A05223A8325100D954D8 /* ModelMapping.swift in Sources */, 8876D5F51FB50AB000EB2E3D /* UILabel+MFCustom.m 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 */, @@ -920,13 +965,16 @@ EA09CD62282ACDDB00A7835F /* Decoder+UserInfo.swift in Sources */, EA09CD99282BF83600A7835F /* DecodableDefault.swift in Sources */, 01C851D123CF97FE0021F976 /* ActionBackModel.swift in Sources */, - D27073D125BB844B001C7246 /* MVMCoreActionDelegateProtocol+Extension.swift in Sources */, + D27073D125BB844B001C7246 /* ActionDelegateProtocol.swift in Sources */, AFBB96921FBA3A9A0008D868 /* MVMCoreNavigationOperation.m in Sources */, 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 */, + AF69D4F1286E9D8000BC6862 /* ActionNoopHandler.swift in Sources */, 016FF6F2259A4FCC00F5E4AA /* ClientParameterModel.swift in Sources */, D2DEDCBB23C65BC300C44CC4 /* Percent.swift in Sources */, AFBB966A1FBA3A570008D868 /* MVMCoreLoadRequestOperation.m in Sources */, @@ -1098,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; @@ -1126,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; diff --git a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift new file mode 100644 index 0000000..6a0d34c --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift @@ -0,0 +1,51 @@ +// +// ActionActionsHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +open class ActionActionsHandler: 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? 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 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 (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction(with: action, additionalData: additionalData, delegateObject: delegateObject) + } + } + } + } else { + for action in model.actions { + try Task.checkCancellation() + try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction(with: action, additionalData: additionalData, delegateObject: delegateObject) + } + } + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionActionsModel.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift similarity index 95% rename from MVMCore/MVMCore/Models/ActionType/ActionActionsModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift index 0638f3e..c7e00fc 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionActionsModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift @@ -8,7 +8,7 @@ import Foundation -@objcMembers open class ActionActionsModel: ActionModelProtocol { +public struct ActionActionsModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -46,7 +46,7 @@ import Foundation // 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/ActionBackHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift new file mode 100644 index 0000000..6e93fd4 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift @@ -0,0 +1,32 @@ +// +// ActionBackHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +open class ActionBackHandler: MVMCoreJSONActionHandlerProtocol { + required public init() {} + + 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. + closure(JSON, additionalData) + } else { + try await execute(with: model, delegateObject: delegateObject, additionalData: additionalData) + } + } + + 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: { + continuation.resume() + }) + } + } + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionBackModel.swift b/MVMCore/MVMCore/ActionHandling/ActionBackModel.swift similarity index 93% rename from MVMCore/MVMCore/Models/ActionType/ActionBackModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionBackModel.swift index 3ecdfe4..fea8eea 100644 --- a/MVMCore/MVMCore/Models/ActionType/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 new file mode 100644 index 0000000..2d5d530 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift @@ -0,0 +1,19 @@ +// +// 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 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/Models/ActionType/ActionCallModel.swift b/MVMCore/MVMCore/ActionHandling/ActionCallModel.swift similarity index 94% rename from MVMCore/MVMCore/Models/ActionType/ActionCallModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionCallModel.swift index e5dba2b..dc6a32e 100644 --- a/MVMCore/MVMCore/Models/ActionType/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 new file mode 100644 index 0000000..6dbe413 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift @@ -0,0 +1,20 @@ +// +// ActionCancelHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +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 execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionCancelModel.swift b/MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift similarity index 92% rename from MVMCore/MVMCore/Models/ActionType/ActionCancelModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift index 8a77c11..83ae013 100644 --- a/MVMCore/MVMCore/Models/ActionType/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 new file mode 100644 index 0000000..513bd2d --- /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(priority: .userInitiated) { + await closure() + } + } + } + + 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 { + 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 + 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/Models/ActionType/ActionContactModel.swift b/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift similarity index 81% rename from MVMCore/MVMCore/Models/ActionType/ActionContactModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionContactModel.swift index f473042..9b975fc 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionContactModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift @@ -9,18 +9,24 @@ import ContactsUI -@objcMembers public class ActionContactModel: ActionModelProtocol { +public struct ActionContactModel: ActionModelProtocol { //-------------------------------------------------- // 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/ActionDelegateProtocol.swift b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift new file mode 100644 index 0000000..6788d08 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift @@ -0,0 +1,21 @@ +// +// MVMCoreActionDelegateProtocolExtension.swift +// MVMCore +// +// Created by Scott Pfeil on 1/22/21. +// Copyright © 2021 myverizon. All rights reserved. +// + +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 +} + +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) + } +} diff --git a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift new file mode 100644 index 0000000..c46afcd --- /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 execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {} +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionNoopModel.swift b/MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift similarity index 93% rename from MVMCore/MVMCore/Models/ActionType/ActionNoopModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift index 25f3ded..941e80a 100644 --- a/MVMCore/MVMCore/Models/ActionType/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 new file mode 100644 index 0000000..7bdc502 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -0,0 +1,90 @@ +// +// OpenPageHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 7/14/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +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 } + do { + 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 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) + } + } + + 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 { + try handle(error: error, model: model, delegateObject: delegateObject) + } + } + + /// Adds client parameters and makes calls performRequest() + 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() + return MVMCoreLoadHandler.sharedGlobal()?.loadRequest(requestParameters, dataForPage: additionalData, delegateObject: delegateObject) + } + + /// 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 { + + /// 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() + } + defer { + if showLoadingOverlay { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) + } + } + return try await withCheckedThrowingContinuation({ continuation in + do { + try getParameters(with: model, requestParameters: requestParameters) { parameters in + continuation.resume(returning: parameters) + } + } catch { + continuation.resume(throwing: error) + } + }) + } +} diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift new file mode 100644 index 0000000..611b7b7 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift @@ -0,0 +1,113 @@ +// +// ActionModel.swift +// MVMCore +// +// Created by Suresh, Kamlesh on 10/3/19. +// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// + +import Foundation + +public struct ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, ClientParameterActionProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + 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? + + public var requestParameters: MVMCoreRequestParameters + + //-------------------------------------------------- + // MARK: - Initialzier + //-------------------------------------------------- + + public init(pageType: String, presentationStyle: String? = nil, extraParameters: JSONValueDictionary? = nil, analyticsData: JSONValueDictionary? = nil, tabBarIndex: Int? = nil, background: Bool? = nil) { + self.pageType = pageType + self.presentationStyle = presentationStyle + self.extraParameters = extraParameters + 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 + } + + 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() + } + + 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) + 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/ActionOpenSMSHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift new file mode 100644 index 0000000..f917fb5 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift @@ -0,0 +1,33 @@ +// +// ActionOpenSMSHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 6/30/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +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 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) + try await ActionOpenUrlHandler.openURL(with: string) + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionOpenSMSModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift similarity index 93% rename from MVMCore/MVMCore/Models/ActionType/ActionOpenSMSModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift index ead5d68..88f709d 100644 --- a/MVMCore/MVMCore/Models/ActionType/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 991ed89..5ed9d2d 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift @@ -8,43 +8,84 @@ 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 { + 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 + } +} + +open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol { + 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)" + } + } + } + + /// 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 + if successful { + continuation.resume() + } else { + continuation.resume(throwing: ActionOpenUrlHandler.URLError.failedToOpen(url: url)) + } + } + } as Void + } + + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + try await execute(with: model, delegateObject: delegateObject, additionalData: additionalData) + } + + 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. + 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 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/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 new file mode 100644 index 0000000..a3bded2 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift @@ -0,0 +1,47 @@ +// +// 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: MVMCoreJSONActionHandlerProtocol { + required public init() {} + + 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 execute(with: model, delegateObject: delegateObject, additionalData: additionalData) + } + + 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 } + + // 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 json ?? MVMCoreActionHandler.convertActionToJSON(model) + delegateObject?.actionDelegate?.handleOpenPage?(for: previousRequest, actionInformation: json, additionalData: additionalData) + } else { + 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/Models/ActionType/ActionPreviousSubmitModel.swift b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift similarity index 92% rename from MVMCore/MVMCore/Models/ActionType/ActionPreviousSubmitModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift index 506f056..31290dc 100644 --- a/MVMCore/MVMCore/Models/ActionType/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 new file mode 100644 index 0000000..20e7a90 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift @@ -0,0 +1,35 @@ +// +// 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 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 + + // Invalidates the session before restarting. + MVMCoreSessionTimeHandler.sharedSession()?.invalidateSession({ error in + if let error = error { + guard error.code != NSURLErrorCancelled else { + 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.toJSON(), clearAllVariables: true) + continuation.resume() + } + }) + } + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionRestartModel.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift similarity index 93% rename from MVMCore/MVMCore/Models/ActionType/ActionRestartModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift index da785ac..7cc2d26 100644 --- a/MVMCore/MVMCore/Models/ActionType/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 new file mode 100644 index 0000000..0e468ce --- /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 execute(with model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + try await ActionOpenUrlHandler.openURL(with: await UIApplication.openSettingsURLString) + } +} diff --git a/MVMCore/MVMCore/Models/ActionType/ActionSettingModel.swift b/MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift similarity index 92% rename from MVMCore/MVMCore/Models/ActionType/ActionSettingModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift index 96e95d3..b2fd687 100644 --- a/MVMCore/MVMCore/Models/ActionType/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 new file mode 100644 index 0000000..3f8cb11 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift @@ -0,0 +1,55 @@ +// +// 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 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 { + 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 + 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 + 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: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) { + 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 71% rename from MVMCore/MVMCore/Models/ActionType/ActionShareModel.swift rename to MVMCore/MVMCore/ActionHandling/ActionShareModel.swift index 893c638..2203a8c 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionShareModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift @@ -7,7 +7,13 @@ // -@objcMembers public class ActionShareModel: ActionModelProtocol { +public struct 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 deleted file mode 100644 index 1308ee5..0000000 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol+Extension.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// MVMCoreActionDelegateProtocolExtension.swift -// MVMCore -// -// Created by Scott Pfeil on 1/22/21. -// Copyright © 2021 myverizon. All rights reserved. -// - -import Foundation - -public extension MVMCoreActionDelegateProtocol { - /// 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/MVMCoreActionDelegateProtocol.h b/MVMCore/MVMCore/ActionHandling/MVMCoreActionDelegateProtocol.h index 85616b7..53cde7f 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; @@ -36,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+Extension.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift deleted file mode 100644 index 8b66d82..0000000 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler+Extension.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// MVMCoreActionHandler+Extension.swift -// MVMCore -// -// Created by Scott Pfeil on 1/22/21. -// Copyright © 2021 myverizon. All rights reserved. -// - -import Foundation - -public protocol MVMCoreActionHandlerProtocol: ModelHandlerProtocol { - init() - func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) -} - -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 - } - } - - @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?) { - guard let json = convertActionToJSON(model, delegateObject: delegateObject) else { return } - synchronouslyHandleAction(model.actionType, actionInformation: json, additionalData: additionalData, delegateObject: delegateObject) - } - - /// 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) - } - - @objc static func getErrorLocation(with delegate: MVMCoreActionDelegateProtocol?, actionType: String) -> String { - return "\(String(describing: delegate))_\(actionType)" - } -} diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h deleted file mode 100644 index 2f46e22..0000000 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.h +++ /dev/null @@ -1,97 +0,0 @@ -// -// MVMCoreActionHandler.h -// myverizon -// -// Created by Scott Pfeil on 11/20/15. -// Copyright © 2015 Verizon Wireless. All rights reserved. -// -// 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; - -/// 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; - -/// 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; - -@end diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m deleted file mode 100644 index fb51e8c..0000000 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.m +++ /dev/null @@ -1,486 +0,0 @@ -// -// MVMCoreActionHandler.m -// myverizon -// -// Created by Scott Pfeil on 11/20/15. -// Copyright © 2015 Verizon Wireless. All rights reserved. -// - -#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: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]; - } -} - -- (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)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]; -} - -- (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]; - } - } -} - -- (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]; -} - -- (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]; - } -} - -@end diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift new file mode 100644 index 0000000..398a590 --- /dev/null +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift @@ -0,0 +1,204 @@ +// +// MVMCoreActionHandler.swift +// MVMCore +// +// Created by Scott Pfeil on 1/22/21. +// Copyright © 2021 myverizon. All rights reserved. +// + +import Foundation + +/// Handlers that can be registered and used by the MVMCoreActionHandler to handle actions. +public protocol MVMCoreActionHandlerProtocol: ModelHandlerProtocol { + init() + + 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. 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 +} + +@objc open class MVMCoreActionHandler: NSObject { + + enum ActionError: MVMError, CustomStringConvertible { + case unknownAction(type: String) + + public var description: String { + switch self { + case MVMCoreActionHandler.ActionError.unknownAction(let type): + return "Couldn't perform action: \(type)" + } + } + + public var errorCode: Int { + ErrorCode.unknownActionType.rawValue + } + } + + /// Used to temporarily store the json in additionalData. + private let jsonKey = "MVMCore.JSON" + + /// 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 + } + return json + } + + /// Creates a model from the action json. + 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, delegateObject: delegateObject) as? ActionModelProtocol else { + throw ModelRegistry.Error.decoderOther(message: "Could not decode to ActionModelProtocol") + } + return actionModel + } + + // MARK: - Error Handling + + /// 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 + + /// 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 + logAction(with: json, additionalData: additionalData, delegateObject: delegateObject) + + do { + let handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type + let handler = handlerType.init() + 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: 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 + } + } + + // MARK: - Subclassables + + /// Subclass to log the action was fired. + open func logAction(with JSON: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + // Calls legacy log action function. + delegateObject?.actionDelegate?.logAction?(withActionInformation: JSON, additionalData: additionalData) + } + + /// Logs the error. + @objc open func defaultHandleActionError(_ error: MVMCoreErrorObject, additionalData: [AnyHashable: Any]?) { + guard error.logError else { return } + MVMCoreLoggingHandler.addError(toLog: error) + } + + // MARK: - Legacy Holdovers + + 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) + } + + 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?) { + _ = 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) + 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. + if try await handleUnregisteredAction(with: nil, json: json!, additionalData: additionalData, delegateObject: delegateObject) == false { + fallthrough + } + default: + MVMCoreActionHandler.log(string: "Failed Action: Error \(error)", additionalData: additionalData) + if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: actionType ?? "noAction")) { + defaultHandleActionError(errorObject, additionalData: additionalData) + } + } + } + } + } + + /// 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() + 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 + open func handleUnregisteredAction(with model: ActionModelProtocol?, json: [AnyHashable: Any], additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws -> Bool { + // Check if the delegate handles the action. + 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(actionType, json, additionalData) + return true + } else { + return false + } + } +} 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/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/Models/ActionType/ActionOpenPageModel.swift b/MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift deleted file mode 100644 index 184be57..0000000 --- a/MVMCore/MVMCore/Models/ActionType/ActionOpenPageModel.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// ActionModel.swift -// MVMCore -// -// Created by Suresh, Kamlesh on 10/3/19. -// Copyright © 2019 Suresh, Kamlesh. All rights reserved. -// - - -@objcMembers open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, ClientParameterActionProtocol { - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - - public static var identifier: String = "openPage" - public var actionType: String = ActionOpenPageModel.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? - - //-------------------------------------------------- - // MARK: - Initialzier - //-------------------------------------------------- - - public init(pageType: String, presentationStyle: String? = nil, extraParameters: JSONValueDictionary? = nil, analyticsData: JSONValueDictionary? = nil, tabBarIndex: Int? = nil, background: Bool? = nil) { - self.pageType = pageType - self.presentationStyle = presentationStyle - self.extraParameters = extraParameters - self.analyticsData = analyticsData - self.tabBarIndex = tabBarIndex - self.background = background - } -} 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 da05419..a7f546c 100644 --- a/MVMCore/MVMCore/Models/ModelMapping.swift +++ b/MVMCore/MVMCore/Models/ModelMapping.swift @@ -12,18 +12,18 @@ 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(ActionCallModel.self) - ModelRegistry.register(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(ActionContactModel.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) + 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(handler: ActionContactHandler.self, for: ActionContactModel.self) } } 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 03a8411..be532a7 100644 --- a/MVMCore/MVMCore/Singletons/MVMCoreObject.h +++ b/MVMCore/MVMCore/Singletons/MVMCoreObject.h @@ -10,13 +10,13 @@ #import #import #import -#import #import #import #import #import #import #import +@class MVMCoreActionHandler; @interface MVMCoreObject : NSObject diff --git a/MVMCore/MVMCore/Utility/MVMCoreError.swift b/MVMCore/MVMCore/Utility/MVMCoreError.swift new file mode 100644 index 0000000..31fe2f8 --- /dev/null +++ b/MVMCore/MVMCore/Utility/MVMCoreError.swift @@ -0,0 +1,52 @@ +// +// 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) + } + } +} + +@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:)]) { diff --git a/MVMCore/MVMCore/Utility/MVMError.swift b/MVMCore/MVMCore/Utility/MVMError.swift new file mode 100644 index 0000000..e048840 --- /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 + +public protocol MVMError: LocalizedError, CustomNSError {} +extension MVMError { + public var errorDescription: String? { return MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) } + + public static var errorDomain: String { + return ErrorDomainNative + } +}