diff --git a/.gitignore b/.gitignore index f146c77..e59d4f9 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,10 @@ SharedFrameworks/* # Exception to the rule above (Keeps the folder around) !SharedFrameworks/.gitkeep +VDSTypographyTokens.framework +VDSFormControlsTokens.framework +VDSColorTokens.framework + __MACOSX # Xcode diff --git a/JSONCreator.xcworkspace/contents.xcworkspacedata b/JSONCreator.xcworkspace/contents.xcworkspacedata index 7a5da18..40a48cc 100644 --- a/JSONCreator.xcworkspace/contents.xcworkspacedata +++ b/JSONCreator.xcworkspace/contents.xcworkspacedata @@ -5,10 +5,10 @@ location = "group:vds_ios/VDS.xcodeproj"> + location = "group:mvm_core/MVMCore/MVMCore.xcodeproj"> + location = "group:mvm_core_ui/MVMCoreUI.xcodeproj"> diff --git a/JSONCreator_iOS/JSONCreator.xcodeproj/project.pbxproj b/JSONCreator_iOS/JSONCreator.xcodeproj/project.pbxproj index 72d4422..48c0ec6 100644 --- a/JSONCreator_iOS/JSONCreator.xcodeproj/project.pbxproj +++ b/JSONCreator_iOS/JSONCreator.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 52; objects = { /* Begin PBXAggregateTarget section */ @@ -18,11 +18,20 @@ name = UpdateDependency; productName = UpdateDependency; }; + EA985CBC298AE8CB00F2FF2E /* Artifactory */ = { + isa = PBXAggregateTarget; + buildConfigurationList = EA985CBF298AE8CC00F2FF2E /* Build configuration list for PBXAggregateTarget "Artifactory" */; + buildPhases = ( + EA985CC0298AE8D000F2FF2E /* ShellScript */, + ); + dependencies = ( + ); + name = Artifactory; + productName = Artifactory; + }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - AF0EE4642A4B7AD4000DF6CF /* VDS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF0EE4632A4B7AD3000DF6CF /* VDS.framework */; }; - AF0EE4652A4B7AD4000DF6CF /* VDS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AF0EE4632A4B7AD3000DF6CF /* VDS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D21B3A27259B93ED001483DC /* SelfSizingCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B3A25259B93ED001483DC /* SelfSizingCollectionView.swift */; }; D2431DEB25E93A4F001C7AAC /* buttimag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2431DEA25E93A4F001C7AAC /* buttimag.swift */; }; D27564B62590FADB003CA713 /* ListDeviceRightVariableCaretModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27564B42590FADB003CA713 /* ListDeviceRightVariableCaretModel.swift */; }; @@ -42,10 +51,47 @@ D2FC4FAE25897ACB00061EA4 /* OrderTrackerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FC4FAA25897ACB00061EA4 /* OrderTrackerModel.swift */; }; D2FC4FAF25897ACB00061EA4 /* Step.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FC4FAB25897ACB00061EA4 /* Step.swift */; }; D2FC4FB025897ACB00061EA4 /* OrderTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FC4FAC25897ACB00061EA4 /* OrderTracker.swift */; }; - EA7E676B27582F2200ABF773 /* MVMCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA7E676927582F2200ABF773 /* MVMCore.framework */; }; - EA7E676C27582F2200ABF773 /* MVMCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EA7E676927582F2200ABF773 /* MVMCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - EA7E676D27582F2200ABF773 /* MVMCoreUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA7E676A27582F2200ABF773 /* MVMCoreUI.framework */; }; - EA7E676E27582F2200ABF773 /* MVMCoreUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EA7E676A27582F2200ABF773 /* MVMCoreUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + EA09CDBD282C3FD800A7835F /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA09CDBC282C3FD800A7835F /* CoreBluetooth.framework */; }; + EA09CDD1282C40CC00A7835F /* MFFGHSBluetoothPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDBE282C40CB00A7835F /* MFFGHSBluetoothPair.swift */; }; + EA09CDD2282C40CC00A7835F /* GMFGBluetoothPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDC0282C40CB00A7835F /* GMFGBluetoothPair.swift */; }; + EA09CDD6282C40CC00A7835F /* GMFGConstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDC4282C40CB00A7835F /* GMFGConstant.swift */; }; + EA09CDD8282C40CC00A7835F /* GMFGBLEHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDC7282C40CC00A7835F /* GMFGBLEHandlerProtocol.swift */; }; + EA09CDD9282C40CC00A7835F /* GMFG5GCBandSignalHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDC9282C40CC00A7835F /* GMFG5GCBandSignalHandler.swift */; }; + EA09CDDA282C40CC00A7835F /* GMFGOperationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDCA282C40CC00A7835F /* GMFGOperationHandler.swift */; }; + EA09CDDB282C40CC00A7835F /* GMFGPublicInternetAccessHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDCB282C40CC00A7835F /* GMFGPublicInternetAccessHandler.swift */; }; + EA09CDDC282C40CC00A7835F /* GMFGFotaHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDCC282C40CC00A7835F /* GMFGFotaHandler.swift */; }; + EA09CDDD282C40CC00A7835F /* GMFGSpeedTestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDCD282C40CC00A7835F /* GMFGSpeedTestHandler.swift */; }; + EA09CDDE282C40CC00A7835F /* GMFG5GSignalHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDCE282C40CC00A7835F /* GMFG5GSignalHandler.swift */; }; + EA09CDDF282C40CC00A7835F /* GMFGRouterWifiHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDCF282C40CC00A7835F /* GMFGRouterWifiHandler.swift */; }; + EA09CDE6282C416C00A7835F /* BluetoothDebuggableProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDE2282C416C00A7835F /* BluetoothDebuggableProtocol.swift */; }; + EA09CDE7282C416C00A7835F /* MulticastDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDE3282C416C00A7835F /* MulticastDelegate.swift */; }; + EA09CDE8282C416C00A7835F /* BluetoothDebugger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDE4282C416C00A7835F /* BluetoothDebugger.swift */; }; + EA09CDE9282C416C00A7835F /* BluetoothDebuggerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDE5282C416C00A7835F /* BluetoothDebuggerView.swift */; }; + EA09CDEB282C422900A7835F /* GMFGStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDEA282C422900A7835F /* GMFGStorageManager.swift */; }; + EA09CDED282C423F00A7835F /* GMFGLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDEC282C423E00A7835F /* GMFGLocationManager.swift */; }; + EA09CDEF282C429800A7835F /* GMFGTestScreenData.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDEE282C429800A7835F /* GMFGTestScreenData.swift */; }; + EA09CDF8282C430400A7835F /* BluetoothPairingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDF1282C430400A7835F /* BluetoothPairingProtocol.swift */; }; + EA09CDF9282C430400A7835F /* BluetoothPairableProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDF2282C430400A7835F /* BluetoothPairableProtocol.swift */; }; + EA09CDFA282C430400A7835F /* BluetoothConfigModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDF4282C430400A7835F /* BluetoothConfigModel.swift */; }; + EA09CDFB282C430400A7835F /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDF5282C430400A7835F /* PeripheralModel.swift */; }; + EA09CDFC282C430400A7835F /* CharacteristicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDF6282C430400A7835F /* CharacteristicModel.swift */; }; + EA09CDFD282C430400A7835F /* ServiceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDF7282C430400A7835F /* ServiceModel.swift */; }; + EA09CDFF282C437C00A7835F /* MFFGHSAnalyticsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CDFE282C437C00A7835F /* MFFGHSAnalyticsProtocol.swift */; }; + EA09CE01282C43E800A7835F /* KeyedDecodingContainer+Decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CE00282C43E800A7835F /* KeyedDecodingContainer+Decode.swift */; }; + EA09CE03282C44A100A7835F /* MFFGHSUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CE02282C44A100A7835F /* MFFGHSUtility.swift */; }; + EA09CE05282C45C200A7835F /* BluetoothPairBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CE04282C45C200A7835F /* BluetoothPairBehavior.swift */; }; + EA33618B288B1B630071C351 /* VDS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA33618A288B1B630071C351 /* VDS.framework */; }; + EA33618C288B1B630071C351 /* VDS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EA33618A288B1B630071C351 /* VDS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + EA3361FB2891D54A0071C351 /* VDSTypographyTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA3361FA2891D54A0071C351 /* VDSTypographyTokens.xcframework */; }; + EA3361FC2891D54A0071C351 /* VDSTypographyTokens.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EA3361FA2891D54A0071C351 /* VDSTypographyTokens.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + EA5B696E2866BC1000B17D2E /* MVMCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA5B696C2866BC1000B17D2E /* MVMCore.framework */; }; + EA5B696F2866BC1000B17D2E /* MVMCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EA5B696C2866BC1000B17D2E /* MVMCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + EA5B69702866BC1000B17D2E /* MVMCoreUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA5B696D2866BC1000B17D2E /* MVMCoreUI.framework */; }; + EA5B69712866BC1000B17D2E /* MVMCoreUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EA5B696D2866BC1000B17D2E /* MVMCoreUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + EAA658152875FA5E00484A7D /* VDSFormControlsTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA658142875FA5E00484A7D /* VDSFormControlsTokens.xcframework */; }; + EAA658162875FA5E00484A7D /* VDSFormControlsTokens.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EAA658142875FA5E00484A7D /* VDSFormControlsTokens.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + EACA5E5E2853DBC900CBA65B /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EACA5E5D2853DBC900CBA65B /* VDSColorTokens.xcframework */; }; + EACA5E5F2853DBC900CBA65B /* VDSColorTokens.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EACA5E5D2853DBC900CBA65B /* VDSColorTokens.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -55,9 +101,12 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - EA7E676E27582F2200ABF773 /* MVMCoreUI.framework in Embed Frameworks */, - EA7E676C27582F2200ABF773 /* MVMCore.framework in Embed Frameworks */, - AF0EE4652A4B7AD4000DF6CF /* VDS.framework in Embed Frameworks */, + EA33618C288B1B630071C351 /* VDS.framework in Embed Frameworks */, + EAA658162875FA5E00484A7D /* VDSFormControlsTokens.xcframework in Embed Frameworks */, + EA5B696F2866BC1000B17D2E /* MVMCore.framework in Embed Frameworks */, + EACA5E5F2853DBC900CBA65B /* VDSColorTokens.xcframework in Embed Frameworks */, + EA3361FC2891D54A0071C351 /* VDSTypographyTokens.xcframework in Embed Frameworks */, + EA5B69712866BC1000B17D2E /* MVMCoreUI.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -65,7 +114,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - AF0EE4632A4B7AD3000DF6CF /* VDS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = VDS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D21B3A25259B93ED001483DC /* SelfSizingCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelfSizingCollectionView.swift; sourceTree = ""; }; D2431DEA25E93A4F001C7AAC /* buttimag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = buttimag.swift; sourceTree = ""; }; D27564B42590FADB003CA713 /* ListDeviceRightVariableCaretModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListDeviceRightVariableCaretModel.swift; sourceTree = ""; }; @@ -90,8 +138,47 @@ D2FC4FAA25897ACB00061EA4 /* OrderTrackerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTrackerModel.swift; sourceTree = ""; }; D2FC4FAB25897ACB00061EA4 /* Step.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Step.swift; sourceTree = ""; }; D2FC4FAC25897ACB00061EA4 /* OrderTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTracker.swift; sourceTree = ""; }; + EA09CDBC282C3FD800A7835F /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; + EA09CDBE282C40CB00A7835F /* MFFGHSBluetoothPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFFGHSBluetoothPair.swift; sourceTree = ""; }; + EA09CDC0282C40CB00A7835F /* GMFGBluetoothPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGBluetoothPair.swift; sourceTree = ""; }; + EA09CDC4282C40CB00A7835F /* GMFGConstant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGConstant.swift; sourceTree = ""; }; + EA09CDC7282C40CC00A7835F /* GMFGBLEHandlerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGBLEHandlerProtocol.swift; sourceTree = ""; }; + EA09CDC9282C40CC00A7835F /* GMFG5GCBandSignalHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFG5GCBandSignalHandler.swift; sourceTree = ""; }; + EA09CDCA282C40CC00A7835F /* GMFGOperationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGOperationHandler.swift; sourceTree = ""; }; + EA09CDCB282C40CC00A7835F /* GMFGPublicInternetAccessHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGPublicInternetAccessHandler.swift; sourceTree = ""; }; + EA09CDCC282C40CC00A7835F /* GMFGFotaHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGFotaHandler.swift; sourceTree = ""; }; + EA09CDCD282C40CC00A7835F /* GMFGSpeedTestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGSpeedTestHandler.swift; sourceTree = ""; }; + EA09CDCE282C40CC00A7835F /* GMFG5GSignalHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFG5GSignalHandler.swift; sourceTree = ""; }; + EA09CDCF282C40CC00A7835F /* GMFGRouterWifiHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGRouterWifiHandler.swift; sourceTree = ""; }; + EA09CDE2282C416C00A7835F /* BluetoothDebuggableProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothDebuggableProtocol.swift; sourceTree = ""; }; + EA09CDE3282C416C00A7835F /* MulticastDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MulticastDelegate.swift; sourceTree = ""; }; + EA09CDE4282C416C00A7835F /* BluetoothDebugger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothDebugger.swift; sourceTree = ""; }; + EA09CDE5282C416C00A7835F /* BluetoothDebuggerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothDebuggerView.swift; sourceTree = ""; }; + EA09CDEA282C422900A7835F /* GMFGStorageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGStorageManager.swift; sourceTree = ""; }; + EA09CDEC282C423E00A7835F /* GMFGLocationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGLocationManager.swift; sourceTree = ""; }; + EA09CDEE282C429800A7835F /* GMFGTestScreenData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GMFGTestScreenData.swift; sourceTree = ""; }; + EA09CDF1282C430400A7835F /* BluetoothPairingProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothPairingProtocol.swift; sourceTree = ""; }; + EA09CDF2282C430400A7835F /* BluetoothPairableProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothPairableProtocol.swift; sourceTree = ""; }; + EA09CDF4282C430400A7835F /* BluetoothConfigModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothConfigModel.swift; sourceTree = ""; }; + EA09CDF5282C430400A7835F /* PeripheralModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; + EA09CDF6282C430400A7835F /* CharacteristicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacteristicModel.swift; sourceTree = ""; }; + EA09CDF7282C430400A7835F /* ServiceModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceModel.swift; sourceTree = ""; }; + EA09CDFE282C437C00A7835F /* MFFGHSAnalyticsProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFFGHSAnalyticsProtocol.swift; sourceTree = ""; }; + EA09CE00282C43E800A7835F /* KeyedDecodingContainer+Decode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KeyedDecodingContainer+Decode.swift"; sourceTree = ""; }; + EA09CE02282C44A100A7835F /* MFFGHSUtility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFFGHSUtility.swift; sourceTree = ""; }; + EA09CE04282C45C200A7835F /* BluetoothPairBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothPairBehavior.swift; sourceTree = ""; }; + EA2ED278285BB3F400781478 /* MVMCoreUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MVMCoreUI.xcframework; path = ../SharedFrameworks/MVMCoreUI.xcframework; sourceTree = ""; }; + EA2ED279285BB3F400781478 /* MVMCore.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MVMCore.xcframework; path = ../SharedFrameworks/MVMCore.xcframework; sourceTree = ""; }; + EA2ED27E285BD00D00781478 /* MVMCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MVMCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EA2ED27F285BD00D00781478 /* MVMCoreUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MVMCoreUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EA33618A288B1B630071C351 /* VDS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = VDS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EA3361FA2891D54A0071C351 /* VDSTypographyTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSTypographyTokens.xcframework; path = ../SharedFrameworks/VDSTypographyTokens.xcframework; sourceTree = ""; }; + EA5B696C2866BC1000B17D2E /* MVMCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MVMCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EA5B696D2866BC1000B17D2E /* MVMCoreUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MVMCoreUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EA7E676927582F2200ABF773 /* MVMCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MVMCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EA7E676A27582F2200ABF773 /* MVMCoreUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MVMCoreUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EAA658142875FA5E00484A7D /* VDSFormControlsTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSFormControlsTokens.xcframework; path = ../SharedFrameworks/VDSFormControlsTokens.xcframework; sourceTree = ""; }; + EACA5E5D2853DBC900CBA65B /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -99,9 +186,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - EA7E676D27582F2200ABF773 /* MVMCoreUI.framework in Frameworks */, - EA7E676B27582F2200ABF773 /* MVMCore.framework in Frameworks */, - AF0EE4642A4B7AD4000DF6CF /* VDS.framework in Frameworks */, + EACA5E5E2853DBC900CBA65B /* VDSColorTokens.xcframework in Frameworks */, + EA09CDBD282C3FD800A7835F /* CoreBluetooth.framework in Frameworks */, + EA33618B288B1B630071C351 /* VDS.framework in Frameworks */, + EA3361FB2891D54A0071C351 /* VDSTypographyTokens.xcframework in Frameworks */, + EA5B696E2866BC1000B17D2E /* MVMCore.framework in Frameworks */, + EAA658152875FA5E00484A7D /* VDSFormControlsTokens.xcframework in Frameworks */, + EA5B69702866BC1000B17D2E /* MVMCoreUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -138,6 +229,7 @@ D2B1E3F122F4A68F0065F95C /* JSONCreator */ = { isa = PBXGroup; children = ( + EA09CD9A282C3F6B00A7835F /* 5G */, D288D69B26CAE26900A5C365 /* MF */, D2B1E40922F4C9F00065F95C /* JSON */, D2B1E3F222F4A68F0065F95C /* AppDelegate.swift */, @@ -154,7 +246,17 @@ D2B1E42A22F9D0D90065F95C /* Frameworks */ = { isa = PBXGroup; children = ( - AF0EE4632A4B7AD3000DF6CF /* VDS.framework */, + EA3361FA2891D54A0071C351 /* VDSTypographyTokens.xcframework */, + EA33618A288B1B630071C351 /* VDS.framework */, + EAA658142875FA5E00484A7D /* VDSFormControlsTokens.xcframework */, + EA5B696C2866BC1000B17D2E /* MVMCore.framework */, + EA5B696D2866BC1000B17D2E /* MVMCoreUI.framework */, + EA2ED27E285BD00D00781478 /* MVMCore.framework */, + EA2ED27F285BD00D00781478 /* MVMCoreUI.framework */, + EA2ED279285BB3F400781478 /* MVMCore.xcframework */, + EA2ED278285BB3F400781478 /* MVMCoreUI.xcframework */, + EACA5E5D2853DBC900CBA65B /* VDSColorTokens.xcframework */, + EA09CDBC282C3FD800A7835F /* CoreBluetooth.framework */, EA7E676927582F2200ABF773 /* MVMCore.framework */, EA7E676A27582F2200ABF773 /* MVMCoreUI.framework */, D2B1E51C22FA0DF50065F95C /* MVMCore.framework */, @@ -181,6 +283,89 @@ path = "Order Tracker"; sourceTree = ""; }; + EA09CD9A282C3F6B00A7835F /* 5G */ = { + isa = PBXGroup; + children = ( + EA09CE04282C45C200A7835F /* BluetoothPairBehavior.swift */, + EA09CE02282C44A100A7835F /* MFFGHSUtility.swift */, + EA09CE00282C43E800A7835F /* KeyedDecodingContainer+Decode.swift */, + EA09CDBE282C40CB00A7835F /* MFFGHSBluetoothPair.swift */, + EA09CDFE282C437C00A7835F /* MFFGHSAnalyticsProtocol.swift */, + EA09CDF3282C430400A7835F /* Models */, + EA09CDF0282C430400A7835F /* Protocols */, + EA09CDE1282C416C00A7835F /* Debugger */, + EA09CDBF282C40CB00A7835F /* Utility */, + ); + path = 5G; + sourceTree = ""; + }; + EA09CDBF282C40CB00A7835F /* Utility */ = { + isa = PBXGroup; + children = ( + EA09CDEC282C423E00A7835F /* GMFGLocationManager.swift */, + EA09CDEA282C422900A7835F /* GMFGStorageManager.swift */, + EA09CDC0282C40CB00A7835F /* GMFGBluetoothPair.swift */, + EA09CDC4282C40CB00A7835F /* GMFGConstant.swift */, + EA09CDC6282C40CC00A7835F /* Protocols */, + EA09CDC8282C40CC00A7835F /* BLE Handlers */, + EA09CDEE282C429800A7835F /* GMFGTestScreenData.swift */, + ); + path = Utility; + sourceTree = ""; + }; + EA09CDC6282C40CC00A7835F /* Protocols */ = { + isa = PBXGroup; + children = ( + EA09CDC7282C40CC00A7835F /* GMFGBLEHandlerProtocol.swift */, + ); + path = Protocols; + sourceTree = ""; + }; + EA09CDC8282C40CC00A7835F /* BLE Handlers */ = { + isa = PBXGroup; + children = ( + EA09CDC9282C40CC00A7835F /* GMFG5GCBandSignalHandler.swift */, + EA09CDCA282C40CC00A7835F /* GMFGOperationHandler.swift */, + EA09CDCB282C40CC00A7835F /* GMFGPublicInternetAccessHandler.swift */, + EA09CDCC282C40CC00A7835F /* GMFGFotaHandler.swift */, + EA09CDCD282C40CC00A7835F /* GMFGSpeedTestHandler.swift */, + EA09CDCE282C40CC00A7835F /* GMFG5GSignalHandler.swift */, + EA09CDCF282C40CC00A7835F /* GMFGRouterWifiHandler.swift */, + ); + path = "BLE Handlers"; + sourceTree = ""; + }; + EA09CDE1282C416C00A7835F /* Debugger */ = { + isa = PBXGroup; + children = ( + EA09CDE2282C416C00A7835F /* BluetoothDebuggableProtocol.swift */, + EA09CDE3282C416C00A7835F /* MulticastDelegate.swift */, + EA09CDE4282C416C00A7835F /* BluetoothDebugger.swift */, + EA09CDE5282C416C00A7835F /* BluetoothDebuggerView.swift */, + ); + path = Debugger; + sourceTree = ""; + }; + EA09CDF0282C430400A7835F /* Protocols */ = { + isa = PBXGroup; + children = ( + EA09CDF1282C430400A7835F /* BluetoothPairingProtocol.swift */, + EA09CDF2282C430400A7835F /* BluetoothPairableProtocol.swift */, + ); + path = Protocols; + sourceTree = ""; + }; + EA09CDF3282C430400A7835F /* Models */ = { + isa = PBXGroup; + children = ( + EA09CDF4282C430400A7835F /* BluetoothConfigModel.swift */, + EA09CDF5282C430400A7835F /* PeripheralModel.swift */, + EA09CDF6282C430400A7835F /* CharacteristicModel.swift */, + EA09CDF7282C430400A7835F /* ServiceModel.swift */, + ); + path = Models; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -218,6 +403,9 @@ D2B1E43722F9F84E0065F95C = { CreatedOnToolsVersion = 10.3; }; + EA985CBC298AE8CB00F2FF2E = { + CreatedOnToolsVersion = 14.2; + }; }; }; buildConfigurationList = D2B1E3EA22F4A68F0065F95C /* Build configuration list for PBXProject "JSONCreator" */; @@ -237,6 +425,7 @@ targets = ( D2B1E3EE22F4A68F0065F95C /* JSONCreator */, D2B1E43722F9F84E0065F95C /* UpdateDependency */, + EA985CBC298AE8CB00F2FF2E /* Artifactory */, ); }; /* End PBXProject section */ @@ -273,6 +462,23 @@ shellPath = /bin/sh; shellScript = "#!/bin/bash\n\ncd ../\nsh update.sh\n"; }; + EA985CC0298AE8D000F2FF2E /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n# Type a script or drag a script file from your workspace to insert its path.\ncd \"${PROJECT_DIR}\"\ncd ../\ncd \"Supporting Files/Artifactory\"\nmkdir -p \"../../SharedFrameworks\"\nsh DownloadArtifactoryItems.sh\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -280,20 +486,48 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + EA09CDFC282C430400A7835F /* CharacteristicModel.swift in Sources */, D2B1E3F722F4A68F0065F95C /* DetailViewController.swift in Sources */, + EA09CDDD282C40CC00A7835F /* GMFGSpeedTestHandler.swift in Sources */, + EA09CDDF282C40CC00A7835F /* GMFGRouterWifiHandler.swift in Sources */, + EA09CDFD282C430400A7835F /* ServiceModel.swift in Sources */, + EA09CE01282C43E800A7835F /* KeyedDecodingContainer+Decode.swift in Sources */, + EA09CDDE282C40CC00A7835F /* GMFG5GSignalHandler.swift in Sources */, + EA09CDD1282C40CC00A7835F /* MFFGHSBluetoothPair.swift in Sources */, + EA09CDEB282C422900A7835F /* GMFGStorageManager.swift in Sources */, + EA09CE05282C45C200A7835F /* BluetoothPairBehavior.swift in Sources */, + EA09CDFA282C430400A7835F /* BluetoothConfigModel.swift in Sources */, D27564CA25939E91003CA713 /* LinksModel.swift in Sources */, D2B1E3F522F4A68F0065F95C /* MasterViewController.swift in Sources */, D27564B62590FADB003CA713 /* ListDeviceRightVariableCaretModel.swift in Sources */, + EA09CDEF282C429800A7835F /* GMFGTestScreenData.swift in Sources */, + EA09CDED282C423F00A7835F /* GMFGLocationManager.swift in Sources */, + EA09CE03282C44A100A7835F /* MFFGHSUtility.swift in Sources */, + EA09CDF9282C430400A7835F /* BluetoothPairableProtocol.swift in Sources */, D27564B72590FADB003CA713 /* ListDeviceRightVariableCaret.swift in Sources */, D2431DEB25E93A4F001C7AAC /* buttimag.swift in Sources */, D27564C825939E91003CA713 /* LinkCollectionViewCell.swift in Sources */, + EA09CDFB282C430400A7835F /* PeripheralModel.swift in Sources */, D2FC4FB025897ACB00061EA4 /* OrderTracker.swift in Sources */, + EA09CDD8282C40CC00A7835F /* GMFGBLEHandlerProtocol.swift in Sources */, + EA09CDDC282C40CC00A7835F /* GMFGFotaHandler.swift in Sources */, + EA09CDD6282C40CC00A7835F /* GMFGConstant.swift in Sources */, D27564C925939E91003CA713 /* Links.swift in Sources */, + EA09CDE6282C416C00A7835F /* BluetoothDebuggableProtocol.swift in Sources */, + EA09CDD2282C40CC00A7835F /* GMFGBluetoothPair.swift in Sources */, + EA09CDE9282C416C00A7835F /* BluetoothDebuggerView.swift in Sources */, D2FC4FAE25897ACB00061EA4 /* OrderTrackerModel.swift in Sources */, D21B3A27259B93ED001483DC /* SelfSizingCollectionView.swift in Sources */, + EA09CDD9282C40CC00A7835F /* GMFG5GCBandSignalHandler.swift in Sources */, + EA09CDF8282C430400A7835F /* BluetoothPairingProtocol.swift in Sources */, D2B1E3F322F4A68F0065F95C /* AppDelegate.swift in Sources */, + EA09CDDA282C40CC00A7835F /* GMFGOperationHandler.swift in Sources */, + EA09CDE7282C416C00A7835F /* MulticastDelegate.swift in Sources */, D29C557825BF1F340082E7D6 /* JSONCreatorActionHandler.swift in Sources */, + EA09CDDB282C40CC00A7835F /* GMFGPublicInternetAccessHandler.swift in Sources */, D2FC4FAD25897ACB00061EA4 /* StepModel.swift in Sources */, + EA09CDFF282C437C00A7835F /* MFFGHSAnalyticsProtocol.swift in Sources */, + EA09CDE8282C416C00A7835F /* BluetoothDebugger.swift in Sources */, D2FC4FAF25897ACB00061EA4 /* Step.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -447,7 +681,7 @@ "$(PROJECT_DIR)/../SharedFrameworks", ); INFOPLIST_FILE = JSONCreator/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -471,7 +705,7 @@ "$(PROJECT_DIR)/../SharedFrameworks", ); INFOPLIST_FILE = JSONCreator/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -502,6 +736,24 @@ }; name = Release; }; + EA985CBD298AE8CC00F2FF2E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = FCMA4QKS77; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + EA985CBE298AE8CC00F2FF2E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = FCMA4QKS77; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -532,6 +784,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + EA985CBF298AE8CC00F2FF2E /* Build configuration list for PBXAggregateTarget "Artifactory" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EA985CBD298AE8CC00F2FF2E /* Debug */, + EA985CBE298AE8CC00F2FF2E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = D2B1E3E722F4A68F0065F95C /* Project object */; diff --git a/JSONCreator_iOS/JSONCreator.xcodeproj/xcshareddata/xcschemes/JSONCreator.xcscheme b/JSONCreator_iOS/JSONCreator.xcodeproj/xcshareddata/xcschemes/JSONCreator.xcscheme new file mode 100644 index 0000000..39d34d2 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator.xcodeproj/xcshareddata/xcschemes/JSONCreator.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JSONCreator_iOS/JSONCreator/5G/BluetoothPairBehavior.swift b/JSONCreator_iOS/JSONCreator/5G/BluetoothPairBehavior.swift new file mode 100644 index 0000000..891463b --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/BluetoothPairBehavior.swift @@ -0,0 +1,9 @@ +// +// BluetoothPairBehavior.swift +// JSONCreator +// +// Created by Matt Bruce on 5/11/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation diff --git a/JSONCreator_iOS/JSONCreator/5G/Debugger/BluetoothDebuggableProtocol.swift b/JSONCreator_iOS/JSONCreator/5G/Debugger/BluetoothDebuggableProtocol.swift new file mode 100644 index 0000000..a86910f --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Debugger/BluetoothDebuggableProtocol.swift @@ -0,0 +1,43 @@ +// +// BluetoothDebuggableProtocol.swift +// MVM5G +// +// Created by Matt Bruce on 3/18/22. +// Copyright © 2022 Kyle. All rights reserved. +// + +import Foundation +import UIKit + +//This is implemented on ViewControllers +protocol BluetoothDebuggableProtocol: BluetoothDebuggerDelegate { + associatedtype BluetoothPairType: MFFGHSBluetoothPair + var view: UIView! { get set } + var bluetoothPair: BluetoothPairType? { get set } + var bluetoothDebugger: BluetoothDebugger? { get } + var debuggerView: BluetoothDebuggerView? { get set} + func addDebuggerView() +} + +extension BluetoothDebuggableProtocol { + var bluetoothDebugger: BluetoothDebugger? { + return self.bluetoothPair?.bluetoothDebugger + } + + func addDebuggerView(){ + guard GMFGTestScreenData.shared.visualDebugger else { return } + debuggerView = BluetoothDebuggerView() + if let debuggerView = debuggerView { + debuggerView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(debuggerView) + debuggerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + debuggerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + debuggerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + debuggerView.heightAnchor.constraint(equalToConstant: 400).isActive = true + } + } + + func bluetoothDebuggerUpdate(message: String){ + self.debuggerView?.text = message + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Debugger/BluetoothDebugger.swift b/JSONCreator_iOS/JSONCreator/5G/Debugger/BluetoothDebugger.swift new file mode 100644 index 0000000..0b959ea --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Debugger/BluetoothDebugger.swift @@ -0,0 +1,327 @@ +// +// GMFGBluetoothDebugger.swift +// MVM5G +// +// Created by Matt Bruce on 3/16/22. +// Copyright © 2022 Kyle. All rights reserved. +// + +import Foundation +import CoreBluetooth + +//Delegate used to receive updates from the debugger +//during a onChange() debugger event +protocol BluetoothDebuggerDelegate: AnyObject { + func bluetoothDebuggerUpdate(message: String) +} + +class BluetoothDebugger: NSObject { + weak var delegate: BluetoothDebuggerDelegate? + var deviceId: String + var config: BluetoothConfigModel + var bluetoothState: CBManagerState = .unknown { didSet { onChange() } } + var bluetoothStatus: Bool = false { didSet { onChange() } } + var isDevicePaired: Bool = false { didSet{ onChange() } } + var isDeviceActivated: Bool = false{ didSet{ onChange() } } + var isBluetoothPermissionDenied: Bool = false { didSet{ onChange() } } + var deviceDiscovered: String? { didSet{ onChange() } } + var deviceDiscoveredNameFailed: Set = [] { didSet{ onChange() } } + var deviceMismatch: Set = [] { didSet{ onChange() } } + var signalStatus: String? { didSet{ onChange() } } + var otherErrors: Set = [] { didSet{ onChange() } } + var logs: Set = [] { didSet { onChange() } } + + //computed properties + var deviceInfo: String{ + let value = """ + deviceId: \(deviceId)\r + bluetoothState: \(bluetoothState.description)\r + bluetoothStatus: \(bluetoothStatus)\r + bluetoothPermissionDenied: \(isBluetoothPermissionDenied)\r + devicePaired: \(isDevicePaired)\r + deviceActivated: \(isDeviceActivated)\r + """ + return value + } + + var discoveryInfo: String? { + var value: [String] = [] + + if let deviceDiscovered = deviceDiscovered { + value.append("deviceDiscovered: \(deviceDiscovered)\r") + } + + if deviceDiscoveredNameFailed.isNotEmpty { + value.append("deviceDiscoveredNameFailed: \r\(deviceDiscoveredNameFailed.toString())") + } + + if deviceMismatch.isNotEmpty { + value.append("deviceMismatch: \(deviceMismatch.toString())") + } + + return value.isNotEmpty ? value.joined(separator: "") : nil + } + + var otherErrorsInfo: String? { + guard otherErrors.isNotEmpty else { return nil } + return "\rErrors Occurred:\r\(otherErrors.toString())" + } + + var logsInfo: String { + return "\rlogs:\r\(logs.toString())" + } + + var configInfo: String { + guard let string = config.toJSONString() else { + return "\rConfig:\rdecodeError" + } + return "\rConfig:\r\(string)" + } + + override var description: String { + var descriptions: [String] = [deviceInfo] + + //optional messages + if let discoveryInfo = discoveryInfo { + descriptions.append(discoveryInfo) + } + + if let firmwareInfo = firmwareInfo { + descriptions.append(firmwareInfo) + } + + if let signalStatus = signalStatus { + descriptions.append(signalStatus) + } + + if let publicInternetAccessStatusInfo = publicInternetAccessStatusInfo { + descriptions.append(publicInternetAccessStatusInfo) + } + + if let speedTestStatusInfo = speedTestStatusInfo { + descriptions.append(speedTestStatusInfo) + } + + if let routerWifiInfo = routerWifiInfo { + descriptions.append(routerWifiInfo) + } + + if let signalUpdateInfo = signalUpdateInfo { + descriptions.append(signalUpdateInfo) + } + + if let signalUpdateInfo = signalUpdateCBandInfo { + descriptions.append(signalUpdateInfo) + } + + if let otherErrorsInfo = otherErrorsInfo { + descriptions.append(otherErrorsInfo) + } + + descriptions.append(contentsOf: [logsInfo, configInfo]) + + return descriptions.joined(separator: "") + } + + required init(config: BluetoothConfigModel, delegate: BluetoothDebuggerDelegate?){ + self.deviceId = config.advertisedData + self.config = config + self.delegate = delegate + super.init() + self.onChange() + } + + //methods + func onChange(){ + delegate?.bluetoothDebuggerUpdate(message: description) + } + + func addLog(message: String){ + logs.insert(message) + } + + func addOtherError(error: String){ + otherErrors.insert(error) + } + + func addDiscoveredNameFailures(foundNames: [String]) { + foundNames.forEach { name in + deviceDiscoveredNameFailed.insert(name) + } + } + + func addDeviceIMEIMismatch(found: String) { + deviceMismatch.insert(found) + } + + func updateCurrentSignalStatus(_ status: MFFGHSSignalStatus, strengthCheckStatus: MFFGHSSignalStrengthCheckStatus, rssi: Double?) { + var rssiValue = "none" + if let rssi = rssi { + rssiValue = "\(rssi)" + } + signalStatus = "signalStatus: status: \(status) strengthCheck: \(strengthCheckStatus) rssi: \(rssiValue)\r" + } + + //Handler Delegates Properties + + //GMFGSpeedTestDelegate + var speedTestStatus: GMFGSpeedTestUpdate? { didSet { onChange() } } + var speedTestStatusInfo: String? { + guard let status = speedTestStatus else { return nil } + return "speedTestStatusInfo: \(status)\r" + } + + //GMFGRouterWifiDelegate + var routerWifiCredentials: GMFGRouterWifiInfo? { didSet { onChange() } } + var routerWifiStatus: GMFGRouterWifiStatus? { didSet { onChange() } } + var routerWifiInfo: String? { + var value = "" + if let routerWifiCredentials = routerWifiCredentials { + value = """ + routerWifiCredentials:\r + - ssid: \(routerWifiCredentials.ssid)\r + - password: \(routerWifiCredentials.password)\r + """ + } + if let routerWifiStatus = routerWifiStatus { + value = value + """ + routerWifiStatus: \(routerWifiStatus)\r + """ + } + return value + } + + //GMFGPublicInternetAccessDelegate + var publicInternetAccessStatus: GMPublicInternetAccessStatus? { didSet { onChange() } } + var publicInternetAccessStatusInfo: String? { + guard let status = publicInternetAccessStatus else { return nil } + return "publicInternetAccessStatus: \(status)\r" + } + + //GMFGFotaDelegate + var firmwareResponse: GMFotaResponse? { didSet { onChange() } } + var isCheckingFirmware: Bool? { didSet { onChange() } } + var firmwareInfo: String? { + var value: [String] = [] + if let isCheckingFirmware = isCheckingFirmware { + value.append("checkingFirmware: \(isCheckingFirmware)\r") + } + if let fota = firmwareResponse { + value.append(""" + firmwareStatus: \(fota.status)\r + firmwareVersion: \(fota.version)\r + firmwareError: \(fota.errorMsg ?? "none")\r + """) + } + return value.isNotEmpty ? value.joined(separator: "") : nil + } + + //GMFG5GSignalDelegate + var signalUpdate: GMFG5GSignalUpdate? { didSet { onChange() } } + var signalUpdateInfo: String? { + guard let signalUpdate = signalUpdate else { return nil } + return "\r5G Signal Update: \r\(signalUpdate)\r" + } + + //GMFG5GSignalDelegate + var signalUpdateCBand: GMFG5GCBandSignalUpdate? { didSet { onChange() } } + var signalUpdateCBandInfo: String? { + guard let signalUpdate = signalUpdateCBand, let results = signalUpdate.rawResults else { return nil } + return "\r5G C-Band Signal Update: \r\(results)\r" + } + +} + +//Handler Delegate Implementations +extension BluetoothDebugger: GMFGSpeedTestDelegate { + func didStartSpeedTest() { + //BLELog has this + } + + func didStopSpeedTest() { + //BLELog has this + } + + func speedTestStatus(status: GMFGSpeedTestUpdate) { + speedTestStatus = status + } +} + +extension BluetoothDebugger: GMFGRouterWifiDelegate { + func routerWifi(credentials: GMFGRouterWifiInfo) { + routerWifiCredentials = credentials + } + + func routerWifiStatus(status: GMFGRouterWifiStatus) { + routerWifiStatus = status + } +} + +extension BluetoothDebugger: GMFGPublicInternetAccessDelegate { + func publicInternetAccessStatus(status: GMPublicInternetAccessStatus) { + publicInternetAccessStatus = status + } +} + +extension BluetoothDebugger: GMFGFotaDelegate { + func fotaStatus(statusResponse: GMFotaResponse) { + firmwareResponse = statusResponse + } + + func unknownResponse(rawResponse: [String : Any]?) { + let response: String = rawResponse == nil ? "none" : "\(rawResponse!)" + addLog(message: "unknown response: \(response)") + } +} + +extension BluetoothDebugger: GMFG5GSignalDelegate { + + func updated5GSignal(_ update: GMFG5GSignalUpdate){ + signalUpdate = update + } + + func signalNotReady() { + signalUpdate = nil + addLog(message: "5G Signal Not Ready") + } + +} + +extension BluetoothDebugger: GMFG5GCBandSignalDelegate { + + func updated5GCBandSignal(_ update: GMFG5GCBandSignalUpdate){ + signalUpdateCBand = update + } + + func signal5GCBandNotReady() { + signalUpdateCBand = nil + addLog(message: "5G C-Band Signal Not Ready") + } +} + +extension CBManagerState: CustomStringConvertible { + public var description: String { + switch self { + case .unknown: + return "unknown" + case .resetting: + return "resetting" + case .unsupported: + return "unsupported" + case .unauthorized: + return "unauthorized" + case .poweredOff: + return "poweredOff" + case .poweredOn: + return "poweredOn" + @unknown default: + return "unknown" + } + } +} + +extension Collection { + fileprivate func toString() -> String { + return self.map{" - \($0)\r"}.joined(separator: "") + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Debugger/BluetoothDebuggerView.swift b/JSONCreator_iOS/JSONCreator/5G/Debugger/BluetoothDebuggerView.swift new file mode 100644 index 0000000..ece549e --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Debugger/BluetoothDebuggerView.swift @@ -0,0 +1,96 @@ +// +// BluetoothDebuggerView.swift +// MVM5G +// +// Created by Matt Bruce on 3/19/22. +// Copyright © 2022 Kyle. All rights reserved. +// + +import Foundation +import UIKit + +/// View Used to show on Pairing screens to act as a Console Log +class BluetoothDebuggerView: UIView { + + //console log view + var textView = UITextView() + + //text show in textView + var text: String = "Initial Page Load" { + didSet{ + textView.text = text + } + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupView() + } + + init() { + super.init(frame: .zero) + setupView() + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + private func setupView() { + translatesAutoresizingMaskIntoConstraints = false + + let doneButton = UIButton() + doneButton.setTitle("Done", for: .normal) + doneButton.addTarget(self, action: #selector(close), for: .touchUpInside) + doneButton.translatesAutoresizingMaskIntoConstraints = false + + let headerView = UIView() + headerView.translatesAutoresizingMaskIntoConstraints = false + headerView.backgroundColor = .mvmCoolGray6 + headerView.addSubview(doneButton) + doneButton.rightAnchor.constraint(equalTo: headerView.rightAnchor).isActive = true + doneButton.topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true + doneButton.heightAnchor.constraint(equalToConstant: 40).isActive = true + doneButton.widthAnchor.constraint(equalToConstant: 100).isActive = true + + textView.setContentCompressionResistancePriority(.required, for: .vertical) + textView.isUserInteractionEnabled = true + textView.showsVerticalScrollIndicator = true + textView.isEditable = false + textView.isScrollEnabled = true + textView.scrollRangeToVisible(NSMakeRange(0, 0)) + textView.setContentOffset(CGPoint(x: 0, y: 0), animated: false) + + textView.translatesAutoresizingMaskIntoConstraints = false + textView.backgroundColor = .white + textView.text = text + addSubview(headerView) + addSubview(textView) + + headerView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + headerView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true + headerView.topAnchor.constraint(equalTo: topAnchor).isActive = true + headerView.bottomAnchor.constraint(equalTo: textView.topAnchor).isActive = true + headerView.heightAnchor.constraint(equalToConstant: 40).isActive = true + + textView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + textView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true + textView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + textView.heightAnchor.constraint(equalToConstant: 400).isActive = true + } + + //removes self from View + @objc func close(sender: UIButton){ + removeFromSuperview() + } + +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Debugger/MulticastDelegate.swift b/JSONCreator_iOS/JSONCreator/5G/Debugger/MulticastDelegate.swift new file mode 100644 index 0000000..da63e83 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Debugger/MulticastDelegate.swift @@ -0,0 +1,56 @@ +// +// MulticastDelegate.swift +// MVM5G +// +// Created by Matt Bruce on 3/23/22. +// Copyright © 2022 Kyle. All rights reserved. +// + +import Foundation + +public class MulticastDelegate { + + // MARK: - Properties + private class Wrapper { + weak var delegate: AnyObject? + + init(_ delegate: AnyObject) { + self.delegate = delegate + } + } + + private var wrappers: [Wrapper] = [] + public var delegates: [T] { + return wrappers + .compactMap{ $0.delegate } as! [T] + } + + // MARK: - Actions + public func add(delegate: T) { + let wrapper = Wrapper(delegate as AnyObject) + wrappers.append(wrapper) + } + + public func add(delegates: [T?]){ + for case let delegate? in delegates { + add(delegate: delegate) + } + } + + public func remove(delegate: T) { + guard let index = wrappers.firstIndex(where: { + $0.delegate === (delegate as AnyObject) + }) else { + return + } + wrappers.remove(at: index) + } + + public func removeAll(){ + wrappers.removeAll() + } + + public func invokeForEachDelegate(_ handler: (T) -> ()) { + delegates.forEach { handler($0) } + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/KeyedDecodingContainer+Decode.swift b/JSONCreator_iOS/JSONCreator/5G/KeyedDecodingContainer+Decode.swift new file mode 100644 index 0000000..0c2fad6 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/KeyedDecodingContainer+Decode.swift @@ -0,0 +1,59 @@ +// +// KeyedDecodingContainer+Decode.swift +// MVM5G +// +// Created by Matt Bruce on 4/15/22. +// Copyright © 2022 Kyle. All rights reserved. +// + +import Foundation +import MVMCore + + +public enum KeyedDecodingContainerError: Error { + case decode(objectType: String, key: String, message: String) +} + +extension KeyedDecodingContainerError: LocalizedError, CustomStringConvertible { + public var description: String { + switch self { + case .decode(let objectType, let key, let message): + return "Decoding Error for object: \(objectType) on coding key: \(key) with error: \(message)" + } + } + + public var errorDescription: String? { + return self.description + } +} + +extension KeyedDecodingContainer { + public func decode(_ type: T.Type, + forKey key: Self.Key, + objectType: K.Type) throws -> T { + do { + return try self.decode(type, forKey: key) + } catch { + throw KeyedDecodingContainerError.decode(objectType: "\(objectType)", key: key.stringValue, message: error.localizedDescription) + } + } +} + +extension JSONError: LocalizedError, CustomStringConvertible { + public var description: String { + switch self { + case .pathNotFound: + return "JSON path not found" + case .data(path: let path): + return "JSON data in \(path) is corrupt" + case .other(error: let error): + return error.localizedDescription + case .error(message: let message): + return message + } + } + + public var errorDescription: String? { + return description + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/MFFGHSAnalyticsProtocol.swift b/JSONCreator_iOS/JSONCreator/5G/MFFGHSAnalyticsProtocol.swift new file mode 100644 index 0000000..a5ad26a --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/MFFGHSAnalyticsProtocol.swift @@ -0,0 +1,217 @@ +// +// MFFGHSAnalyticsProtocol.swift +// MVM5G +// +// Created by Gujuluva Santharam, Ajai Prabhu on 20/05/20. +// Copyright © 2020 Kyle. All rights reserved. +// +import Foundation +import UIKit +import MVMCore +import MVMCoreUI + +protocol MFFGHSAnalyticsProtocol { + func trackPage(data: [String: Any]?, additionalData: [String:Any]?) + func trackAction(action: [String: Any]?, pageData:[String: Any]?, additionalData: [String: Any]?) + func trackPageAppear(with pageJSON: [String: Any]?) + func trackPageDisappear(with pageJSON: [String: Any]?) + + /** true means it will stop sending data to server when view appear to reduce data load. Temporary bool, will remove next release */ + func stopInitialEvent() -> Bool +} + +enum AnalyticsLogType: String { + case UI = "UI" + case BLE = "BLE" +} + +struct AnalyticsPageInfo { + var pageType: String? +} + +extension MFFGHSAnalyticsProtocol { + + /** this boolean enable entire analytics feature. enable = false means nothing will send to server */ + private var enable: Bool { + let initialParams:[String: Any] = [:] + return !initialParams.boolForKey("isFGAnalyticsDisabled") + } + + /** enable time tracking feature. TODO - we are reducing call not sending data on start event. */ + private var enableTimeTracker: Bool { return true } + + /** true means it will stop sending data to server when view appear to reduce data load. Temporary bool, will remove next release */ + func stopInitialEvent() -> Bool { return false } + + func trackPage(data: [String: Any]?, additionalData:[String:Any]?) { + let params: [String: Any] = [ + "eventType" : additionalData?["eventType"] ?? "pageDisplay", + "pageType" : data?["pageType"] ?? "NA", + "template" : data?["template"] ?? "NA", + "eventName" : additionalData?["eventName"] ?? "start", + "pageClass" : additionalData?["pageClass"] ?? String(describing: Self.self), + "epochSec" : Int(Date().timeIntervalSince1970), + "tdn" : getVendorId(), + "os" : "iOS" + ] + let mergedParams = params.merging(additionalData ?? [:]) { $1 } + performRequest(params: ["data": mergedParams]) + } + + func trackAction(action: [String: Any]?, pageData:[String: Any]?, additionalData: [String: Any]?) { + /* + let params: [String: Any] = [ + "eventType" : "action", + "actionPageType": action?["pageType"] ?? "NA", + "actionTitle" : action?["title"] ?? "NA", + "pageType" : pageData?.stringForkey("pageType") ?? "NA", + "template" : pageData?.stringForkey("template") ?? "NA", + "epochSec" : Int(Date().timeIntervalSince1970) + ] + performRequest(params: params) + */ + } + + func trackGemini(value: [String: Any], logType: AnalyticsLogType, pageInfo: AnalyticsPageInfo? = nil) { + var result: [String: Any] = [ + "os" : "iOS", + "LogType" : logType.rawValue, + "tdn" : getVendorId() + ] + if let pageInfo = pageInfo { + result["pageType"] = pageInfo.pageType + } + let payload = result.merging(value) { $1 } + performRequest(params: ["data": payload]) + } + + func trackPKI(processStep: String, attributeList: GMFGAnalyticsAttributeList?) { + var payload: [String: Any] = [ + "processStep" : processStep, + "os" : "iOS", + "tdn" : getVendorId() + ] + if let attributeList = attributeList { + payload["attributeList"] = attributeList.getList() + //GlassboxManager.glassboxCustomEvent(forKey: GMFGConstant.Glassbox.fivegSetup, withParameters: ["gen3Data": attributeList.getList()]) + } + performRequest(params: payload, forPageType: "gen3Data") + } + + func trackGen3Data(processStep: String, attributeList: GMFGAnalyticsAttributeList?) { + var payload: [String: Any] = [ + "processStep" : processStep, + "os" : "iOS", + "tdn" : getVendorId() + ] + if let attributeList = attributeList { + payload["attributeList"] = attributeList.getList() + } + performRequest(params: payload, forPageType: "gen3Data", priority: .high) + } + + func resetGMAnalytics() { + LogRequestSerialiser.shared.resetTimer() + } + + // MARK:- Get the Device UUID + func getVendorId() -> String { + let uuid = UIDevice.current.identifierForVendor?.uuidString + return uuid ?? GMFGConstant.empty + } + + private func performRequest(params: [String: Any], forPageType: String = "logUIData", priority: LogPriority = .normal) { + if enable { + LogRequestSerialiser.shared.queueLogRequest(params: params, forPageType: forPageType, priority: priority) + } + } + + // MARK:- Public API - Time Tracker + + func trackPageAppear(with pageJSON: [String: Any]?) { + if !enableTimeTracker { return } + //GMFGSelfInstallTimeTracker.shared.trackPageAppear(with: pageJSON) + } + + func trackPageDisappear(with pageJSON: [String: Any]?) { + if !enableTimeTracker { return } + //GMFGSelfInstallTimeTracker.shared.trackPageDisappear(with: pageJSON) + } +} + +final class LogRequestSerialiser { + + static let shared: LogRequestSerialiser = LogRequestSerialiser() + private lazy var requestQueue: [RequestItem] = [] + private lazy var timer: Timer? = nil + private init() { /* this avoid creating new instance */ } + private var index = 0 + + fileprivate func queueLogRequest(params: [String: Any], forPageType: String, priority: LogPriority) { + let requestItem = RequestItem(priority: priority, endpoint: forPageType, parameters: params) + requestQueue.append(requestItem) + startTimer() + } + + @objc fileprivate func performRequestIfAvailable() { + if requestQueue.count > index { + let objectToBePerformed = requestQueue[index] + if let reqParams = MVMCoreRequestParameters(pageType: objectToBePerformed.endpoint, extraParameters: nil) { + index = index + 1 + reqParams.add(objectToBePerformed.parameters) + MVMCoreLoadHandler.sharedGlobal()?.loadBackgroundRequest(reqParams, dataForPage: nil, delegateObject: nil) + } + } + if requestQueue.count <= index { resetTimer() } + } + + fileprivate func resetTimer() { + timer?.invalidate() + timer = nil + index = 0 + requestQueue.removeAll() + } + + fileprivate func startTimer() { + if timer == nil { + DispatchQueue.main.async { + self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.performRequestIfAvailable), userInfo: nil, repeats: true) + self.timer?.tolerance = 0.5 + } + } + } +} + +fileprivate struct RequestItem { + var priority: LogPriority + var endpoint: String + var parameters: [String: Any] +} + +fileprivate enum LogPriority { + case high + case normal +} + +struct GMFGAnalyticsAttributeList { + private var attrList: [[String: Any]] = [] + + @available(*, deprecated, message: "use add(name:, value:)") + mutating func addAttribute(name: String, value: Any) { + attrList.append([ + "attributeName": name, + "attributeValue": value + ]) + } + + mutating func add(_ name: String, _ value: Any) { + attrList.append([ + "attributeName": name, + "attributeValue": value + ]) + } + + func getList() -> [[String: Any]] { + return attrList + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/MFFGHSBluetoothPair.swift b/JSONCreator_iOS/JSONCreator/5G/MFFGHSBluetoothPair.swift new file mode 100644 index 0000000..afc59b4 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/MFFGHSBluetoothPair.swift @@ -0,0 +1,547 @@ +// +// MFFGHSBluetoothPair.swift +// MobileFirstFramework +// +// Created by Chowdhury, Shohrab on 3/29/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import CoreBluetooth + +public enum MFFGHSSignalStatus: NSNumber { + case noBluetoothConnection = -1, noSignal = 1, poor, good, confirmedGood, confirmedPoor +} + +public enum MFFGHSSignalStrengthCheckStatus: NSNumber { + case idle = 0, processing, complete +} + +public protocol MFFGHSBluetoothTestingProtocol { + var continueTesting: Bool { get set } +} + +public protocol MFFGHSBluetoothPairDelegate : NSObjectProtocol { + + /// Receive ble status ON/OFF + func bluetoothStatus(isOn: Bool) + + /// New status that's more reliable as a callback if BLE is off + func bluetoothOff() + + /// Bluetooth does not have access from the user + func bluetoothPermissionsDenied() + + /// BLE found a device that doesn't match what it expects to find. + func deviceIMEIMismatch(expected: String, found: String) + + /// An attempted pairing failed due to a broadcast naming mismatch. + func onBluetoothDiscoveredNameFailed(expectedName: String, foundNames: [String]) + + /// The bluetooth service was discovered. + func onBluetoothDiscovered(expectedName: String, foundName: String) + + /// Check device is paired with phone + func pairUpdate(isPaired: Bool) + + /// Check device is activated or not + func updateActivatedStatus(isActivated: Bool) + + /// Receive current RSSI + func updateCurrentSignalStatus(_ status: MFFGHSSignalStatus, strengthCheckStatus: MFFGHSSignalStrengthCheckStatus, rssi: Double?) + +} + +extension MFFGHSBluetoothPairDelegate { + public func bluetoothStatus(isOn: Bool) {} + public func bluetoothOff() {} + public func bluetoothPermissionsDenied() {} + public func deviceIMEIMismatch(expected: String, found: String) {} + public func onBluetoothDiscoveredNameFailed(expectedName: String, foundNames: [String]) {} + public func onBluetoothDiscovered(expectedName: String, foundName: String) {} + public func pairUpdate(isPaired: Bool) {} + public func updateActivatedStatus(isActivated:Bool) {} + public func updateCurrentSignalStatus(_ status: MFFGHSSignalStatus, strengthCheckStatus: MFFGHSSignalStrengthCheckStatus, rssi: Double?) {} +} + +class MFFGHSBluetoothPair: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, BluetoothParingProtocol { + + // !!Major change!! Some controllers are relying on bluetooth object being passed. + static weak var instance:MFFGHSBluetoothPair? + + /// Weak instance returned. Controller that uses it should hold onto strong reference. When all controllers deallocate, this will also. + static var sharedInstance: MFFGHSBluetoothPair { + get { + if let instance = instance { + return instance + } else { + let newInstance = MFFGHSBluetoothPair() + instance = newInstance + return newInstance + } + } + } + + // Bluetooth control objects. + var centralManager: CBCentralManager? + var peripheral: CBPeripheral? + + // Event delegate. + weak var delegate: MFFGHSBluetoothPairDelegate? + + //debugger for event delegate + var bluetoothDebugger: BluetoothDebugger? + + // Data from bluetooth connection + var isPaired = false + var isCPEActivated = false + var currentSignalStatus: MFFGHSSignalStatus = .noBluetoothConnection + var currentRSSIValue: Double? + + // Configured properties. + var bluetoothConfig: BluetoothConfigModel? { + didSet { + if (oldValue != bluetoothConfig) { // TODO: Needs deep check for config changes. + setupBluetoothScanner() + } + } + } + + var signalStrengthObserveDuration: TimeInterval = 30 + var signalPassingPercentage: Double = 100 + var signalStrengthCheckStatus: MFFGHSSignalStrengthCheckStatus = .idle + var signalStrengthObserverTimer: Timer? + var lastSignalStatusTimeStamp: Date? + var signalStrengthGoodTotalTime: Double = 0 + var signalStrengthFailTotalTime: Double = 0 + + override init() { + super.init() + setupBluetoothScanner() + } + + #if DEBUG + deinit { + print("bluetooth destroy") + } + #endif + + var bluetoothEnabled:Bool { + #if targetEnvironment(simulator) + return true + #else + return self.centralManager?.state == CBManagerState.poweredOn + #endif + } + + func scanForPeripherals(central:CBCentralManager) { + let advertiseIds = getBleAdvertiseUUIDs() + if advertiseIds.count > 0 { + central.scanForPeripherals(withServices: advertiseIds, options: nil) + } else { + central.stopScan() + } + } + + func setupBluetoothScanner(showPowerAlert:Bool = false) { + // Reset connection on new config. + if let peripheral = self.peripheral { + centralManager?.cancelPeripheralConnection(peripheral) + self.peripheral = nil + self.isPaired = false + self.currentSignalStatus = .noBluetoothConnection + } + if self.centralManager == nil || showPowerAlert { + centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main, options: [CBCentralManagerOptionShowPowerAlertKey: showPowerAlert]) + } else if (bluetoothEnabled) { + scanForPeripherals(central: centralManager!) + } // else wait for powered on status + + if GMFGTestScreenData.shared.isCPESimulated { + #if DEBUG + simulateBluetoothMessaging() + #endif + } else { + #if DEBUG && targetEnvironment(simulator) + simulateBluetoothMessaging() + #endif + } + } + + #if DEBUG + // For simulating RSSI changes. + var timer: Timer? + private func simulateBluetoothMessaging() { + // If we don't get advertisement data, technically nothing will come. + if getBleAdvertiseUUIDs().count == 0 { return } + + if timer != nil { return } + + var signalRotate = 0 + _ = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { (timer) in + self.delegate?.bluetoothStatus(isOn: true) + self.isCPEActivated = true + self.delegate?.updateActivatedStatus(isActivated: self.isCPEActivated) + self.isPaired = true + self.delegate?.pairUpdate(isPaired: self.isPaired) + signalRotate = (signalRotate + 1) % 5 + self.currentRSSIValue = Double(signalRotate) + if (signalRotate >= 3) { + self.currentRSSIValue = -76 + self.currentSignalStatus = .confirmedGood + } else if (signalRotate >= 2) { + self.currentRSSIValue = -86 + self.currentSignalStatus = .good + } else if (signalRotate >= 1) { + self.currentRSSIValue = -101 + self.currentSignalStatus = .poor + } else { + self.currentRSSIValue = -999 + self.currentSignalStatus = .noSignal + } + + self.delegate?.updateCurrentSignalStatus(self.currentSignalStatus, strengthCheckStatus:self.signalStrengthCheckStatus, rssi: self.currentRSSIValue) + self.bluetoothDebugger?.bluetoothStatus = true + self.bluetoothDebugger?.isDeviceActivated = true + self.bluetoothDebugger?.isDevicePaired = self.isPaired + self.bluetoothDebugger?.updateCurrentSignalStatus(self.currentSignalStatus, strengthCheckStatus:self.signalStrengthCheckStatus, rssi: self.currentRSSIValue) + } + } + #endif + + // Should be used when we detect scanning is no longer required. + private func stopScanning() { + if let peripheral = self.peripheral { + centralManager?.cancelPeripheralConnection(peripheral) + self.peripheral = nil; + } + centralManager?.stopScan() + centralManager = nil + } + + open func shutdown() { + stopScanning() + MFFGHSBluetoothPair.instance = nil + } + + public func stopNotify5G(controller: MFFGHSBluetoothPairDelegate) { + if let del = delegate, del === controller { + delegate = nil + } + resetSignalStrengthCheckValue() + } + + public func setup(config: BluetoothConfigModel, delegate: BluetoothDebuggerDelegate) { + bluetoothDebugger = BluetoothDebugger(config: config, delegate: delegate) + bluetoothConfig = config + } + + public func startNotify5G(controller: MFFGHSBluetoothPairDelegate) { + delegate = controller + // Initialize + delegate?.pairUpdate(isPaired: isPaired) + delegate?.updateActivatedStatus(isActivated: isCPEActivated) + delegate?.updateCurrentSignalStatus(currentSignalStatus, strengthCheckStatus: signalStrengthCheckStatus, rssi: currentRSSIValue) + + if let state = centralManager?.state { + bluetoothDebugger?.bluetoothState = state + } + bluetoothDebugger?.bluetoothStatus = bluetoothEnabled + bluetoothDebugger?.isDevicePaired = isPaired + bluetoothDebugger?.isDeviceActivated = isCPEActivated + bluetoothDebugger?.updateCurrentSignalStatus(currentSignalStatus, strengthCheckStatus:signalStrengthCheckStatus, rssi: currentRSSIValue) + } + + // MARK:- Bluetooth delegate + + public func centralManagerDidUpdateState(_ central: CBCentralManager) { + bluetoothDebugger?.bluetoothState = central.state + switch (central.state) { + case .poweredOn: + delegate?.bluetoothStatus(isOn: true) + bluetoothDebugger?.bluetoothStatus = true + scanForPeripherals(central: central) + case .poweredOff, .unsupported, .unauthorized, .unknown: + delegate?.bluetoothStatus(isOn: false) + bluetoothDebugger?.bluetoothStatus = false + default: + print("bluetooth not powered on") + } + } + + public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { + if findPeripheralName(getBleAdvertiseName(), in: advertisementData) { + self.peripheral = peripheral + peripheral.delegate = self + central.connect(peripheral, options: nil) + central.stopScan() + } else { + if let foundName = advertisementData[CBAdvertisementDataLocalNameKey] as? String, foundName.hasPrefix(GMFGConstant.BLE.advertisePrefix) { + delegate?.deviceIMEIMismatch(expected: getBleAdvertiseName(), found: foundName) + bluetoothDebugger?.addDeviceIMEIMismatch(found: foundName) + } + } + } + + private func findPeripheralName(_ name: String, in advertisementData: [String : Any]) -> Bool { + var found = false + var foundAdvertisedNames: [String] = [] + + // Check the local data name. (Where it should be. Used for testing app.) + if let localServiceName = advertisementData[CBAdvertisementDataLocalNameKey] as? String { + if localServiceName == name { + found = true + } else { + foundAdvertisedNames.append(localServiceName) + } + } + // Check the service data. (Where it is actually implemented by CPE.) + if !found, let avdService = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID:Data] { + for key in avdService.keys { + if let rawName = avdService[key], + let advertisedName = String(bytes: rawName, encoding: .utf8) { + if (advertisedName == name) { + found = true + break + } else { + foundAdvertisedNames.append(advertisedName) + } + } + } + } + + if found { + // Currently exact equality passes. Later can adjust to be actual advertised name if the requirement relaxes. + delegate?.onBluetoothDiscovered(expectedName: name, foundName: name) + bluetoothDebugger?.deviceDiscovered = name + let value: [String : Any] = [ + "blePairStatus": "discovered", + "expectedBroadcastName": name, + "foundBroadcastName": name + ] + track(value) + + return true; + } else { + delegate?.onBluetoothDiscoveredNameFailed(expectedName: name, foundNames: foundAdvertisedNames) + bluetoothDebugger?.addDiscoveredNameFailures(foundNames: foundAdvertisedNames) + let value: [String : Any] = [ + "blePairStatus": "mismatch", + "expectedBroadcastName": name, + "foundBroadcastName": foundAdvertisedNames.joined(separator: " / ").prefix(500) + ] + track(value) + + return false + } + } + + public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + isPaired = true + delegate?.pairUpdate(isPaired: true) + bluetoothDebugger?.isDevicePaired = true + peripheral.discoverServices(getBleAdvertiseUUIDs()) // For testing app which can only advertise on 1 service. + peripheral.discoverServices(getSeviceUUIDs()) + } + + public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { + for service in peripheral.services ?? [] { + peripheral.discoverCharacteristics(getCharacteristicUUIDs(serviceId: service.uuid), for: service) + } + } + + public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { + for characteristic in service.characteristics ?? [] { + peripheral.setNotifyValue(true, for: characteristic) + } + } + + public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { + + let cpeResponse = formatCharacteristicData(characteristic) + + handleActivated(response: cpeResponse) + + handleFiveGSignal(response: cpeResponse) + } + + public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { } + + public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { + // We only connect to 1 peripheral. Using that assumption below. + isPaired = false + currentSignalStatus = .noBluetoothConnection + currentRSSIValue = nil + delegate?.pairUpdate(isPaired: false) + delegate?.updateCurrentSignalStatus(currentSignalStatus, strengthCheckStatus: signalStrengthCheckStatus, rssi: currentRSSIValue) + bluetoothDebugger?.isDevicePaired = false + bluetoothDebugger?.updateCurrentSignalStatus(currentSignalStatus, strengthCheckStatus: signalStrengthCheckStatus, rssi: currentRSSIValue) + self.peripheral = nil + scanForPeripherals(central: central) + } + + func formatCharacteristicData(_ characteristic: CBCharacteristic) -> [String: Any] { + guard + let advertiseData = characteristic.value, !advertiseData.isEmpty, + let advertiseDict = try? JSONSerialization.jsonObject(with: advertiseData, options: []) as? [String: Any] + else { + return [:] + } + return advertiseDict + } + + // MARK: Handle Activation + func handleActivated(response: [String: Any]) { + if let activation = response["Activation"] as? [String: Any] { + isCPEActivated = activation.stringForkey("Status") == "Activated" + delegate?.updateActivatedStatus(isActivated: isCPEActivated) + bluetoothDebugger?.isDeviceActivated = isCPEActivated + } + } + + // MARK: Handle FiveGSignal + func handleFiveGSignal(response: [String: Any]) { + if let fivegSignal = response["5GSignal"] as? [String: Any] { + if let rssiValue = parseRSRPFromJSON(fiveGData: fivegSignal) { + currentRSSIValue = rssiValue + + // This need to call before currentSignalStatus set with new status. This is to gather the amount of time on the previous signal status before the update. + if signalStrengthCheckStatus == .processing { + //lastSignalStatusTimeStamp = lastSignalStatusTimeStamp != nil ? lastSignalStatusTimeStamp : Date() + checkSignalStrengthContinueRequired() + } + + if GMFGTestScreenData.shared.isEnable5GSignal { + currentSignalStatus = currentSignalStatus == .confirmedGood ? .confirmedGood : .good + } else { + if rssiValue >= higherThreshold || rssiValue <= lowerThreshold { + // Future reference: -999 means no signal from CPE. Need to make sure it will always be included in check. + // Invalid value. We should not be above the upper limits or beneath the lower limits. + currentSignalStatus = .noSignal + } else if rssiValue >= rssiThreshold { + currentSignalStatus = currentSignalStatus == .confirmedGood ? .confirmedGood : .good + } else { + currentSignalStatus = .poor + } + } + } else { + currentSignalStatus = .noSignal // Invalid value or doesn't exist. + } + + delegate?.updateCurrentSignalStatus(currentSignalStatus, strengthCheckStatus: signalStrengthCheckStatus, rssi: currentRSSIValue) + bluetoothDebugger?.updateCurrentSignalStatus(currentSignalStatus, strengthCheckStatus: signalStrengthCheckStatus, rssi: currentRSSIValue) + } + } + + // MARK:- Handle Signal History + + private func resetSignalStrengthCheckValue() { + signalStrengthCheckStatus = .idle + signalStrengthObserverTimer?.invalidate() + signalStrengthObserverTimer = nil + lastSignalStatusTimeStamp = nil + signalStrengthGoodTotalTime = 0 + signalStrengthFailTotalTime = 0 + } + + func checkSignalStrengthContinueRequired() { + let lastTrackDate = lastSignalStatusTimeStamp ?? Date() + if [.good, .confirmedGood].contains(currentSignalStatus) { + signalStrengthGoodTotalTime += Date().timeIntervalSince(lastTrackDate) + } else { + signalStrengthFailTotalTime += Date().timeIntervalSince(lastTrackDate) + } + lastSignalStatusTimeStamp = Date() + evaluateSignalStrength() + } + + private func evaluateSignalStrength() { + let maxFailPercentage = 100 - signalPassingPercentage + let maxFailTime = (maxFailPercentage / 100) * signalStrengthObserveDuration + let maxPassTime = (signalPassingPercentage / 100) * signalStrengthObserveDuration + + #if DEBUG + print("[ MFFGHSBluetoothPair ] Good Signal Time >>>> \(signalStrengthGoodTotalTime)") + print("[ MFFGHSBluetoothPair ] Bad Signal Time >>>> \(signalStrengthFailTotalTime)") + #endif + + if signalStrengthFailTotalTime > maxFailTime { + // already Fail, stop testing + resetSignalStrengthCheckValue() + signalStrengthCheckStatus = .complete + currentSignalStatus = .confirmedPoor + delegate?.updateCurrentSignalStatus(currentSignalStatus, strengthCheckStatus: signalStrengthCheckStatus, rssi: currentRSSIValue) + bluetoothDebugger?.updateCurrentSignalStatus(currentSignalStatus, strengthCheckStatus: signalStrengthCheckStatus, rssi: currentRSSIValue) + } else if signalStrengthGoodTotalTime >= maxPassTime || signalStrengthCheckStatus == .complete { + // already PASS. stop testing and handle + resetSignalStrengthCheckValue() + signalStrengthCheckStatus = .complete + currentSignalStatus = .confirmedGood + delegate?.updateCurrentSignalStatus(currentSignalStatus, strengthCheckStatus: signalStrengthCheckStatus, rssi: currentRSSIValue) + bluetoothDebugger?.updateCurrentSignalStatus(currentSignalStatus, strengthCheckStatus: signalStrengthCheckStatus, rssi: currentRSSIValue) + } else { + //contiue + } + } + + public func startSignalStrengthCheckValue(duration: TimeInterval, percentage: Double){ + if signalStrengthCheckStatus != .processing { + resetSignalStrengthCheckValue() + signalStrengthObserveDuration = duration + signalPassingPercentage = percentage + if currentSignalStatus == .confirmedGood { + currentSignalStatus = .good + } + signalStrengthCheckStatus = .processing + lastSignalStatusTimeStamp = Date() + signalStrengthObserverTimer = Timer(timeInterval: (signalStrengthObserveDuration + 0.3), repeats: false) { (timer) in + #if DEBUG + print("[ MFFGHSBluetoothPair ] Signal Test Timeout") + #endif + self.signalStrengthCheckStatus = .complete + self.checkSignalStrengthContinueRequired() + } + RunLoop.main.add(signalStrengthObserverTimer!, forMode: .common) + } + } + + // MARK:- Utility Functions + func parseRSRPFromJSON(fiveGData: [String: Any]) -> Double? { + return fiveGData["SS-RSRP"] as? Double ?? nil + } + + // MARK:- Analytics + func track(_ data: [String: Any]) { + if let _delegate = delegate { + trackGemini(value: data.merging(["delegateName": String(describing: _delegate)]) { $1 }, logType: .BLE) + } + } + + // MARK: BluetoothConfigModel methods + var rssiThreshold: Double { + return bluetoothConfig?.bleSignalThreshold ?? 0 + } + + var lowerThreshold: Double { + return bluetoothConfig?.bleSignalLowerBound ?? 0 + } + + var higherThreshold: Double { + return bluetoothConfig?.bleSignalUpperBound ?? 0 + } + + func getBleAdvertiseUUIDs() -> [CBUUID] { + return bluetoothConfig?.peripherals.map{ $0.uuid } ?? [] + } + + open func getBleAdvertiseName() -> String { + return bluetoothConfig?.advertisedData ?? "" + } + + func getSeviceUUIDs() -> [CBUUID] { + return bluetoothConfig?.services.map { $0.uuid } ?? [] + } + + func getCharacteristicUUIDs(serviceId: CBUUID) -> [CBUUID] { + return bluetoothConfig?.services.first?.characteristics.map { $0.uuid } ?? [] + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/MFFGHSUtility.swift b/JSONCreator_iOS/JSONCreator/5G/MFFGHSUtility.swift new file mode 100644 index 0000000..8415ee2 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/MFFGHSUtility.swift @@ -0,0 +1,17 @@ +import MVMCore +import MVMCoreUI + +extension ViewController { + public func fivegBLEConfig() throws -> BluetoothConfigModel { + guard let json = loadObject?.modulesJSON?.dictionaryWithChainOfKeysOrIndexes(["fivegBleUuid", "fivegBleUuid"]) else { + throw BluetoothConfigError.noJSONFound + } + return try BluetoothConfigModel.decode(jsonDict: json, delegateObject: nil) + } +} + +extension Collection { + var isNotEmpty: Bool { + return !isEmpty + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Models/BluetoothConfigModel.swift b/JSONCreator_iOS/JSONCreator/5G/Models/BluetoothConfigModel.swift new file mode 100644 index 0000000..3fdcd80 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Models/BluetoothConfigModel.swift @@ -0,0 +1,103 @@ +// +// BluetoothConfig.swift +// MVM5G +// +// Created by Matt Bruce on 4/13/22. +// Copyright © 2022 Kyle. All rights reserved. +// + +import Foundation +import CoreBluetooth +import MVMCore + +public enum BluetoothConfigError: Error, LocalizedError, CustomStringConvertible { + case decode(key: String) + case noJSONFound + + public var description: String { + switch self { + case .decode: + return GMFGConstant.BLE.LogMsg.bleDecodeError + case .noJSONFound: + return GMFGConstant.BLE.LogMsg.bleModuleMissing + } + } + + public var errorDescription: String? { + return description + } +} + +public struct BluetoothConfigModel: Codable, Equatable { + var advertisedData: String + var pin: String + var bleAPIVersion: Double + var bleSignalThreshold: Double + var bleSignalLowerBound: Double + var bleSignalUpperBound: Double + var peripherals: [PeripheralModel] + var services: [ServiceModel] + var logger: ((String, String, [String: Any])-> Void)? + + enum CodingKeys: String, CodingKey { + case bleAPIVersion + case pin + case bleSignalThreshold + case bleSignalLowerBound + case bleSignalUpperBound + case advertisedData + case bleAdv + case service + } + + public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + let _bleAPIVersion = try typeContainer.decode(String.self, forKey: .bleAPIVersion, objectType: BluetoothConfigModel.self) + let _bleSignalThreshold = try typeContainer.decode(String.self, forKey: .bleSignalThreshold, objectType: BluetoothConfigModel.self) + let _bleSignalLowerBound = try typeContainer.decode(String.self, forKey: .bleSignalLowerBound, objectType: BluetoothConfigModel.self) + let _bleSignalUpperBound = try typeContainer.decode(String.self, forKey: .bleSignalUpperBound, objectType: BluetoothConfigModel.self) + + guard let double = Double(_bleAPIVersion) else { + throw BluetoothConfigError.decode(key: "bleAPIVersion: \(_bleAPIVersion)") + } + bleAPIVersion = double + + guard let double = Double(_bleSignalThreshold) else { + throw BluetoothConfigError.decode(key: "bleSignalThreshold: \(_bleSignalThreshold)") + } + bleSignalThreshold = double + + guard let double = Double(_bleSignalLowerBound) else { + throw BluetoothConfigError.decode(key: "bleSignalLowerBound: \(_bleSignalLowerBound)") + } + bleSignalLowerBound = double + + guard let double = Double(_bleSignalUpperBound) else { + throw BluetoothConfigError.decode(key: "bleSignalUpperBound: \(_bleSignalUpperBound)") + } + bleSignalUpperBound = double + pin = try typeContainer.decode(String.self, forKey: .pin, objectType: BluetoothConfigModel.self) + advertisedData = try typeContainer.decode(String.self, forKey: .advertisedData, objectType: BluetoothConfigModel.self) + peripherals = try typeContainer.decode([PeripheralModel].self, forKey: .bleAdv, objectType: BluetoothConfigModel.self) + services = try typeContainer.decode([ServiceModel].self, forKey: .service, objectType: BluetoothConfigModel.self) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(bleAPIVersion, forKey: .bleAPIVersion) + try container.encode(pin, forKey: .pin) + try container.encode(bleSignalThreshold, forKey: .bleSignalThreshold) + try container.encode(bleSignalLowerBound, forKey: .bleSignalLowerBound) + try container.encode(bleSignalUpperBound, forKey: .bleSignalUpperBound) + try container.encode(advertisedData, forKey: .advertisedData) + try container.encode(peripherals, forKey: .bleAdv) + try container.encode(services, forKey: .service) + } + + public static func == (lhs: BluetoothConfigModel, rhs: BluetoothConfigModel) -> Bool { + return lhs.advertisedData == rhs.advertisedData && + lhs.pin == rhs.pin && + lhs.peripherals == rhs.peripherals && + lhs.services == rhs.services + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Models/CharacteristicModel.swift b/JSONCreator_iOS/JSONCreator/5G/Models/CharacteristicModel.swift new file mode 100644 index 0000000..c1a24cb --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Models/CharacteristicModel.swift @@ -0,0 +1,44 @@ +// +// Characteristic.swift +// MVM5G +// +// Created by Matt Bruce on 4/13/22. +// Copyright © 2022 Kyle. All rights reserved. +// + +import Foundation +import CoreBluetooth + +protocol CBUUIDIdentifiable { + var uuid: CBUUID { get set} +} + +public enum CharacteristicType: String { + case speedTest = "RouterSpeedTest" + case speedTestResults = "RouterSpeedTestResults" + case internetAccess = "RouterPublicInternetAccess" + case operationMode = "ReceiverMode" + case fotaCheck = "FOTACheck" + case fotaStatus = "FOTAStatus" + case fourGSignal = "Fourg" + case fiveGSignal = "Fiveg" + case fiveGCBandSignal = "FivegCBand" + case routerWiFi = "RouterWifi" + case routerMode = "RouterMode" + case repeaterOperations = "RepeaterOperations" + case repeaterPairStatus = "RepeaterPairStatus" + case repeaterMode = "RepeaterMode" + case repeaterPair = "RepeaterPair" + case radiocontrol = "Radiocontrol" + case activation = "Activation" + case unknown = "unknown" +} + +public struct CharacteristicModel: CBUUIDIdentifiable, Equatable{ + var uuid: CBUUID + var name: CharacteristicType + + public static func == (lhs: CharacteristicModel, rhs: CharacteristicModel) -> Bool { + return lhs.uuid == rhs.uuid && lhs.name == rhs.name + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Models/PeripheralModel.swift b/JSONCreator_iOS/JSONCreator/5G/Models/PeripheralModel.swift new file mode 100644 index 0000000..c24e801 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Models/PeripheralModel.swift @@ -0,0 +1,34 @@ +// +// Peripheral.swift +// MVM5G +// +// Created by Matt Bruce on 4/13/22. +// Copyright © 2022 Kyle. All rights reserved. +// + +import Foundation +import CoreBluetooth + +public struct PeripheralModel: CBUUIDIdentifiable, Codable, Equatable { + var uuid: CBUUID + + enum CodingKeys: String, CodingKey { + case uuid + } + + public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + let _uuid = try typeContainer.decode(String.self, forKey: .uuid) + uuid = CBUUID(string: _uuid) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(uuid.uuidString, forKey: .uuid) + } + + public static func == (lhs: PeripheralModel, rhs: PeripheralModel) -> Bool { + return lhs.uuid == rhs.uuid + } + +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Models/ServiceModel.swift b/JSONCreator_iOS/JSONCreator/5G/Models/ServiceModel.swift new file mode 100644 index 0000000..4161b3a --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Models/ServiceModel.swift @@ -0,0 +1,44 @@ +// +// Service.swift +// MVM5G +// +// Created by Matt Bruce on 4/13/22. +// Copyright © 2022 Kyle. All rights reserved. +// + +import Foundation +import CoreBluetooth + +public struct ServiceModel: CBUUIDIdentifiable, Codable, Equatable { + var uuid: CBUUID + var characteristics: [CharacteristicModel] + + enum CodingKeys: String, CodingKey { + case uuid + case characteristicUUID + } + + public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + let _uuid = try typeContainer.decode(String.self, forKey: .uuid) + uuid = CBUUID(string: _uuid) + let _characteristics = try typeContainer.decode([String: String].self, forKey: .characteristicUUID) + characteristics = _characteristics.map { key, value in + return CharacteristicModel(uuid: CBUUID(string: value), name: CharacteristicType(rawValue: key) ?? .unknown) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(uuid.uuidString, forKey: .uuid) + var _characteristics: [String: String] = [:] + characteristics.forEach { c in + _characteristics[c.name.rawValue] = c.uuid.uuidString + } + try container.encode(_characteristics, forKey: .characteristicUUID) + } + + public static func == (lhs: ServiceModel, rhs: ServiceModel) -> Bool { + return lhs.uuid == rhs.uuid && lhs.characteristics == rhs.characteristics + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Protocols/BluetoothPairableProtocol.swift b/JSONCreator_iOS/JSONCreator/5G/Protocols/BluetoothPairableProtocol.swift new file mode 100644 index 0000000..988e6e5 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Protocols/BluetoothPairableProtocol.swift @@ -0,0 +1,65 @@ +// +// BluetoothPairableProtocol.swift +// MVM5G +// +// Created by Matt Bruce on 4/15/22. +// Copyright © 2022 Kyle. All rights reserved. +// + +import Foundation +import MVMCore +import MVMCoreUI + +/// This Protocol will be implemented by UI +protocol BluetoothPairableProtocol: AnyObject { + associatedtype BluetoothPairingType: BluetoothParingProtocol + var bluetoothPair: BluetoothPairingType? { get set } + func setBluetoothConfig() -> Bool + func track(_ message: Any, logType: AnalyticsLogType) +} + +extension BluetoothPairableProtocol where Self: ViewController, Self: MFFGHSAnalyticsProtocol { + @discardableResult + func setBluetoothConfig() -> Bool { + do { + let config = try fivegBLEConfig() + bluetoothPair?.bluetoothConfig = config + return true + } catch { + track(error.localizedDescription) + return false + } + } + + func track(_ message: Any, logType: AnalyticsLogType = .UI) { + if let message = message as? String { + trackGemini(value: [KeyMessage: message], logType: logType, pageInfo: AnalyticsPageInfo(pageType: loadObject?.pageType)) + } else if let dict = message as? [String: Any] { + trackGemini(value: dict, logType: logType, pageInfo: AnalyticsPageInfo(pageType: loadObject?.pageType)) + } + } +} + +extension BluetoothPairableProtocol where Self: ViewController, Self: MFFGHSAnalyticsProtocol, Self: BluetoothDebuggableProtocol { + @discardableResult + func setBluetoothConfig() -> Bool { + do { + let config = try fivegBLEConfig() + bluetoothPair?.setup(config: config, delegate: self) + return true + } catch { + track(error.localizedDescription) + return false + } + } + + func track(_ message: Any, logType: AnalyticsLogType = .UI) { + if let message = message as? String { + bluetoothDebugger?.addLog(message: message) + trackGemini(value: [KeyMessage: message], logType: logType, pageInfo: AnalyticsPageInfo(pageType: loadObject?.pageType)) + } else if let dict = message as? [String: Any] { + bluetoothDebugger?.addLog(message: dict.description) + trackGemini(value: dict, logType: logType, pageInfo: AnalyticsPageInfo(pageType: loadObject?.pageType)) + } + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Protocols/BluetoothPairingProtocol.swift b/JSONCreator_iOS/JSONCreator/5G/Protocols/BluetoothPairingProtocol.swift new file mode 100644 index 0000000..5837f0f --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Protocols/BluetoothPairingProtocol.swift @@ -0,0 +1,32 @@ +// +// BluetoothPairingProtocol.swift +// MVM5G +// +// Created by Matt Bruce on 4/15/22. +// Copyright © 2022 Kyle. All rights reserved. +// + +import Foundation + +/// This Protocol will be implemented by a variation of BluetoothPairing Classes +protocol BluetoothParingProtocol: AnyObject, MFFGHSAnalyticsProtocol { + var bluetoothConfig: BluetoothConfigModel? { get set } + var bluetoothDebugger: BluetoothDebugger? { get set } + func getBleAdvertiseName() -> String + func track(_ data: [String: Any]) + func track(_ message: String, function: String, additionalData: [String: Any]) +} + +extension BluetoothParingProtocol { + func track(_ message: String, function: String = #function, additionalData: [String: Any] = [:]) { + guard bluetoothConfig != nil else { return } + + let data = additionalData.values.isEmpty ? "" : "\r \(additionalData)" + bluetoothDebugger?.addLog(message: "\(message)\(data)") + + trackGemini(value: ([GMFGConstant.functionName: function, + GMFGConstant.BLE.advertisedData: getBleAdvertiseName(), + "message": message + ]).merging(additionalData, uniquingKeysWith: { $1 }), logType: .BLE) + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFG5GCBandSignalHandler.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFG5GCBandSignalHandler.swift new file mode 100644 index 0000000..eaeda77 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFG5GCBandSignalHandler.swift @@ -0,0 +1,105 @@ +// +// GMFG5GSignalHandler.swift +// MVM5G +// +// Created by Jason Beck on 5/11/21. +// Copyright © 2021 Kyle. All rights reserved. +// +import CoreBluetooth + +protocol GMFG5GCBandSignalDelegate: AnyObject { + func updated5GCBandSignal(_ update: GMFG5GCBandSignalUpdate) + func signal5GCBandNotReady() +} + +private enum ResultKeys: String { + case signalNoiseRatio = "SINR" + case referencePower = "RSRP" + case pci = "PCI" + case ssbIndex = "SSBI" + case referenceSignalQuality = "RSRQ" + case modulationOrder = "MO" + case mcsIndex = "MCS " //This is on purpose. The firmware has a trailing space in the key name. + case arfcn5G = "5GARFCN" + case ledColor = "LED" +} + +struct GMFG5GCBandSignalUpdate { + var signalToNoiseRatio: Int = 0 + var referencePower: Int = 0 + var pci: Int = 0 + var ssbIndex: Int = 0 + var referenceSignalQuality: Int = 0 + var modulationOrder: String = "NA" + var mcsIndex: Int = 0 + var arfcn5G: Int = 0 + var ledColor: GMFGCPELEDColor? + var rawResults: [String: Any]? +} + +class GMFG5GCBandSignalHandler: GMFGBLEHandlerProtocol { + + private let multicast = MulticastDelegate() + public func setDelegate(delegates: [GMFG5GCBandSignalDelegate?]) { + multicast.add(delegates: delegates) + } + + public func reset() { + multicast.removeAll() + } + + //MARK:- Helper + private func rawKey(_ type: ResultKeys) -> String { return type.rawValue } + private func rawStatus(_ type: GMFGCPELEDColor) -> String { return type.rawValue } + + public func get5GSignal(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) { + peripheral.readValue(for: characteristic) + } + + //It is possible to try and call the device before it's done loading + public func deviceNotReady() { + multicast.invokeForEachDelegate{ $0.signal5GCBandNotReady() } + } + + public func response(response: [String : Any]) { + if let validResponse = responseFormat(response: response) { + multicast.invokeForEachDelegate{ $0.updated5GCBandSignal(validResponse) } + } + } + + func responseFormat(response: [String: Any]) -> GMFG5GCBandSignalUpdate? { + guard let fiveGData = response["5GSignal"] as? [String: Any] else { + return nil + } + var returnSignal = GMFG5GCBandSignalUpdate() + if let signalToNoise = (fiveGData[rawKey(.signalNoiseRatio)] as? String), let snrInt = Int(signalToNoise) { + returnSignal.signalToNoiseRatio = snrInt + } + if let referenceSignalPower = (fiveGData[rawKey(.referencePower)] as? String), let refPowerInt = Int(referenceSignalPower) { + returnSignal.referencePower = refPowerInt + } + if let fiveGARFCN = (fiveGData[rawKey(.arfcn5G)] as? String), let arfcnInt = Int(fiveGARFCN) { + returnSignal.arfcn5G = arfcnInt + } + if let referenceQuality = (fiveGData[rawKey(.referenceSignalQuality)] as? String), let sqInt = Int(referenceQuality) { + returnSignal.referenceSignalQuality = sqInt + } + if let ssbi = (fiveGData[rawKey(.ssbIndex)] as? String), let ssbInt = Int(ssbi) { + returnSignal.ssbIndex = ssbInt + } + if let ledColorRaw = (fiveGData[rawKey(.ledColor)] as? String), let ledColor = GMFGCPELEDColor(rawValue: ledColorRaw) { + returnSignal.ledColor = ledColor + } + if let mcs = (fiveGData[rawKey(.mcsIndex)] as? String), let mcsInt = Int(mcs) { + returnSignal.mcsIndex = mcsInt + } + if let pci = (fiveGData[rawKey(.pci)] as? String), let pciInt = Int(pci) { + returnSignal.pci = pciInt + } + if let mo = (fiveGData[rawKey(.modulationOrder)] as? String) { + returnSignal.modulationOrder = mo + } + returnSignal.rawResults = fiveGData + return returnSignal + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFG5GSignalHandler.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFG5GSignalHandler.swift new file mode 100644 index 0000000..d3898d1 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFG5GSignalHandler.swift @@ -0,0 +1,111 @@ +// +// GMFG5GSignalHandler.swift +// MVM5G +// +// Created by Jason Beck on 5/11/21. +// Copyright © 2021 Kyle. All rights reserved. +// +import CoreBluetooth + +protocol GMFG5GSignalDelegate: AnyObject { + func updated5GSignal(_ update: GMFG5GSignalUpdate) + func signalNotReady() +} + +private enum ResultKeys: String { + case signalNoiseRatio = "SINR" + case referencePower = "RSRP" + case pci = "PCI" + case ssbIndex = "SSBI" + case referenceSignalQuality = "RSRQ" + case modulationOrder = "MO" + case mcsIndex = "MCS " //This is on purpose. The firmware has a trailing space in the key name. + case arfcn5G = "5GARFCN" + case ledColor = "LED" +} + +enum GMFGCPELEDColor: String { + case red = "R" + case yellow = "Y" + case green = "G" +} + +struct GMFG5GSignalUpdate { + var signalToNoiseRatio: Int = 0 + var referencePower: Int = 0 + var pci: Int = 0 + var ssbIndex: Int = 0 + var referenceSignalQuality: Int = 0 + var modulationOrder: String = "NA" + var mcsIndex: Int = 0 + var arfcn5G: Int = 0 + var ledColor: GMFGCPELEDColor? + var rawResults: [String: Any]? +} + +class GMFG5GSignalHandler: GMFGBLEHandlerProtocol { + + private let multicast = MulticastDelegate() + public func setDelegate(delegates: [GMFG5GSignalDelegate?]) { + multicast.add(delegates: delegates) + } + + public func reset() { + multicast.removeAll() + } + + //MARK:- Helper + private func rawKey(_ type: ResultKeys) -> String { return type.rawValue } + private func rawStatus(_ type: GMFGCPELEDColor) -> String { return type.rawValue } + + public func get5GSignal(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) { + peripheral.readValue(for: characteristic) + } + + //It is possible to try and call the device before it's done loading + public func deviceNotReady() { + multicast.invokeForEachDelegate{ $0.signalNotReady() } + } + + public func response(response: [String : Any]) { + if let validResponse = responseFormat(response: response) { + multicast.invokeForEachDelegate{ $0.updated5GSignal(validResponse) } + } + } + + func responseFormat(response: [String: Any]) -> GMFG5GSignalUpdate? { + guard let fiveGData = response["5GSignal"] as? [String: Any] else { + return nil + } + var returnSignal = GMFG5GSignalUpdate() + if let signalToNoise = (fiveGData[rawKey(.signalNoiseRatio)] as? String), let snrInt = Int(signalToNoise) { + returnSignal.signalToNoiseRatio = snrInt + } + if let referenceSignalPower = (fiveGData[rawKey(.referencePower)] as? String), let refPowerInt = Int(referenceSignalPower) { + returnSignal.referencePower = refPowerInt + } + if let fiveGARFCN = (fiveGData[rawKey(.arfcn5G)] as? String), let arfcnInt = Int(fiveGARFCN) { + returnSignal.arfcn5G = arfcnInt + } + if let referenceQuality = (fiveGData[rawKey(.referenceSignalQuality)] as? String), let sqInt = Int(referenceQuality) { + returnSignal.referenceSignalQuality = sqInt + } + if let ssbi = (fiveGData[rawKey(.ssbIndex)] as? String), let ssbInt = Int(ssbi) { + returnSignal.ssbIndex = ssbInt + } + if let ledColorRaw = (fiveGData[rawKey(.ledColor)] as? String), let ledColor = GMFGCPELEDColor(rawValue: ledColorRaw) { + returnSignal.ledColor = ledColor + } + if let mcs = (fiveGData[rawKey(.mcsIndex)] as? String), let mcsInt = Int(mcs) { + returnSignal.mcsIndex = mcsInt + } + if let pci = (fiveGData[rawKey(.pci)] as? String), let pciInt = Int(pci) { + returnSignal.pci = pciInt + } + if let mo = (fiveGData[rawKey(.modulationOrder)] as? String) { + returnSignal.modulationOrder = mo + } + returnSignal.rawResults = fiveGData + return returnSignal + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGFotaHandler.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGFotaHandler.swift new file mode 100644 index 0000000..813d15f --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGFotaHandler.swift @@ -0,0 +1,132 @@ +// +// GMFGFotaHandler.swift +// MVM5G +// +// Created by Jason Beck on 4/20/20. +// Copyright © 2020 Kyle. All rights reserved. +// +import CoreBluetooth + +private enum GMFotaKey: String { + case status = "FOTAStatus" + case error = "ErrorMessage" + case version = "FirmwareVersion" + case check = "Check" + case fotaOp = "FOTAOperation" +} + +public enum GMFotaStatus: String { + case notReady = "NotReady" + case latest = "Latest" + case available = "NewAvailable" + case success = "SuccessfulUpgrade" + case fail = "FailedUpgrade" + case failDownload = "FailedDownload" + case progress = "DownloadProgress" + case upgrading = "Upgrading" +} + +protocol GMFGFotaDelegate: AnyObject { + func didStartUpdate() + func fotaStatus(statusResponse: GMFotaResponse) + func unknownResponse(rawResponse: [String: Any]?) +} + +extension GMFGFotaDelegate { + func didStartUpdate() {} +} + +final class GMFotaResponse: CustomStringConvertible { + var status: GMFotaStatus + var errorMsg: String? + private var _version = "" + var version: String { + set { + if (newValue.isEmpty) || (newValue == "XXX.YYY.ZZZZ") || (newValue == "Unknown") { + _version = "0.0.0.0" + } else { + _version = newValue + } + } + get { + return _version + } + } + var rawResponse: [String: Any] + + init(status: GMFotaStatus, version: String, rawResponse: [String: Any]) { + self.status = status + self.rawResponse = rawResponse + self.version = version + } + + var description: String { + return """ + Status: \(self.status.rawValue) + Error Message: \(self.errorMsg ?? GMFGConstant.empty) + Version: \(self.version) + Raw Response:\n\(self.rawResponse) + """ + } +} + +final class GMFGFotaHandler: GMFGBLEHandlerProtocol { + + private let multicast = MulticastDelegate() + + public func setDelegate(delegates: [GMFGFotaDelegate?]) { + self.multicast.add(delegates: delegates) + } + + public func reset() { + self.multicast.removeAll() + } + + //MARK:- Helper + private func rawKey(_ type: GMFotaKey) -> String { return type.rawValue } + private func rawStatus(_ type: GMFotaStatus) -> String { return type.rawValue } + + //MARK:- Fota Functions + public func requestVersion(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) { + peripheral.readValue(for: characteristic) + } + + public func startUpdate(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) { + let requestValue = [rawKey(.fotaOp): rawKey(.check)] + guard let data = convertToData(requestValue) else { return } + let writeType: CBCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse : .withResponse + peripheral.writeValue(data, for: characteristic, type: writeType) + multicast.invokeForEachDelegate { $0.didStartUpdate() } + } + + public func response(response: [String : Any]) { + let status: GMFotaStatus! + var version = "0.0.0.0" + + guard let statusString = response[rawKey(.status)] as? String else { + multicast.invokeForEachDelegate{ $0.unknownResponse(rawResponse: response) } + return + } + + if statusString.contains("DownloadProgress") { + status = .progress + } else if let _status = GMFotaStatus(rawValue: statusString), + let _version = response[rawKey(.version)] as? String { + status = _status + version = _version + } else { + multicast.invokeForEachDelegate{ $0.unknownResponse(rawResponse: response) } + return + } + + let responseObj = GMFotaResponse(status: status, version: version, rawResponse: response) + responseObj.errorMsg = response[rawKey(.error)] as? String + multicast.invokeForEachDelegate{ $0.fotaStatus(statusResponse: responseObj) } + } + + public func deviceNotReady() { + let responseObj = GMFotaResponse(status: .notReady, version: "Unknown", rawResponse: [:]) + multicast.invokeForEachDelegate{ $0.fotaStatus(statusResponse: responseObj) } + } +} + diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGOperationHandler.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGOperationHandler.swift new file mode 100644 index 0000000..8ffe54f --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGOperationHandler.swift @@ -0,0 +1,34 @@ +// +// GMFGOperationHandler.swift +// MVM5G +// +// Created by Jason Beck on 7/28/20. +// Copyright © 2020 Kyle. All rights reserved. +// +import CoreBluetooth + +enum GMFGOperationMode: String { + case installation = "Installation" + case operational = "Operational" +} + +class GMFGOperationHandler: GMFGBLEHandlerProtocol { + public func setOpMode(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic, mode: GMFGOperationMode, disableBLE: Bool = false) -> Bool { + var requestValue: [String: Any] = ["ReceiverMode": mode.rawValue] + if disableBLE { + requestValue["DisableBLE"] = disableBLE + } + guard let data = convertToData(requestValue) else { return false } + let writeType: CBCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse : .withResponse + peripheral.writeValue(data, for: characteristic, type: writeType) + return true + } + + func response(response: [String : Any]) { + //This class does not currently respond to the VC, so this function is here only to satisfy protocol conformance + } + + func deviceNotReady() { + //This class does not currently respond to the VC, so this function is here only to satisfy protocol conformance + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGPublicInternetAccessHandler.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGPublicInternetAccessHandler.swift new file mode 100644 index 0000000..7502b70 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGPublicInternetAccessHandler.swift @@ -0,0 +1,47 @@ +// +// GMFGPublicInternetAccessHandler.swift +// MVM5G +// +// Created by Jason Beck on 5/27/20. +// Copyright © 2020 Kyle. All rights reserved. +// +import CoreBluetooth + +protocol GMFGPublicInternetAccessDelegate: AnyObject { + func publicInternetAccessStatus(status: GMPublicInternetAccessStatus) +} + +enum GMPublicInternetAccessStatus: String { + case enabled = "Enabled" + case disabled = "Disabled" + case notReady = "NotReady" +} + +class GMFGPublicInternetAccessHandler: GMFGBLEHandlerProtocol { + + private let multicast = MulticastDelegate() + + public func setDelegate(delegates: [GMFGPublicInternetAccessDelegate?]) { + self.multicast.add(delegates: delegates) + } + + public func reset() { + self.multicast.removeAll() + } + + public func getPublicInternetAccess(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) { + peripheral.readValue(for: characteristic) + } + + public func response(response: [String : Any]) { + guard let statusString = response["PublicInternetAccess"] as? String, let statusEnum = GMPublicInternetAccessStatus.init(rawValue: statusString) else { + return + } + multicast.invokeForEachDelegate{ $0.publicInternetAccessStatus(status: statusEnum) } + reset() + } + + public func deviceNotReady() { + multicast.invokeForEachDelegate{ $0.publicInternetAccessStatus(status: .notReady) } + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGRouterWifiHandler.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGRouterWifiHandler.swift new file mode 100644 index 0000000..06e8026 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGRouterWifiHandler.swift @@ -0,0 +1,51 @@ +// +// GMFGRouterWifiHandler.swift +// MVM5G +// +// Created by Rajesh on 12/9/20. +// Copyright © 2020 Kyle. All rights reserved. +// +import Foundation +import CoreBluetooth + +protocol GMFGRouterWifiDelegate: AnyObject { + func routerWifi(credentials: GMFGRouterWifiInfo) + func routerWifiStatus(status: GMFGRouterWifiStatus) +} + +struct GMFGRouterWifiInfo { + var ssid: String + var password: String +} + +enum GMFGRouterWifiStatus: String { + case disabled = "Disabled" + case notReady = "NotReady" +} + +class GMFGRouterWifiHandler: GMFGBLEHandlerProtocol { + + private let multicast = MulticastDelegate() + + public func setDelegate(delegates: [GMFGRouterWifiDelegate?]) { + self.multicast.add(delegates: delegates) + } + + public func reset() { + self.multicast.removeAll() + } + + public func fetchRouterWifiCredentials(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) { + peripheral.readValue(for: characteristic) + } + + public func response(response: [String : Any]) { + guard let wifiSSID = response["SSID"] as? String, let wifiPwd = response["Password"] as? String else { return } + multicast.invokeForEachDelegate{ $0.routerWifi(credentials: GMFGRouterWifiInfo.init(ssid: wifiSSID, password: wifiPwd)) } + reset() + } + + public func deviceNotReady() { + multicast.invokeForEachDelegate{ $0.routerWifiStatus(status: .notReady) } + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGSpeedTestHandler.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGSpeedTestHandler.swift new file mode 100644 index 0000000..bb001bb --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/BLE Handlers/GMFGSpeedTestHandler.swift @@ -0,0 +1,141 @@ +// +// GMFGSpeedTestHandler.swift +// MVM5G +// +// Created by Jason Beck on 5/26/20. +// Copyright © 2020 Kyle. All rights reserved. +// +import CoreBluetooth + +protocol GMFGSpeedTestDelegate: AnyObject { + func didStartSpeedTest() + func didStopSpeedTest() + func speedTestStatus(status: GMFGSpeedTestUpdate) +} + +extension GMFGSpeedTestDelegate { + func didStartSpeedTest(){} + func didStopSpeedTest(){} +} + +private enum ResultKeys: String { + case result = "Result" + case speedTestServer = "SIP" + case speedTestServerID = "SID" + case routerIP = "RIP" + case downlink = "DL" + case uplink = "UL" + case latency = "Lat" + case jitter = "Jitt" + case progress = "Prog" + case errorMessage = "Error" +} + +enum Status: String { + case ready = "Ready" + case running = "Running" + case failed = "Failed" + case done = "Done" + case notReady = "NotReady" +} + +struct GMFGSpeedTestUpdate { + var result: Status + var speedTestServerIP: String? + var speedTestServerID: Int? + var routerIP: String? + var downlink: String? + var uplink: String? + var latency: String? + var jitter: Int? + var progress: Double? + var errorMessage: String? + var rawResults: [String: Any]? + + func getDictFromSelf() -> [String: Any]? { + let dataDict: [String: Any] = [ + ResultKeys.result.rawValue : result.rawValue, + ResultKeys.speedTestServer.rawValue : speedTestServerIP ?? GMFGConstant.empty, + ResultKeys.speedTestServerID.rawValue: speedTestServerID ?? -1, + ResultKeys.routerIP.rawValue : routerIP ?? GMFGConstant.empty, + ResultKeys.downlink.rawValue : downlink ?? GMFGConstant.empty, + ResultKeys.uplink.rawValue : uplink ?? GMFGConstant.empty, + ResultKeys.latency.rawValue : latency ?? GMFGConstant.empty, + ResultKeys.jitter.rawValue : jitter ?? -1, + ResultKeys.progress.rawValue : progress ?? 0.0, + ResultKeys.errorMessage.rawValue : errorMessage ?? GMFGConstant.empty + ] + return dataDict + } +} + +class GMFGSpeedTestHandler: GMFGBLEHandlerProtocol { + + private let multicast = MulticastDelegate() + public func setDelegate(delegates: [GMFGSpeedTestDelegate?]) { + self.multicast.add(delegates: delegates) + } + + public func reset() { + multicast.removeAll() + } + + //MARK:- Helper + private func rawKey(_ type: ResultKeys) -> String { return type.rawValue } + private func rawStatus(_ type: Status) -> String { return type.rawValue } + + public func start(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) { + let requestValue = ["SpeedTest": "Start"] + guard let data = convertToData(requestValue) else { return } + let writeType: CBCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse : .withResponse + peripheral.writeValue(data, for: characteristic, type: writeType) + multicast.invokeForEachDelegate{ $0.didStartSpeedTest() } + + //Notify process started having status = running + let responseObj = GMFGSpeedTestUpdate(result: .running) + multicast.invokeForEachDelegate{ $0.speedTestStatus(status: responseObj) } + } + + public func stop(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) { + let requestValue = ["SpeedTest": "Stop"] + guard let data = convertToData(requestValue) else { return } + let writeType: CBCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse : .withResponse + peripheral.writeValue(data, for: characteristic, type: writeType) + multicast.invokeForEachDelegate{ $0.didStopSpeedTest() } + } + + public func update(_ peripheral: CBPeripheral, _ characteristic: CBCharacteristic) { + peripheral.readValue(for: characteristic) + } + + //It is possible to try and call the device before it's done loading + public func deviceNotReady() { + let responseObj = GMFGSpeedTestUpdate(result: .notReady) + multicast.invokeForEachDelegate{ $0.speedTestStatus(status: responseObj) } + } + + public func response(response: [String : Any]) { + guard let statusString = response[rawKey(.result)] as? String, + let status = Status(rawValue: statusString) else { + return + } + + let responseObj = responseFormat(status: status, response: response) + if responseObj.result == .done || responseObj.result == .failed { + multicast.invokeForEachDelegate{ $0.didStopSpeedTest() } + } + multicast.invokeForEachDelegate{ $0.speedTestStatus(status: responseObj) } + } + + func responseFormat(status: Status, response: [String: Any]) -> GMFGSpeedTestUpdate { + return GMFGSpeedTestUpdate(result: status, speedTestServerIP: response[rawKey(.speedTestServer)] as? String, + speedTestServerID: response[rawKey(.speedTestServerID)] as? Int, + routerIP: response[rawKey(.routerIP)] as? String, + downlink: response[rawKey(.downlink)] as? String, + uplink: response[rawKey(.uplink)] as? String, + latency: response[rawKey(.latency)] as? String, + jitter: response[rawKey(.jitter)] as? Int, + progress: (response[rawKey(.progress)] as? NSString)?.doubleValue, + errorMessage: response[rawKey(.errorMessage)] as? String, rawResults: response) + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGBluetoothPair.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGBluetoothPair.swift new file mode 100644 index 0000000..5bc9c00 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGBluetoothPair.swift @@ -0,0 +1,509 @@ +// +// GMFGBluetoothPair.swift +// MobileFirstFramework +// +// Created by Chowdhury, Shohrab on 4/20/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import CoreBluetooth + +class GMFGBluetoothPair: MFFGHSBluetoothPair { + private static weak var ginstance: GMFGBluetoothPair? + static var shared: GMFGBluetoothPair { + get { + if let ginstance = ginstance { + return ginstance + } else { + let newInstance = GMFGBluetoothPair() + ginstance = newInstance + return newInstance + } + } + } + + override var bluetoothConfig: BluetoothConfigModel? { + didSet { + if (oldValue != bluetoothConfig) { // TODO: Needs deep check for config changes. + setupBluetoothScanner(showPowerAlert: true) + } + } + } + + override func shutdown() { + super.shutdown() + GMFGBluetoothPair.ginstance = nil + } + + typealias ResponseHandler = ([String : Any]) -> Void + private var speedTestHandler = GMFGSpeedTestHandler() + private var internetAccessHandler = GMFGPublicInternetAccessHandler() + private var routerWifiHandler = GMFGRouterWifiHandler() + private var operationModeHandler = GMFGOperationHandler() + private var fotaHandler = GMFGFotaHandler() + private var fiveGSignalHandler = GMFG5GSignalHandler() + private var fiveGCBandSignalHandler = GMFG5GCBandSignalHandler() + private var responseHandlers: [CBUUID: ResponseHandler] = [:] + + private var max5GSignal: Double? + var max5GSignalSeen: Double? { + set { + if (max5GSignal == nil && newValue != nil) || (max5GSignal != nil && newValue == nil) { + max5GSignal = newValue + return + } + guard let value = newValue, let maxSignal = max5GSignal else { return } + if value > maxSignal { max5GSignal = newValue } + } + get { return max5GSignal } + } + + private var min5GSignal: Double? + var min5GSignalSeen: Double? { + set { + if (min5GSignal == nil && newValue != nil) || (min5GSignal != nil && newValue == nil) { + min5GSignal = newValue + return + } + guard let value = newValue, let minSignal = min5GSignal else { return } + if value < minSignal { min5GSignal = newValue } + } + get { return min5GSignal } + } + var samples5GSignalSeen = 0 + + public class func isValidAdvertisedData(_ testData: String) -> Bool { + return testData.contains(GMFGConstant.BLE.advertisePrefix) && testData.count == 20 + } + + // MARK:- Notification Functions + func updateAllNotifyValue(_ shouldNotify: Bool) { + if let _peripheral = peripheral, let services = _peripheral.services { + for service in services { + if let characteristics = service.characteristics { + for characteristic in characteristics { + if characteristic.isNotifying { + _peripheral.setNotifyValue(shouldNotify, for: characteristic) + } + } + } + } + } + } + + @discardableResult func sendNotifications(for characteristic: CharacteristicType, shouldNotify: Bool) -> Bool { + if let _peripheral = peripheral, let _characteristic = getCharacteristic(peripheral: _peripheral, fromType: characteristic) { + _peripheral.setNotifyValue(shouldNotify, for: _characteristic) + return true + } + return false + } + + + /// This will ensure that the handlers are registered only once + /// All this logic was originally happening in a case statement + /// therefore each comparison / lookup was happening everytime + /// the callback occurred. This is registering a characteric to a handler + /// which happens 1 time and used then with a dictionary lookup for the handler. + /// - Parameter peripheral: comes in from delegate callback + func ensureResponseHandlers(peripheral: CBPeripheral) { + //compare local peripheral vs one passed in + //make sure bluetoothConfig is set + //make sure there are no responseHandlers registered + guard let localPeripheral = self.peripheral, + peripheral.identifier == localPeripheral.identifier, + responseHandlers.isEmpty, + bluetoothConfig != nil else { return } + + // activation + if let uuid = getCharacteristic(peripheral: peripheral, fromType: .activation)?.uuid { + responseHandlers[uuid] = handleActivated(response:) + } + + // speedTest + if let uuid = getCharacteristic(peripheral: peripheral, fromType: .speedTest)?.uuid { + responseHandlers[uuid] = speedTestHandler.response(response:) + } + if let uuid = getCharacteristic(peripheral: peripheral, fromType: .speedTestResults)?.uuid { + responseHandlers[uuid] = speedTestHandler.response(response:) + } + + //internetAccess + if let uuid = getCharacteristic(peripheral: peripheral, fromType: .internetAccess)?.uuid { + responseHandlers[uuid] = internetAccessHandler.response(response:) + } + + //fota + if let uuid = getCharacteristic(peripheral: peripheral, fromType: .fotaCheck)?.uuid { + responseHandlers[uuid] = fotaHandler.response(response:) + } + if let uuid = getCharacteristic(peripheral: peripheral, fromType: .fotaStatus)?.uuid { + responseHandlers[uuid] = fotaHandler.response(response:) + } + + //routerWifi + if let uuid = getCharacteristic(peripheral: peripheral, fromType: .routerWiFi)?.uuid { + responseHandlers[uuid] = routerWifiHandler.response(response:) + } + + //fiveGCBandSignal + if let uuid = getCharacteristic(peripheral: peripheral, fromType: .fiveGCBandSignal)?.uuid { + responseHandlers[uuid] = fiveGCBandSignalHandler.response(response:) + } + + //fiveGSignal + if let uuid = getCharacteristic(peripheral: peripheral, fromType: .fiveGSignal)?.uuid { + func fiveGResponse(response: [String: Any]){ + updateFiveGStatistics(cpeResponse: response) + handleFiveGSignal(response: response) + fiveGSignalHandler.response(response: response) + } + responseHandlers[uuid] = fiveGResponse(response:) + } + } + + // MARK:- CBPeripheralDelegate + override public func centralManagerDidUpdateState(_ central: CBCentralManager) { + bluetoothDebugger?.bluetoothState = central.state + if central.state == .unauthorized { + delegate?.bluetoothPermissionsDenied() //User denied BLE + bluetoothDebugger?.isBluetoothPermissionDenied = true + } else if central.state == .poweredOff { + delegate?.bluetoothOff() //User turned off BLE + } + super.centralManagerDidUpdateState(central) + } + + override func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { + let cpeResponse = formatCharacteristicData(characteristic) + track("Message From CPE", additionalData: cpeResponse.merging(["CBUUID":characteristic.uuid.uuidString], uniquingKeysWith: { $1 })) + + //make sure responseHandlers are registered + ensureResponseHandlers(peripheral: peripheral) + + if let responseHandler = responseHandlers[characteristic.uuid] { + responseHandler(cpeResponse) + } + } + + override public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { + if let peripheralName = advertisementData[CBAdvertisementDataLocalNameKey] as? String, peripheralName.hasPrefix(GMFGConstant.BLE.advertisePrefix) { + track("Scanning found peripheral \(peripheralName) with RSSI \(RSSI).") + } else { + track("Scanning found peripheral, but not a VZ5G device") + } + super.centralManager(central, didDiscover: peripheral, advertisementData: advertisementData, rssi: RSSI) + } + + override public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + track("Connected to \(String(describing: peripheral.name))") + super.centralManager(central, didConnect: peripheral) + } + + override public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { + track("Disconnected from \(String(describing: peripheral.name))") + fiveGSignalHandler.deviceNotReady() + super.centralManager(central, didDisconnectPeripheral: peripheral, error: error) + } + + override public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { + if let error = error { + track("Discover Service Error: \(error.localizedDescription)") + } + super.peripheral(peripheral, didDiscoverServices: error) + } + + public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { + if let error = error { + track("Read error: \(error.localizedDescription) with RSSI: \(RSSI)") + } + } + + public func peripheral(_ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error?) { + if let error = error { + track("Error opening channel: \(error.localizedDescription)") + } + } + + public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) { + if let error = error { + track("Error writing to characteristic: \(String(describing: descriptor.characteristic?.uuid.uuidString)) holding value: \(String(describing: descriptor.value)) with error: \(error.localizedDescription)") + } + } + + public func peripheral(_ peripheral: CBPeripheral, didDiscoverIncludedServicesFor service: CBService, error: Error?) { + if let error = error { + track("Error in discovered device characteristics: \(error.localizedDescription)") + } + } + + override func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { + track(error?.localizedDescription ?? "Unknown", function: "Central Manager Failed Connection") + } + + override func scanForPeripherals(central: CBCentralManager) { + if !central.isScanning { + track("Starting scan for peripherals") + central.scanForPeripherals(withServices: nil, options: nil) + } else { + track("Already Scanning for peripherals") + } + } + + //5GSignal Characteristic + private func updateFiveGStatistics(cpeResponse: [String: Any]) { + if let fivegSignal = cpeResponse["5GSignal"] as? [String: Any] { + if let rssiValue = parseRSRPFromJSON(fiveGData: fivegSignal) { + max5GSignalSeen = rssiValue + min5GSignalSeen = rssiValue + samples5GSignalSeen = samples5GSignalSeen + 1 + } + } + } + + // MARK:- Speed Test Functions + + public func startSpeedTest(_ speedTestDelegate: GMFGSpeedTestDelegate, withNotifications: Bool) { + speedTestHandler.setDelegate(delegates: [speedTestDelegate, bluetoothDebugger]) + if let _peripheral = peripheral, let _cTriggerSpeedTest = getCharacteristic(peripheral: _peripheral, fromType: .speedTest) { + if withNotifications { + updateAllNotifyValue(false) //Turn them all off to reduce traffic. + _ = sendNotifications(for: .speedTestResults, shouldNotify: true) + } + track("Starting Speed Test") + speedTestHandler.start(_peripheral, _cTriggerSpeedTest) + } else { + let errorMessage = peripheral == nil ? GMFGConstant.BLE.LogMsg.peripheralError : GMFGConstant.BLE.LogMsg.characteristicError + track(errorMessage) + speedTestHandler.deviceNotReady() + } + } + + public func stopSpeedTest() { + speedTestHandler.reset() + if let _peripheral = peripheral, let _cTriggerSpeedTest = getCharacteristic(peripheral: _peripheral, fromType: .speedTest) { + _ = sendNotifications(for: .speedTestResults, shouldNotify: false) + track("Speed Test Stopping") + speedTestHandler.stop(_peripheral, _cTriggerSpeedTest) + } else { + let errorMessage = peripheral == nil ? GMFGConstant.BLE.LogMsg.peripheralError : GMFGConstant.BLE.LogMsg.characteristicError + track(errorMessage) + speedTestHandler.deviceNotReady() + } + } + + //MARK:- Router WIFI Info + public func fetchRouterWifi(delegate: GMFGRouterWifiDelegate) { + routerWifiHandler.setDelegate(delegates: [delegate, bluetoothDebugger]) + if let _peripheral = peripheral, let _cRouterWifi = getCharacteristic(peripheral: _peripheral, fromType: .routerWiFi) { + track("Started fetching Wifi info") + routerWifiHandler.fetchRouterWifiCredentials(_peripheral, _cRouterWifi) + } else { + let errorMessage = peripheral == nil ? GMFGConstant.BLE.LogMsg.peripheralError : GMFGConstant.BLE.LogMsg.characteristicError + track(errorMessage) + routerWifiHandler.deviceNotReady() + } + } + + // MARK:- Public Internet Functions + public func checkInternetStatus(delegate: GMFGPublicInternetAccessDelegate) { + internetAccessHandler.setDelegate(delegates: [delegate, bluetoothDebugger]) + if let _peripheral = peripheral, let _cPublicInternetAccess = getCharacteristic(peripheral: _peripheral, fromType: .internetAccess) { + track("Getting Internet Access") + internetAccessHandler.getPublicInternetAccess(_peripheral, _cPublicInternetAccess) + } else { + let errorMessage = peripheral == nil ? GMFGConstant.BLE.LogMsg.peripheralError : GMFGConstant.BLE.LogMsg.characteristicError + track(errorMessage) + internetAccessHandler.deviceNotReady() + } + } + + // MARK:- Operational Mode Functions + @discardableResult + public func setOpMode(mode: GMFGOperationMode, disableBLE: Bool = false) -> Bool { + let additionalLogData: [String: Any] = ["Mode": mode.rawValue, "DisableBLE": disableBLE] + if let _peripheral = peripheral, let _cOperationMode = getCharacteristic(peripheral: _peripheral, fromType: .operationMode) { + track("Disabling BLE", additionalData: additionalLogData) + return operationModeHandler.setOpMode(_peripheral, _cOperationMode, mode: mode, disableBLE: disableBLE) + } + let errorMessage = peripheral == nil ? GMFGConstant.BLE.LogMsg.peripheralError : GMFGConstant.BLE.LogMsg.characteristicError + track(errorMessage, additionalData: additionalLogData) + return false + } + + // MARK:- FOTA Functions + public func startGetFirmwareVersion(delegate: GMFGFotaDelegate) { + bluetoothDebugger?.isCheckingFirmware = true + fotaHandler.setDelegate(delegates: [delegate, bluetoothDebugger]) + if let _peripheral = peripheral, let _cFota = getCharacteristic(peripheral: _peripheral, fromType: .fotaStatus) { + track("Started getting firmware version from device") + fotaHandler.requestVersion(_peripheral, _cFota) + } else { + let errorMessage = peripheral == nil ? GMFGConstant.BLE.LogMsg.peripheralError : GMFGConstant.BLE.LogMsg.characteristicError + track(errorMessage) + fotaHandler.deviceNotReady() + } + } + + public func stopGetFirmwareVersion() { + bluetoothDebugger?.isCheckingFirmware = false + fotaHandler.reset() + track("Stopped getting firmware version") + } + + public func startFirmwareUpgrade(delegate: GMFGFotaDelegate, useNotifications: Bool) { + fotaHandler.setDelegate(delegates: [delegate, bluetoothDebugger]) + if let _peripheral = peripheral, let _cFota = getCharacteristic(peripheral: _peripheral, fromType: .fotaCheck), getCharacteristic(peripheral: _peripheral,fromType: .fotaStatus) != nil { + track("Starting firmware update") + updateAllNotifyValue(false) + _ = self.sendNotifications(for: .fotaStatus, shouldNotify: useNotifications) + fotaHandler.startUpdate(_peripheral, _cFota) + } else { + let errorMessage = peripheral == nil ? GMFGConstant.BLE.LogMsg.peripheralError : GMFGConstant.BLE.LogMsg.characteristicError + track(errorMessage) + fotaHandler.deviceNotReady() + } + } + + public func readFOTAStatus(delegate: GMFGFotaDelegate) { + fotaHandler.setDelegate(delegates: [delegate, bluetoothDebugger]) + if let _peripheral = peripheral, let _cFota = getCharacteristic(peripheral: _peripheral, fromType: .fotaStatus) { + track("Checking FOTA status") + fotaHandler.requestVersion(_peripheral, _cFota) + } else { + let errorMessage = peripheral == nil ? GMFGConstant.BLE.LogMsg.peripheralError : GMFGConstant.BLE.LogMsg.characteristicError + track(errorMessage) + fotaHandler.deviceNotReady() + } + } + + public func stopFirmwareUpgrade() { + fotaHandler.reset() + track("Stopped firmware update") + } + + public func isFotaInProgress() -> Bool { + if let params = GMFGStorageManager.retrieve(key: GMFGConstant.fiveGParams), let inProgress = params[GMFGConstant.BLE.fotaInProgress] as? Bool { + return inProgress + } + return false + } + + public func setFotaInProgress(_ inProgress: Bool) { + GMFGStorageManager.addEntries(dictionary: [GMFGConstant.BLE.fotaInProgress: inProgress]) + } + + public func getOldFirmwareVersion() -> String? { + if let params = GMFGStorageManager.retrieve(key: GMFGConstant.fiveGParams), let oldFirmware = params[GMFGConstant.BLE.firmwareVersion] as? String { + return oldFirmware + } + return nil + } + + public func isFirmwareNewer(_ newFirmware: String) -> Bool { + if getOldFirmwareVersion() == newFirmware { + return false + } + return true + } + + // MARK:- 5G Signal Handler + public func start5GSignalUpdates(_ delegate: GMFG5GSignalDelegate, useNotifications: Bool) { + fiveGSignalHandler.setDelegate(delegates: [delegate, bluetoothDebugger]) + if let _peripheral = peripheral, let _c5GSignal = getCharacteristic(peripheral: _peripheral, fromType: .fiveGSignal) { + track("Getting 5G Signal") + sendNotifications(for: .fiveGSignal, shouldNotify: useNotifications) + fiveGSignalHandler.get5GSignal(_peripheral, _c5GSignal) + } else { + let errorMessage = peripheral == nil ? GMFGConstant.BLE.LogMsg.peripheralError : GMFGConstant.BLE.LogMsg.characteristicError + track(errorMessage) + fiveGSignalHandler.deviceNotReady() + } + } + + public func stop5GSignalUpdates() { + fiveGSignalHandler.reset() + track("Stop getting 5G Signal") + updateAllNotifyValue(false) + } + + // MARK:- 5G CBand Signal Handler + public func start5GCBandSignalUpdates(_ delegate: GMFG5GCBandSignalDelegate, useNotifications: Bool) { + fiveGCBandSignalHandler.setDelegate(delegates: [delegate, bluetoothDebugger]) + if let _peripheral = peripheral, let _c5GSignal = getCharacteristic(peripheral: _peripheral, fromType: .fiveGCBandSignal) { + track("Getting 5G C-Band Signal") + sendNotifications(for: .fiveGCBandSignal, shouldNotify: useNotifications) + fiveGCBandSignalHandler.get5GSignal(_peripheral, _c5GSignal) + } else { + let errorMessage = peripheral == nil ? GMFGConstant.BLE.LogMsg.peripheralError : GMFGConstant.BLE.LogMsg.characteristicError + track(errorMessage) + fiveGCBandSignalHandler.deviceNotReady() + } + } + + public func stop5GCBandSignalUpdates() { + fiveGCBandSignalHandler.reset() + track("Stop getting 5G C-Band Signal") + updateAllNotifyValue(false) + } + + // MARK:- Utility Functions + override func parseRSRPFromJSON(fiveGData: [String: Any]) -> Double? { + return (fiveGData["RSRP"] as? NSString)?.doubleValue ?? nil + } + + // MARK:- BluetoothConfigModel Functions + override func getBleAdvertiseName() -> String { + let remoteAdvData = bluetoothConfig?.advertisedData ?? "" + if GMFGBluetoothPair.isValidAdvertisedData(remoteAdvData) { + GMFGStorageManager.addEntries(dictionary: [GMFGConstant.BLE.advertisedData: remoteAdvData]) + return remoteAdvData + } else if let localData = GMFGStorageManager.retrieve(key: GMFGConstant.fiveGParams), let localAdvData = localData[GMFGConstant.BLE.advertisedData] as? String { + track(GMFGConstant.BLE.LogMsg.cachedAdvertised) + return localAdvData + } + return remoteAdvData + } + + func getCharacteristic(peripheral: CBPeripheral, fromType: CharacteristicType) -> CBCharacteristic? { + guard let service = bluetoothConfig?.services.first else { + track("SERVICE was empty in the BLE Module JSON") + return nil + } + + guard let expectedCharacteristicUUID = service.characteristics.first(where: {$0.name == fromType})?.uuid else { + track("No characteristic found for key \(fromType.rawValue) in the BLE Module JSON") + return nil + } + + guard let expectedServiceUUID = getSeviceUUIDs().first else { + track("No services found in the BLE Module JSON") + return nil + } + + guard peripheral.state == .connected else { + track("Tried to get a characteristic from the peripheral, but it is still in state: \(peripheral.state.rawValue)") + return nil + } + + guard let services = peripheral.services else { + return nil + } + + if services.count != 1 { + track("Found \(services.count) services!") + } + + guard let service = services.first(where: {$0.uuid == expectedServiceUUID}) else { + track("Could not find service: \(expectedServiceUUID) on device.") + return nil + } + + guard let characteristic = service.characteristics?.first(where: {$0.uuid == expectedCharacteristicUUID}) else { + track("Could not find characteristic: \(expectedCharacteristicUUID) on device.") + return nil + } + return characteristic + } + +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGConstant.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGConstant.swift new file mode 100644 index 0000000..01b5377 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGConstant.swift @@ -0,0 +1,87 @@ +// +// GMFGConstant.swift +// MVM5G +// +// Created by Shohrab Chowdhury on 6/23/20. +// Copyright © 2020 Kyle. All rights reserved. +// + +struct GMFGConstant { + struct BLE { + static let pin = "pin" + static let advertisedData = "advertisedData" + static let fiveGCurrentSignal = "currentSignal" + static let fiveGHighSignal = "highSignal" + static let fiveGLowSignal = "lowSignal" + static let firmwareVersion = "firmwareVersion" + static let fotaInProgress = "fotaInProgress" + static let fotaStatus = "FOTAStatus" + static let cpeBLEOperation = "CPEBLEOperation" + static let advertisePrefix = "VZ5G_RECEIVER_" + static let routerWifiSSID = "routerWifiSSID" + static let routerWifiPwd = "routerWifiPwd" + struct LogMsg { + static let startingBLE = "Starting BLE Connection" + static let leavingPage = "Disconnecting BT because user is leaving page" + static let noAdvertisedName = "No Advertised Name" + static let attemptInstallationMode = "Attempting to switch to Installation mode" + static let attemptOperationalMode = "Attempting to switch to Operational mode" + static let internetNotEnabled = "CPE Internet Not Enabled. Starting speed test anyway..." + static let attemptDisableBleOpMode = "Attempting to disable BLE and set Operational mode" + static let fotaGotVersion = "Got version from CPE" + static let fotaUnexpectedCPEStatus = "Unexpected FOTA status returned from CPE" + static let cachedAdvertised = "Using local cached advertising name" + static let peripheralError = "Peripheral not found" + static let characteristicError = "Characteristic not found" + static let unknownCPEResponse = "Unknown response from CPE" + static let bleModuleMissing = "Ble Module missing" + static let bleDecodeError = "Ble Module decocde error" + } + } + struct AR { + static let capable = "arCapable" + static let capableStatusReq = "arCapableStatusRequired" + static let deviceLocation = "deviceLocation" + static let overlayShown = "AROverlayShown" + static let infoAccessibilityLabel = "Information icon button" + } + struct Map { + static let fivegSignalExposures = "fivegSignalExposures" + static let antennaList = "antennasList" + static let antennaLocation = "antennaLocation" + static let selectedAntennaIndex = "selectedAntennaIndex" + static let maxZoomLevel: Float = 18.0 + } + struct Test { + static let hardcodeToggle = "isHardcode" + static let CPEToggle = "isCPESupported" + static let fiveGSignalToggle = "is5GSignalEnabled" + static let forceBCS = "isBCSForce" + static let visualDebugger = "isVisualDebugger" + } + struct LogMsg { + static let invalidTimer = "Timer is invalid" + } + + struct TimeTracker { + static let details = "GMFGTimeTrackingDetails" + } + + struct Glassbox { + static let fivegSetup = "5GSetup" + } + static let empty = "" + static let index = "index" + static let imei = "imei" + static let extenderMacId = "extenderMacId" + static let overlayShown = "OverlayShown" + static let fiveGParams = "fivegParams" + static let orderNumber = "orderNumber" + static let functionName = "functionName" + static let birthCertificate = "BirthCertificate" + static let feedbackShown = "isFeedbackShown" + static let feedback = "feedback" + static let clearCache = "clearCache" + static let blinkingColor = "blinkingColor" + static let blinkSec = "blinkSec" +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGLocationManager.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGLocationManager.swift new file mode 100644 index 0000000..10a0acc --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGLocationManager.swift @@ -0,0 +1,99 @@ +// +// GMFGLocationManager.swift +// MVM5G +// +// Created by Muthulingam, Muthuraj on 03/06/20. +// Copyright © 2020 Kyle. All rights reserved. +// + +import Foundation +import CoreLocation + +protocol GMFGLocationManagerDelegate: AnyObject { + func locationManager(_ manager: GMFGLocationManager, didUpdateLocations locations: [CLLocation]) + func locationManager(_ manager: GMFGLocationManager, didHeadUpdate newHeading: CLHeading) + func locationManager(_ manager: GMFGLocationManager, didUpdateLocationPermissions isAuthorized: Bool) +} + +final class GMFGLocationManager: NSObject { + + // MARK: - Public Properties + weak var delegate: GMFGLocationManagerDelegate? + static let shared = GMFGLocationManager() + var locationServicesEnabled: Bool { + if CLLocationManager.locationServicesEnabled() { + switch CLLocationManager.authorizationStatus() { + case .notDetermined, .restricted, .denied: + return false + case .authorizedAlways, .authorizedWhenInUse: + return true + @unknown default: + return false + } + } else { + return false + } + } + + /// Fetches Current Location if available + private(set) var currentLocation: CLLocation? + + // MARK: - Private Properties + private lazy var locationManager = CLLocationManager() + + /// to avoid called to create instance + private override init() { + super.init() + } + + // MARK: - Public Helpers + func prepareLocationManager() { + locationManager.delegate = self + locationManager.desiredAccuracy = kCLLocationAccuracyBest + locationManager.headingFilter = kCLHeadingFilterNone + locationManager.requestWhenInUseAuthorization() + locationManager.startUpdatingLocation() + locationManager.startUpdatingHeading() + } + + func startLocationMonitoring() { + locationManager.startUpdatingHeading() + locationManager.startUpdatingLocation() + } + + func stopLocationMonitoring() { + locationManager.stopUpdatingHeading() + locationManager.stopUpdatingLocation() + } +} + +extension GMFGLocationManager: CLLocationManagerDelegate { + + func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + switch status { + case .authorizedAlways, .authorizedWhenInUse: + locationManager.startUpdatingLocation() + locationManager.startUpdatingHeading() + case .denied, .notDetermined, .restricted: + delegate?.locationManager(self, didUpdateLocationPermissions: false) + /// Resets the location + currentLocation = nil + @unknown default: currentLocation = nil + } + } + + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + if let currentLocation = locations.last { + self.currentLocation = currentLocation + } + delegate?.locationManager(self, didUpdateLocations: locations) + } + + func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { + delegate?.locationManager(self, didHeadUpdate: newHeading) + } +} + +extension CLLocation { + var array: [Double] { return [coordinate.latitude, coordinate.longitude] } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGMapARViewControllerUtilities.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGMapARViewControllerUtilities.swift new file mode 100644 index 0000000..60a9938 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGMapARViewControllerUtilities.swift @@ -0,0 +1,123 @@ +// +// GMFGMapARViewControllerUtilities.swift +// MVM5G +// +// Created by Muthulingam, Muthuraj on 01/06/20. +// Copyright © 2020 Kyle. All rights reserved. +// + +import Foundation +import SceneKit + +public extension CLLocation { + func bearingToLocationRadian(_ destinationLocation: CLLocation) -> CGFloat { + + let lat1 = self.coordinate.latitude.degreesToRadians + let lon1 = self.coordinate.longitude.degreesToRadians + + let lat2 = destinationLocation.coordinate.latitude.degreesToRadians + let lon2 = destinationLocation.coordinate.longitude.degreesToRadians + + let dLon = lon2 - lon1 + + let y = sin(dLon) * cos(lat2) + let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon) + let radiansBearing = atan2(y, x) + + return CGFloat(radiansBearing) + } + + func bearingToLocationDegrees(destinationLocation: CLLocation) -> CGFloat { + return bearingToLocationRadian(destinationLocation).radiansToDegrees + } + + func bearingAngle(to location: CLLocation) -> Double { + + let latA = coordinate.latitude.degreesToRadians + let lonA = coordinate.longitude.degreesToRadians + + let latB = location.coordinate.latitude.degreesToRadians + let lonB = location.coordinate.longitude.degreesToRadians + + let longitudeDiff = lonB - lonA + + let y = sin(longitudeDiff) * cos(latB) + let x = cos(latA) * sin(latB) - sin(latA) * cos(latB) * cos(longitudeDiff) + + return atan2(y, x) + } +} + +public extension CGFloat { + var degreesToRadians: CGFloat { return self * .pi / 180 } + var radiansToDegrees: CGFloat { return self * 180 / .pi } +} + +private extension Double { + var degreesToRadians: Double { return Double(CGFloat(self).degreesToRadians) } + var radiansToDegrees: Double { return Double(CGFloat(self).radiansToDegrees) } +} + +public final class GMFGMapARViewControllerUtilities { + + private init() {} + + static func positionFromTransform(_ transform: matrix_float4x4, reversed: Bool = false) -> SCNVector3 { + + if reversed { + return SCNVector3Make(-transform.columns.3.x, -transform.columns.3.y, -transform.columns.3.z) + } + + return SCNVector3Make(transform.columns.3.x, transform.columns.3.y, transform.columns.3.z) + } + + static func position(model modelNode: SCNNode, byDistance distance: Float, fromUserLocation userLocation: CLLocation, toLocation location: CLLocation) { + modelNode.position = translateNodeFrom(userLocation: userLocation, toLocation: location, distance: distance) + modelNode.scale = scaleNode(distance, location: location) + } + + private static func translateNodeFrom(userLocation: CLLocation, toLocation location: CLLocation, distance: Float) -> SCNVector3 { + let locationTransform = transformMatrix(matrix: matrix_identity_float4x4, originLocation: userLocation, targetLocation: location, distance: distance) + return positionFromTransform(locationTransform) + } + + private static func scaleNode(_ distance: Float, location: CLLocation) -> SCNVector3 { + let scale = max( min( Float(1000/distance), 1.5 ), 3 ) + return SCNVector3(x: scale, y: scale, z: scale) + } + + private static func transformMatrix(matrix: simd_float4x4, originLocation: CLLocation, targetLocation: CLLocation, distance: Float) -> simd_float4x4 { + let bearingAngle = originLocation.bearingAngle(to: targetLocation) + let rotationMatrix = rotateAroundY(matrix_identity_float4x4, Float(bearingAngle)) + + var position = vector_float4(0.0, 0.0, -distance, 0.0) + + if distance > 1000.0 { + position = vector_float4(0.0, 0.0, -25.0, 0.0) + } + + let translationMatrix = getTranslationMatrix(matrix_identity_float4x4, position) + + let transformMatrix = simd_mul(rotationMatrix, translationMatrix) + + return simd_mul(matrix, transformMatrix) + } + + private static func getTranslationMatrix(_ matrix: simd_float4x4, _ translation : vector_float4) -> simd_float4x4 { + var matrix = matrix + matrix.columns.3 = translation + return matrix + } + + private static func rotateAroundY(_ matrix: simd_float4x4, _ degrees: Float) -> simd_float4x4 { + var matrix = matrix + + matrix.columns.0.x = cos(degrees) + matrix.columns.0.z = -sin(degrees) + + matrix.columns.2.x = sin(degrees) + matrix.columns.2.z = cos(degrees) + + return matrix.inverse + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGSelfInstallTimeTracker.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGSelfInstallTimeTracker.swift new file mode 100644 index 0000000..19eefd2 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGSelfInstallTimeTracker.swift @@ -0,0 +1,179 @@ +// +// GMFGSelfInstallTimeTracker.swift +// MVM5G +// +// Created by Gujuluva Santharam, Ajai Prabhu on 05/02/21. +// Copyright © 2021 Kyle. All rights reserved. +// + +import Foundation + +enum MFFGHSPageDisplayEvent { + case show + case hide(_ elapse: TimeDuration) +} + +final class GMFGSelfInstallTimeTracker { + // MARK: - Private Propeties + /* Elapsed will give us the actual time spent by user in the self setup */ + private(set) var elapsed: Double = GMFGStorageManager.retrieve(key: GMFGConstant.TimeTracker.details)?["elapsedTime"] as? Double ?? 0 + private var sessionStartTime: Double = Date().timeIntervalSince1970 // this will make sure not to calculate with zero + private var sessionEndTime: Double = 0 + private var pageStartTime: Double = 0 + private var didWentBgState: Bool = false // to track transion of bg/fg states to send analytics + private var foregroundObserver: NSObjectProtocol? + private var currentSessionInterval: Double = 0 + // MARK: - Public Properties + static var shared: GMFGSelfInstallTimeTracker = GMFGSelfInstallTimeTracker() + + // MARK: - Initialisers + private init() { + if #available(iOS 13.0, *) { + NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIScene.willDeactivateNotification, object: nil) + } else { + NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil) + } + + foregroundObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] notification in + self?.beginSession() + } + } + + deinit { + if #available(iOS 13.0, *) { + NotificationCenter.default.removeObserver(self, name: UIScene.willDeactivateNotification, object: nil) + } else { + NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) + } + NotificationCenter.default.removeObserver(foregroundObserver as Any) + } + + // MARK: - Public Helpers + + func beginSession() { + sessionStartTime = currentTime() + } + + func endSession() { + let newSessionEndTime = currentTime() + let previousSessionTime = elapsed + if sessionEndTime != 0 { + elapsed += newSessionEndTime - sessionEndTime + } else { + elapsed += newSessionEndTime - sessionStartTime + } + currentSessionInterval = elapsed - previousSessionTime + sessionEndTime = newSessionEndTime + + func handleLargeTiming() { + // NOTE: Fallback approach to avoid sending unimaginable time spent all sessions to server & Reset time + let currentAllSpendSessionTimeInMonths = elapsed.month + if currentAllSpendSessionTimeInMonths > 1 { + trackGemini(value: ["LASTHappen": true, + "LASTSReceived" : elapsed.millisecond, + "LASTSStartTime": sessionStartTime, + "LASTSEndTime": sessionEndTime, + "LASTSCurrentTime": newSessionEndTime], + logType: .UI) + elapsed = previousSessionTime + sessionStartTime = currentTime() + sessionEndTime = currentTime() + } + } + + handleLargeTiming() + GMFGStorageManager.addEntries(dictionary: ["elapsedTime": elapsed], key: GMFGConstant.TimeTracker.details) + } + + // MARK:- Page Tracking + + func trackPageAppear(with pageJSON: [String: Any]?) { + pageStartTime = currentTime() + } + + func trackPageDisappear(with pageJSON: [String: Any]?) { + let timeDuration = TimeDuration(start: pageStartTime, end: currentTime()) + endSession() + logPageDisplayEvent(of: .hide(timeDuration), with: pageJSON) + if didWentBgState { didWentBgState = false } // reset back to default state + } +} + +struct TimeDuration { + let start: TimeInterval + let end: TimeInterval +} + +extension TimeDuration { + var duration: TimeInterval { + return end - start + } +} + +// MARK: - Private Helpers +private extension GMFGSelfInstallTimeTracker { + + // MARK: - Private Helpers + func currentTime() -> Double { + return Date().timeIntervalSince1970 + } + + @objc + func willResignActive(_ notification: Notification) { + endSession() + didWentBgState = true // reset once logged + } + + func preparePageDisappear(with pageDuration: TimeDuration) -> [String: Any] { + var params: [String: Any] = fetchDefaultAdditionalInfo() + params["eventName"] = "stop" + params["startTime"] = Int(pageDuration.start.millisecond) + params["endTime"] = Int(pageDuration.end.millisecond) + params["timeSpendPage"] = Int(pageDuration.duration.millisecond) + params["didEnterBg"] = didWentBgState + return params + } + + func preparePageAppear() -> [String: Any] { + var params: [String: Any] = fetchDefaultAdditionalInfo() + params["eventName"] = "start" + params["timeSpendPage"] = 0 + return params + } + + func fetchDefaultAdditionalInfo() -> [String: Any] { + return [ "eventType": "pageDisplay", + "timeSpentAllSessions": Int(GMFGSelfInstallTimeTracker.shared.elapsed.millisecond), // to convert seconds to milliseconds + "timeSpentSession": Int(currentSessionInterval.millisecond) + ] as [String : Any] + } +} + +extension TimeInterval { + var hour: Int { + Int(self/3600) + } + var millisecond: Int { + Int(self*1000) + } + var day: Int { + hour/24 + } + var month: Int { + day/30 + } +} + +// MARK: - Analytics Protocol Conformance +extension GMFGSelfInstallTimeTracker: MFFGHSAnalyticsProtocol { + func logPageDisplayEvent(of type: MFFGHSPageDisplayEvent, with pageJson: [String: Any]?) { + let additionalData: [String: Any] + switch type { + case .show: + additionalData = preparePageAppear() + case let .hide(elapsed): + additionalData = preparePageDisappear(with: elapsed) + } + trackPage(data: pageJson, additionalData: additionalData) + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGStorageManager.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGStorageManager.swift new file mode 100644 index 0000000..e71946d --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGStorageManager.swift @@ -0,0 +1,50 @@ +// +// GMFGRequestHandler.swift +// MVM5G +// +// Created by Gujuluva Santharam, Ajai Prabhu on 07/07/20. +// Copyright © 2020 Kyle. All rights reserved. +// + +import UIKit + +class GMFGStorageManager { + + //MARK - user default base methods + class func store(dictionary: [AnyHashable : Any],for key:String) { + // MFUtility.save(dictionary, toUserDefaultsForKey: key) + } + + class func retrieve(key: String) -> [String: Any]? { + return UserDefaults.standard.dictionary(forKey: key) + } + + //Use this to append entries from dictionary to existing one + class func addEntries(dictionary: [String : Any], key: String = GMFGConstant.fiveGParams) { + let storedDict = GMFGStorageManager.retrieve(key: key) ?? [:] + let appendedDict = storedDict.merging(dictionary) { $1 } + GMFGStorageManager.store(dictionary: appendedDict, for: key) + } + + class func extraRequestParams() -> [String: Any] { + var storedDict = GMFGStorageManager.retrieve(key: GMFGConstant.fiveGParams) ?? [:] + if let currentLocation = GMFGLocationManager.shared.currentLocation { + storedDict[GMFGConstant.AR.deviceLocation] = currentLocation.array + } + return GMFGTestScreenData.shared.isHardcodedValues ? GMFGTestScreenData.shared.hardcodeDict : storedDict + } + + //MARK: - methods to remove from user defaults + class func remove(key: String) { + if var storedDict = GMFGStorageManager.retrieve(key: GMFGConstant.fiveGParams) { + storedDict.removeValue(forKey: key) + GMFGStorageManager.store(dictionary: storedDict, for: GMFGConstant.fiveGParams) + } + } + + class func removeAll() { + UserDefaults.standard.removeObject(forKey: GMFGConstant.fiveGParams) + UserDefaults.standard.removeObject(forKey: "selfInstallNextStep") + } +} + diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGTestScreen.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGTestScreen.swift new file mode 100644 index 0000000..c7028f2 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGTestScreen.swift @@ -0,0 +1,228 @@ +// +// GMFGTestScreen.swift +// MVM5G +// +// Created by Shohrab Chowdhury on 7/15/20. +// Copyright © 2020 Kyle. All rights reserved. +// + +import Foundation + +class GMFGTestScreen: TopLabelsAndBottomButtonsViewController { + + private var deviceLocationLatTextField, deviceLocationLongTextField, advertisedDataTextField, pinTextField, orderNumberTextField, imeiTextField, currentRssiTextField, highRssiTextField, lowRssiTextField, cpeVersionNumberTextField: MFTextField? + + private var isARCapableToggle, isCPESupportedToggle, isHardcodeToggle, isEnable5GSignalToggle, isBCSForce: UISwitch? + + var toggleval = [String: Bool]() + + + override public func newDataBuildScreen() { + super.newDataBuildScreen() + topLabelsView?.setHeadlineString("Gemini 5G Test Screen", messageString: "Click on load values button to load default values") + topLabelsView?.separatorView?.isHidden = false + } + + public override func buildViewsBetweenLabelsAndButtons() -> [UIView]? { + + var views:[UIView] = [] + + let locationText = GMFGTestScreenData.shared.deviceLocation + deviceLocationLatTextField = createTextField("Device Location Latitude", locationText.count > 0 && locationText[0] != GMFGConstant.empty ? locationText[0] : "29.72955022663853") + views.append(deviceLocationLatTextField ?? MFTextField()) + + deviceLocationLongTextField = createTextField("Device Location Longitude", locationText.count > 0 && locationText[0] != GMFGConstant.empty ? locationText[1] : "-95.370197614801185") + views.append(deviceLocationLongTextField ?? MFTextField()) + + let advertisedDataText = GMFGTestScreenData.shared.hardcodeDict.stringForkey(GMFGConstant.BLE.advertisedData) + advertisedDataTextField = createTextField("Advertised Data", advertisedDataText != GMFGConstant.empty ? advertisedDataText : "VZ5G_RECEIVER_203975") + views.append(advertisedDataTextField ?? MFTextField()) + + let pinText = GMFGTestScreenData.shared.hardcodeDict.stringForkey(GMFGConstant.BLE.pin) + pinTextField = createTextField("Pin", pinText != GMFGConstant.empty ? pinText : "00000") + views.append(pinTextField ?? MFTextField()) + + let orderNumberText = GMFGTestScreenData.shared.hardcodeDict.stringForkey(GMFGConstant.orderNumber) + orderNumberTextField = createTextField("Order Number", orderNumberText != GMFGConstant.empty ? orderNumberText : "3110116") + views.append(orderNumberTextField ?? MFTextField()) + + let imeiText = GMFGTestScreenData.shared.hardcodeDict.stringForkey(GMFGConstant.imei) + imeiTextField = createTextField("IMEI Number", imeiText != GMFGConstant.empty ? imeiText : "353450100203975") + views.append(imeiTextField ?? MFTextField()) + + let currentRSSIText = GMFGTestScreenData.shared.hardcodeDict.stringForkey(GMFGConstant.BLE.fiveGCurrentSignal) + currentRssiTextField = createTextField("Current Rssi", currentRSSIText != GMFGConstant.empty ? currentRSSIText : "-95") + views.append(currentRssiTextField ?? MFTextField()) + + let highRSSIText = GMFGTestScreenData.shared.hardcodeDict.stringForkey(GMFGConstant.BLE.fiveGHighSignal) + highRssiTextField = createTextField("High Rssi", highRSSIText != GMFGConstant.empty ? highRSSIText : "-98") + views.append(highRssiTextField ?? MFTextField()) + + let lowRSSIText = GMFGTestScreenData.shared.hardcodeDict.stringForkey(GMFGConstant.BLE.fiveGLowSignal) + lowRssiTextField = createTextField("Low Rssi", lowRSSIText != GMFGConstant.empty ? lowRSSIText : "-92") + views.append(lowRssiTextField ?? MFTextField()) + + let cpeVersionNumberText = GMFGTestScreenData.shared.cpeVersionNumber + cpeVersionNumberTextField = createTextField("CPE Version Number", cpeVersionNumberText != GMFGConstant.empty ? cpeVersionNumberText : "2.29.49.1") + views.append(cpeVersionNumberTextField ?? MFTextField()) + + let hardcodeToggleView = getSwitchView("Hardcoded Request", 1, GMFGConstant.Test.hardcodeToggle) + hardcodeToggleView.uiSwitch.isOn = GMFGTestScreenData.shared.isHardcodedValues + views.append(hardcodeToggleView.container) + + let arToggleView = getSwitchView("AR Capable", 2, GMFGConstant.AR.capable) + arToggleView.uiSwitch.isOn = GMFGTestScreenData.shared.isARCapable + views.append(arToggleView.container) + + let cpeToggleView = getSwitchView("CPE Simulation", 3, GMFGConstant.Test.CPEToggle) + cpeToggleView.uiSwitch.isOn = GMFGTestScreenData.shared.isCPESimulated + views.append(cpeToggleView.container) + + let fiveGSignalToggle = getSwitchView("Enable 5G Signal", 4, GMFGConstant.Test.fiveGSignalToggle) + fiveGSignalToggle.uiSwitch.isOn = GMFGTestScreenData.shared.isEnable5GSignal + views.append(fiveGSignalToggle.container) + + let fiveGBCSToggle = getSwitchView("Force BCS Wifi", 5, GMFGConstant.Test.forceBCS) + fiveGBCSToggle.uiSwitch.isOn = GMFGTestScreenData.shared.forceBCS + views.append(fiveGBCSToggle.container) + + let visualDebuggerToggle = getSwitchView("Visual Debugger", 6, GMFGConstant.Test.visualDebugger) + visualDebuggerToggle.uiSwitch.isOn = GMFGTestScreenData.shared.visualDebugger + views.append(visualDebuggerToggle.container) + + + /// Added Empty label to add extra space above button + let emptyLabel = Label() + emptyLabel.text = " " + views.append(emptyLabel) + + let doneButton = PrimaryButtonView(frame: .zero) + doneButton.primaryButton?.setTitle("Load Values", for: .normal) + doneButton.primaryButton?.addTarget(self, action: #selector(donePressed(sender:)), for: .touchUpInside) + doneButton.primaryButton?.isEnabled = true + views.append(doneButton) + + return views + } + + @objc func donePressed(sender: UIButton) { + setHardcodeValue() + dismiss(animated: false, completion: nil) + } + + @objc func switchValueDidChange(_ sender: UISwitch!) { + + switch sender.tag { + case 1: + toggleval[GMFGConstant.Test.hardcodeToggle] = sender.isOn + GMFGTestScreenData.shared.isHardcodedValues = toggleval.boolForKey(GMFGConstant.Test.hardcodeToggle) + case 2: + toggleval.updateValue(sender.isOn, forKey: GMFGConstant.AR.capable) + GMFGTestScreenData.shared.isARCapable = toggleval.boolForKey(GMFGConstant.AR.capable) + case 3: + toggleval.updateValue(sender.isOn, forKey: GMFGConstant.Test.CPEToggle) + GMFGTestScreenData.shared.isCPESimulated = toggleval.boolForKey(GMFGConstant.Test.CPEToggle) + case 4: + toggleval.updateValue(sender.isOn, forKey: GMFGConstant.Test.fiveGSignalToggle) + GMFGTestScreenData.shared.isEnable5GSignal = toggleval.boolForKey(GMFGConstant.Test.fiveGSignalToggle) + case 5: + toggleval.updateValue(sender.isOn, forKey: GMFGConstant.Test.forceBCS) + GMFGTestScreenData.shared.forceBCS = toggleval.boolForKey(GMFGConstant.Test.forceBCS) + case 6: + toggleval.updateValue(sender.isOn, forKey: GMFGConstant.Test.visualDebugger) + GMFGTestScreenData.shared.visualDebugger = toggleval.boolForKey(GMFGConstant.Test.visualDebugger) + default: + break + } + } + + override public func spaceAboveBetweenView() -> NSNumber? { + return 30 + } + + override func spaceAroundUIObject(_ object: Any?) -> UIEdgeInsets { + var insets = super.spaceAroundUIObject(object) + insets.top = PaddingOne + return insets + } + + private func createTextField(_ formLabel: String, _ text: String) -> MFTextField? { + let textField = MFTextField(bothDelegates: self) + textField?.formLabel?.text = formLabel + textField?.text = text as NSString? + return textField + } + + func getSwitchView(_ labelText: String, _ tag: Int, _ key: String) -> (container: UIView, uiSwitch: UISwitch, label: Label) { + let toggleView = MFCommonViewsUtility.commonView() + + let toggle = UISwitch(frame: .zero) + toggleView.addSubview(toggle) + toggle.tag = tag + toggle.addTarget(self, action: #selector(switchValueDidChange(_:)), for: .valueChanged) + + let arToggleLabel = Label() + arToggleLabel.text = labelText + toggleView.addSubview(arToggleLabel) + arToggleLabel.font = MFStyler.fontRegularBodyLarge() + + NSLayoutConstraint.constraintPinSubview(toggle, pinTop: true, pinBottom: true, pinLeft: true, pinRight: false) + NSLayoutConstraint.constraintPinSubview(arToggleLabel, pinTop: true, pinBottom: true, pinLeft: false, pinRight: true) + NSLayoutConstraint(item: toggle as Any, attribute: .width, relatedBy: .equal, toItem: toggleView, attribute: .width, multiplier: 0, constant: 0).isActive = true + NSLayoutConstraint(item: arToggleLabel as Any, attribute: .left, relatedBy: .equal, toItem: toggle, attribute: .right, multiplier: 1.0, constant: PaddingThree).isActive = true + + return (toggleView, toggle, arToggleLabel) + } + + func setHardcodeValue() { + let hardCodeValue: [String : Any] = [ + GMFGConstant.AR.deviceLocation : [ + deviceLocationLatTextField?.text?.doubleValue, + deviceLocationLongTextField?.text?.doubleValue + ], + GMFGConstant.AR.capable : GMFGTestScreenData.shared.isARCapable, + GMFGConstant.BLE.advertisedData : advertisedDataTextField?.text as Any, + GMFGConstant.BLE.firmwareVersion : (cpeVersionNumberTextField?.text)! as String, + GMFGConstant.BLE.pin : pinTextField?.text as Any, + GMFGConstant.orderNumber : orderNumberTextField?.text as Any, + GMFGConstant.imei : imeiTextField?.text as Any, + GMFGConstant.BLE.fiveGCurrentSignal : currentRssiTextField?.text as Any, + GMFGConstant.BLE.fiveGHighSignal : highRssiTextField?.text as Any, + GMFGConstant.BLE.fiveGLowSignal : lowRssiTextField?.text as Any + ] + + /// We can append to above dict but make sure whether backend is expecting below parameters as well when a call is made + let hardcodeToggleVal: [String: Bool] = [ + GMFGConstant.Test.hardcodeToggle : toggleval.boolForKey(GMFGConstant.Test.hardcodeToggle), + GMFGConstant.Test.CPEToggle : toggleval.boolForKey(GMFGConstant.Test.CPEToggle), + GMFGConstant.Test.fiveGSignalToggle : toggleval.boolForKey(GMFGConstant.Test.fiveGSignalToggle), + GMFGConstant.Test.forceBCS : toggleval.boolForKey(GMFGConstant.Test.forceBCS), + GMFGConstant.Test.visualDebugger : toggleval.boolForKey(GMFGConstant.Test.visualDebugger) + ] + + GMFGTestScreenData.shared.hardcodeDict = hardCodeValue + GMFGTestScreenData.shared.toggleDict = hardcodeToggleVal + GMFGTestScreenData.shared.deviceLocation = [(deviceLocationLatTextField?.text as String? ?? GMFGConstant.empty), deviceLocationLongTextField?.text as String? ?? GMFGConstant.empty] + GMFGTestScreenData.shared.cpeVersionNumber = (cpeVersionNumberTextField?.text)! as String + } +} + +class GMFGTestScreenData: NSObject { + var geminiHardCode = MFSessionSingleton.sharedGlobal()?.testObject?.is5GGoodSignalActive ?? false + var isCPESimulated = true && (MFSessionSingleton.sharedGlobal()?.testObject?.is5GGoodSignalActive ?? false) + var isHardcodedValues = false && (MFSessionSingleton.sharedGlobal()?.testObject?.is5GGoodSignalActive ?? false) + var isARCapable = true + var cpeVersionNumber = GMFGConstant.empty + var isEnable5GSignal = true && (MFSessionSingleton.sharedGlobal()?.testObject?.is5GGoodSignalActive ?? false) + var forceBCS = false + var visualDebugger = false + var deviceLocation: [String] = [] + var hardcodeDict: [String: Any] = [:] + var toggleDict: [String : Bool] = [:] + static let shared = GMFGTestScreenData() + + /// to avoid called to create instance + private override init() { + super.init() + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGTestScreenData.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGTestScreenData.swift new file mode 100644 index 0000000..b8ee312 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/GMFGTestScreenData.swift @@ -0,0 +1,30 @@ +// +// GMFGTestScreenData.swift +// JSONCreator +// +// Created by Matt Bruce on 5/11/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation +import UIKit + +class GMFGTestScreenData: NSObject { + var geminiHardCode = false + var isCPESimulated = true + var isHardcodedValues = false + var isARCapable = true + var cpeVersionNumber = GMFGConstant.empty + var isEnable5GSignal = true + var forceBCS = false + var visualDebugger = false + var deviceLocation: [String] = [] + var hardcodeDict: [String: Any] = [:] + var toggleDict: [String : Bool] = [:] + static let shared = GMFGTestScreenData() + + /// to avoid called to create instance + private override init() { + super.init() + } +} diff --git a/JSONCreator_iOS/JSONCreator/5G/Utility/Protocols/GMFGBLEHandlerProtocol.swift b/JSONCreator_iOS/JSONCreator/5G/Utility/Protocols/GMFGBLEHandlerProtocol.swift new file mode 100644 index 0000000..cb025ff --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/5G/Utility/Protocols/GMFGBLEHandlerProtocol.swift @@ -0,0 +1,21 @@ +// +// GMFGBLEHandlerProtocol.swift +// MVM5G +// +// Created by Jason Beck on 7/10/20. +// Copyright © 2020 Kyle. All rights reserved. +// +import Foundation + +protocol GMFGBLEHandlerProtocol { + func convertToData(_ dictionary: [String: Any]) -> Data? + func response(response: [String : Any]) + func deviceNotReady() +} + +extension GMFGBLEHandlerProtocol { + func convertToData(_ dictionary: [String: Any]) -> Data? { + do { return try JSONSerialization.data(withJSONObject: dictionary, options: []) } + catch { return nil } + } +} diff --git a/JSONCreator_iOS/JSONCreator/AppDelegate.swift b/JSONCreator_iOS/JSONCreator/AppDelegate.swift index 255916a..8e4892a 100644 --- a/JSONCreator_iOS/JSONCreator/AppDelegate.swift +++ b/JSONCreator_iOS/JSONCreator/AppDelegate.swift @@ -8,6 +8,7 @@ import UIKit import MVMCoreUI +import VDS @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { @@ -85,7 +86,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele // MARK: - Split view - func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { + func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewControlller:UIViewController, onto primaryViewController:UIViewController) -> Bool { //guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false } //guard let _ = secondaryAsNavController.topViewController as? DetailViewController else { return false } return true diff --git a/JSONCreator_iOS/JSONCreator/DetailViewController.swift b/JSONCreator_iOS/JSONCreator/DetailViewController.swift index 52dea55..c448c0d 100644 --- a/JSONCreator_iOS/JSONCreator/DetailViewController.swift +++ b/JSONCreator_iOS/JSONCreator/DetailViewController.swift @@ -19,6 +19,10 @@ class DetailViewController: UIViewController { guard textView.superview == nil else { return } + + modalPresentationStyle = .formSheet + MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn = self + view.addSubview(textView) if UIDevice.current.userInterfaceIdiom == .pad { textView.font = UIFont.systemFont(ofSize: 40) diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/5G-AccordianList.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/5G-AccordianList.json new file mode 100644 index 0000000..fe94d12 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/5G-AccordianList.json @@ -0,0 +1,215 @@ +{ + "Page": { + "pageType": "eagleSignalStrengthTips", + "template": "modalList", + "behaviors": [ + { + + } + ], + "header": { + "moleculeName": "header", + "molecule": { + "moleculeName": "headlineBody", + "headline": { + "moleculeName": "label", + "text": "Tips for finding a good 5G signal" + } + } + }, + "footer": { + "moleculeName": "footer", + "molecule": { + "moleculeName": "button", + "title": "OK", + "action": { + "actionType": "back", + } + } + }, + "molecules": [ + { + "moleculeName": "accordionListItem", + "hideLineWhenExpanded": true, + "selected": true, + "topPadding": 14, + "bottomPadding": 14, + "line": { + "moleculeName": "line", + "type":"none" + }, + "molecule": { + "moleculeName": "label", + "fontStyle": "BoldBodyLarge", + "text": "Face the 5G antenna." + }, + "molecules": [ + { + "moleculeName": "list1CTxt", + "topPadding": 0, + "body": { + "moleculeName": "label", + "text": "The words “Verizon Receiver” should face out towards the antenna. Use the 5G AR compass to reference the location of the antenna or find a new one." + }, + "link": { + "moleculeName": "link", + "title": "Open 5G AR compass", + "action": { + "actionType": "openPage", + "pageType": "forgotPasswordPage" + } + } + } + ] + }, + { + "moleculeName": "accordionListItem", + "hideLineWhenExpanded": true, + "selected": true, + "topPadding": 14, + "bottomPadding": 14, + "line": { + "moleculeName": "line", + "type":"none" + }, + "molecule": { + "moleculeName": "label", + "fontStyle": "BoldBodyLarge", + "text": "Find the clearest path to the antenna." + }, + "molecules": [ + { + "moleculeName": "listItem", + "topPadding": 0, + "molecule": { + "moleculeName": "label", + "text": "If indoors, test at a window first. If you don’t have a window that faces an antenna, or if you prefer to mount the Receiver to a wall, make sure it’s exterior-facing. Upstairs is recommended, or as high as possible in your location.\r\rIf outdoors, move away from trees and other obstacles. If at a railing, position the Receiver on the outer side of the railling, so the railing itself doesn’t block the signal." + } + } + ] + }, + { + "moleculeName": "accordionListItem", + "hideLineWhenExpanded": true, + "selected": true, + "topPadding": 14, + "bottomPadding": 14, + "line": { + "moleculeName": "line", + "type":"none" + }, + "molecule": { + "moleculeName": "label", + "fontStyle": "BoldBodyLarge", + "text": "Check the Receiver for a solid white light." + }, + "molecules": [ + { + "moleculeName": "list1CTxt", + "topPadding": 0, + "body": { + "moleculeName": "label", + "text": "When the Receiver finds a good signal, its LED light will be solid white." + }, + "link": { + "moleculeName": "link", + "title": "Receiver status lights", + "action": { + "actionType": "openPage", + "pageType": "forgotPasswordPage" + } + } + } + ] + }, + { + "moleculeName": "accordionListItem", + "hideLineWhenExpanded": true, + "selected": true, + "topPadding": 14, + "bottomPadding": 14, + "line": { + "moleculeName": "line", + "type":"none" + }, + "molecule": { + "moleculeName": "label", + "fontStyle": "BoldBodyLarge", + "text": "Explore more options." + }, + "molecules": [ + { + "moleculeName": "list1CTxt", + "topPadding": 0, + "body": { + "moleculeName": "label", + "text": "You may be able to test for a signal outdoors, or indoors if you haven’t already. Answer a few questions so we can help you determine what to do next." + }, + "link": { + "moleculeName": "link", + "title": "Learn what to do next", + "action": { + "actionType": "openPage", + "pageType": "forgotPasswordPage" + } + } + } + ] + } + ] + }, + "ModuleMap": { + "fivegBleUuid": { + "ResponseInfo": { + "locale": "EN", + "type": "Success", + "appSessionExtended": true, + "code": "00000", + "message": "0", + "userMessage": "0", + "topAlertTime": 0 + }, + "fivegBleUuid": { + "bleAPIVersion": "2.0", + "bleSignalThreshold": "-94", + "bleSignalLowerBound": "-140", + "bleSignalUpperBound": "-1", + "advertisedData": "VZ5G_RECEIVER_203975", + "pin": "000000", + "bleAdv": [ + { + "uuid": "00002a37-0000-1000-8000-00805f9b34fb" + } + ], + "service": [ + { + "uuid": "6FE382F6-7A33-4990-9D7B-F2770A161E68", + "characteristicUUID": { + "Fiveg": "6FE382F6-7A33-4990-9D7B-4C9002F90979", + "Fourg": "6FE382F6-7A33-4990-9D7B-4C9005F90979", + "Radiocontrol": "6FE382F6-7A33-4990-9D7B-4C9003F90979", + "Activation": "6FE382F6-7A33-4990-9D7B-4C9006F90979", + "RepeaterPair": "6FE382F6-7A33-4990-9D7B-4C9010F90979", + "RepeaterMode": "6FE382F6-7A33-4990-9D7B-4C9011F90979", + "RepeaterPairStatus": "6FE382F6-7A33-4990-9D7B-4C9012F90979", + "RepeaterOperations": "6FE382F6-7A33-4990-9D7B-4C9013F90979", + "ReceiverMode": "6FE382F6-7A33-4990-9D7B-4C9070F90979", + "RouterMode": "6FE382F6-7A33-4990-9D7B-4C9071F90979", + "RouterWifi": "6FE382F6-7A33-4990-9D7B-4C9072F90979", + "RouterPublicInternetAccess": "6FE382F6-7A33-4990-9D7B-4C9073F90979", + "RouterSpeedTest": "6FE382F6-7A33-4990-9D7B-4C9074F90979", + "RouterSpeedTestResults": "6FE382F6-7A33-4990-9D7B-4C9075F90979", + "WifiExtendersStatus": "6FE382F6-7A33-4990-9D7B-4C9076F90979", + "AddWifiExtender": "6FE382F6-7A33-4990-9D7B-4C9077F90979", + "PowerCycle": "6FE382F6-7A33-4990-9D7B-4C9078F90979", + "FOTACheck": "6FE382F6-7A33-4990-9D7B-4C9079F90979", + "FOTAStatus": "6FE382F6-7A33-4990-9D7B-4C907AF90979", + "RepeaterInfo": "1C120000-88DD-4E3E-AFC2-DF4300574E43" + } + } + ] + } + } + } +} + diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/AccountSecurity.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/AccountSecurity.json new file mode 100644 index 0000000..19f0e0e --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/AccountSecurity.json @@ -0,0 +1,194 @@ +{ + "ResponseInfo": { + "locale": "EN", + "server": "10-74-170-83.ebiz.verizon.com-mf_prepayss01", + "code": "00000", + "hideNotificationLogo": false, + "message": "0", + "buildNumber": "6698", + "type": "Success", + "requestId": "dbb1ee17-69fe-464e-8a82-9563b83422ab", + "topAlertTime": 0 + }, + "Page": { + "navigationBar": { + "moleculeName": "navigationBar", + "title": "Account security" + }, + "pageType": "tbd", + "cache": false, + "screenHeading": "Account security", + "template": "stack", + "header": { + "moleculeName": "header", + "molecule": { + "moleculeName": "headlineBody", + "headline": { + "moleculeName": "label", + "text": "Next up, let's secure this account." + }, + "body": { + "moleculeName": "label", + "attributes": [ + { + "topPadding": 30, + "length": 19, + "action": { + "actionType": "openPage", + "analyticsData": { + "vzdl.page.pageLinkName": "/mf/Manage Profile|Email Verify", + "vzdl.page.linkName": "Verify" + }, + "pageType": "whyWeAskingAEMPR" + }, + "location": 113, + "type": "action" + } + ], + "text": "We'll use this PIN and security question for verification and help accessing the account. Make sure the PIN meets these requirements." + } + } + }, + "stack": { + "moleculeName": "stack", + "molecules": [ + { + "moleculeName": "stackItem", + "horizontalAlignment": "leading", + "molecule": { + "moleculeName": "digitTextField", + "editable": true, + "title": "Account security PIN", + "digits": 4, + "fieldKey": "securityPIN" + } + }, + { + "moleculeName": "stackItem", + "horizontalAlignment": "leading", + "molecule": { + "moleculeName": "digitTextField", + "editable": true, + "title": "Confirm account security PIN", + "type": "secure", + "digits": 4, + "fieldKey": "confirmSecurityPIN" + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "dropDown", + "fieldKey": "securityQuestion", + "title": "Security question", + "options": [ + "What was the first live concert you attended?", + "Where did you and your spouse first meet?", + "What was your favorite place to visit as a child?", + "What was the first name of your first roommate?", + "What is the name of a memorable place?", + "What is the first name of your best friend?", + "What was your favorite restaurant in college?" + ], + "selectedIndex": 0 + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "textField", + "title": "Security answer", + "type": "text", + "fieldKey": "securityAnswer" + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "label", + "text": "Be sure to remember these details. They'll be needed to access the account later." + } + } + ] + }, + "footer": { + "moleculeName": "footer", + "molecule": { + "moleculeName": "twoButtonView", + "primaryButton": { + "groupName": "default", + "moleculeName": "button", + "title": "Next", + "action": { + "analyticsData": { + "vzdl.page.linkName": "Legal Disclosure" + }, + "fieldKey": "next", + "actionType": "openPage", + "extraParameters": { + "from": "none" + }, + "presentationStyle": "push", + "pageType": "legalDisclosurePR" + } + } + } + }, + "formRules": [ + { + "groupName": "default", + "rules": [ + { + "regex": "^(?!.*0123|.*1234|.*2345|.*3456|.*4567|.*5678|.*6789|.*3210|.*4321|.*5432|.*6543|.*7654|.*8765|.*9876|.*0000|.*1111|.*2222|.*3333|.*4444|.*5555|.*6666|.*7777|.*8888|.*9999).*$", + "type": "regex", + "fields": [ + "securityPIN" + ], + "errorMessage": { + "securityPIN": "Your PIN can't have numbers in order or repeating." + } + }, + { + "type": "equals", + "fields": [ + "securityPIN", + "confirmSecurityPIN" + ], + "errorMessage": { + "confirmSecurityPIN": "Your account PIN entries don't match. Try again." + } + }, + { + "type": "regex", + "regex": "^[A-Za-z0-9 ]{3,10}$", + "fields": [ + "securityAnswer" + ], + "errorMessage": { + "securityAnswer": "Your security answer can only letters, numbers, spaces, and periods." + } + }, + { + "type": "regex", + "regex": "^.{3,10}$", + "fields": [ + "securityAnswer" + ], + "errorMessage": { + "securityAnswer": "Your answer must be between 3-10 characters." + } + }, + { + "type": "allRequired", + "ruleId": "requiredRule", + "fields": [ + "securityPIN", + "confirmSecurityPIN", + "securityAnswer" + ] + } + ] + } + ] + } + } diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/DigitalFieldValidation.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/DigitalFieldValidation.json new file mode 100644 index 0000000..4720b28 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/DigitalFieldValidation.json @@ -0,0 +1,133 @@ +{ + "ResponseInfo": { + "locale": "EN", + "server": "10-74-170-83.ebiz.verizon.com-mf_prepayss01", + "code": "00000", + "hideNotificationLogo": false, + "message": "0", + "buildNumber": "6698", + "type": "Success", + "requestId": "dbb1ee17-69fe-464e-8a82-9563b83422ab", + "topAlertTime": 0 + }, + "Page": { + "navigationBar": { + "moleculeName": "navigationBar", + "title": "Account security" + }, + "pageType": "tbd", + "cache": false, + "screenHeading": "Account security", + "template": "stack", + "stack": { + "moleculeName": "stack", + "molecules": [ + { + "moleculeName": "stackItem", + "horizontalAlignment": "leading", + "molecule": { + "moleculeName": "digitTextField", + "editable": true, + "title": "Account security PIN", + "digits": 4, + "fieldKey": "securityPIN" + } + }, + { + "moleculeName": "stackItem", + "horizontalAlignment": "leading", + "molecule": { + "moleculeName": "digitTextField", + "editable": true, + "title": "Confirm account security PIN", + "type": "text", + "digits": 4, + "fieldKey": "confirmSecurityPIN" + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "textField", + "fieldKey": "preferredFirstName", + "type": "text", + "text": "", + "errorMessage": "Please enter a valid first name.", + "title": "Preferred First Name", + "feedback": "This will replace greeting name if you have one.", + "required": false + } + } + ] + }, + "footer": { + "moleculeName": "footer", + "molecule": { + "moleculeName": "twoButtonView", + "primaryButton": { + "groupName": "default", + "moleculeName": "button", + "title": "Next", + "action": { + "analyticsData": { + "vzdl.page.linkName": "Legal Disclosure" + }, + "fieldKey": "next", + "actionType": "openPage", + "extraParameters": { + "from": "none" + }, + "presentationStyle": "push", + "pageType": "legalDisclosurePR" + } + } + } + }, + "formRules": [ + { + "groupName": "default", + "rules": [ + { + "regex": "^[0-6]{0,4}$", + "type": "regex", + "fields": [ + "securityPIN" + ], + "errorMessage": { + "securityPIN": "Your PIN can't have numbers in order or repeating." + } + }, + { + "type": "equals", + "fields": [ + "securityPIN", + "confirmSecurityPIN" + ], + "errorMessage": { + "confirmSecurityPIN": "Your account PIN entries don't match. Try again." + } + }, + { + "regex": "^[0-9a-zA-Z@\\.\\-\\’_?]{0,25}$", + "ruleId": "anyAlphaNumeric", + "errorMessage": { + "preferredFirstName": "Only use for First Name" + }, + "type": "regex", + "fields": [ + "preferredFirstName" + ] + }, + { + "type": "allRequired", + "ruleId": "requiredRule", + "fields": [ + "securityPIN", + "confirmSecurityPIN" + ] + } + ] + } + ] + } + } diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/FormContactInfo.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/FormContactInfo.json index 56ee6c2..5b28f2a 100644 --- a/JSONCreator_iOS/JSONCreator/JSON/Samples/FormContactInfo.json +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/FormContactInfo.json @@ -22,7 +22,7 @@ { "moleculeName": "stackItem", "molecule": { - "moleculeName": "textField", + "moleculeName": "textView", "fieldKey": "firstName", "type": "text", "errorMessage": "Please enter a valid first name.", diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/HMP.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/HMP.json new file mode 100644 index 0000000..ad04bb7 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/HMP.json @@ -0,0 +1,196 @@ +{ + "ResponseInfo" : { + "locale" : "EN", + "server" : "loghost-mf_postpayss01", + "userMessage" : "0", + "code" : "00000", + "appSessionExtended" : true, + "message" : "0", + "mdn" : "7165723410", + "buildNumber" : "61", + "type" : "Success", + "requestId" : "12321a81-a48b-4023-94d9-7bd42af6d65a", + "topAlertTime" : 0 + }, + "Page" : { + "pageStatNames" : [ + "\/mf\/retail\/appt\/flow", + "\/mf\/retail\/appt\/reasons" + ], + "template" : "list", + "footer" : { + "moleculeName" : "footer", + "molecule" : { + "moleculeName" : "twoButtonView", + "primaryButton" : { + "groupName" : "default", + "moleculeName" : "button", + "title" : "Continue", + "action" : { + "analyticsData" : { + "vzdl.page.linkName" : "Continue" + }, + "extraParameters" : { + "destPageType" : "rtlApptsStoreLocator", + "FromStoreDetails" : "N" + }, + "actionType" : "openPage", + "title" : "Confirm", + "presentationStyle" : "push", + "pageType" : "launchApp" + } + } + } + }, + "molecules" : [ + { + "dropDown" : { + "moleculeName" : "dropDown", + "action" : { + "actionType" : "noop" + }, + "placeholder" : "Select a reason for visit", + "title" : "What can we help you with?", + "fieldKey" : "reason", + "type" : "text", + "options" : [ + "Account Support", + "Shop", + "Device Setup\/Troubleshooting", + "Other" + ] + }, + "line" : { + "moleculeName" : "line", + "type" : "none" + }, + "moleculeName" : "dropDownListItem", + "molecules" : [ + [ + { + "line" : { + "moleculeName" : "line", + "type" : "none" + }, + "topPadding" : 0, + "moleculeName" : "listItem", + "molecule" : { + "title" : "Relating to", + "fieldKey" : "subReason", + "moleculeName" : "dropDown", + "options" : [ + "Autopay question or issue", + "Billing issue", + "Misleading promise or information inquiry", + "Disconnect line", + "Payment arrangements", + "Promised promotion or offer", + "Other" + ], + "type" : "text", + "action" : { + "actionType" : "noop" + } + } + } + ], + [ + + ], + [ + + ], + [ + + ] + ] + } + ], + "cache" : false, + "header" : { + "moleculeName" : "header", + "molecule" : { + "moleculeName" : "headlineBody", + "headline" : { + "moleculeName" : "label", + "text" : "Make an appointment to see a representative." + }, + "body" : { + "moleculeName" : "label", + "text" : "We currently have limited staffing. For your safety and the safety of our employees, we are only scheduling appointments to provide one-on-one support for account maintenance or device troubleshooting.", + "fontStyle" : "BoldBodySmall" + } + } + }, + "tab" : [ + { + "appContext" : "mobileFirstSS", + "title" : "Schedule", + "actionType" : "openPage", + "moleculeName" : "label", + "presentationStyle" : "root", + "pageType" : "rtlReasonAndSubReasons" + }, + { + "appContext" : "mobileFirstSS", + "title" : "My Appointments", + "actionType" : "openPage", + "moleculeName" : "label", + "presentationStyle" : "root", + "pageType" : "rtlViewAllScheduledAppts" + } + ], + "pageType" : "rtlReasonAndSubReasons", + "suppressPostLaunchRequests" : false, + "formRules" : [ + { + "groupName": "default", + "rules": [ + { + "type": "allRequired", + "ruleId": "allRequired", + "fields": [ + "reason", + "subReason" + ] + } + ], + "effects": [ + { + "type": "dynamicRuleFormFieldEffect", + "fieldKey": "subReason", + "activatedRuleIds": ["allRequired"], + "rules": [ + { + "type": "regex", + "regex": "^Account Support$", + "fields": [ + "reason" + ] + } + ] + }, + { + "type": "enableFormFieldEffect", + "fieldKey": "subReason", + "rules": [ + { + "type": "regex", + "regex": "^Account Support$", + "fields": [ + "reason" + ] + } + ] + } + ] + } + ], + "navigationBar" : { + "moleculeName" : "navigationBar", + "title" : "Make Appointment" + }, + "tabBarHidden" : true, + "isAtomicTabs" : true + } +} diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/MVA3.0/RadioButtons.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/MVA3.0/RadioButtons.json index 6e84fb7..25070eb 100644 --- a/JSONCreator_iOS/JSONCreator/JSON/Samples/MVA3.0/RadioButtons.json +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/MVA3.0/RadioButtons.json @@ -6,88 +6,85 @@ "Page": { "pageType":"x", "template":"list", - "header": { - - }, - "footer":{ - - }, + "header": {}, + "footer":{}, "molecules": [{ - "moleculeName":"listLVRBImg", - "radioButton":{ -"moleculeName":"radioButton", -}, -"image": { -"moleculeName":"image", -"image": "https://mobile.vzw.com/hybridClient/is/image/VerizonWireless/iPhoneXr_Black_PureAngles" -}, -"eyebrowHeadlineBodyLink": { -"moleculeName": "eyebrowHeadlineBodyLink", -"eyebrow":{ - "moleculeName": "label", - "text":"Eyebrow" -}, -"headline":{ - "moleculeName": "label", - "text":"Headline" -}, -"body":{ - "moleculeName": "label", - "text":"Body" -}, -"link":{ - "moleculeName": "link", - "title":"TextButton", - "action": { - "actionType": "cancel" - } -} -} + "moleculeName":"listLVRBImg", + "radioButton":{ + "moleculeName":"radioButton" + }, + "image": { + "moleculeName":"image", + "image": "https://mobile.vzw.com/hybridClient/is/image/VerizonWireless/iPhoneXr_Black_PureAngles" + }, + "eyebrowHeadlineBodyLink": { + "moleculeName": "eyebrowHeadlineBodyLink", + "eyebrow":{ + "moleculeName": "label", + "text":"Eyebrow" + }, + "headline":{ + "moleculeName": "label", + "text":"Headline" + }, + "body":{ + "moleculeName": "label", + "text":"Body" + }, + "link":{ + "moleculeName": "link", + "title":"TextButton", + "action": { + "actionType": "cancel" + } + } + } }, -{ - "moleculeName":"listLVRBBdy", - "radioButton":{ -"moleculeName":"radioButton", -}, -"headlineBody": { -"moleculeName": "headlineBody", -"headline":{ - "moleculeName": "label", - "text":"Headline" -}, -"body":{ - "moleculeName": "label", - "text":"Body" -} -} - },{ - "moleculeName":"listLVRBAll", - "radioButton":{ -"moleculeName":"radioButton", -}, -"eyebrowHeadlineBodyLink": { -"moleculeName": "eyebrowHeadlineBodyLink", -"eyebrow":{ - "moleculeName": "label", - "text":"Eyebrow" -}, -"headline":{ - "moleculeName": "label", - "text":"Headline" -}, -"body":{ - "moleculeName": "label", - "text":"Body" -}, -"link":{ - "moleculeName": "link", - "title":"TextButton", - "action": { - "actionType": "cancel" - } -} -} - } -] + { + "moleculeName":"listLVRBBdy", + "radioButton":{ + "moleculeName":"radioButton" + }, + "headlineBody": { + "moleculeName": "headlineBody", + "headline":{ + "moleculeName": "label", + "text":"Headline" + }, + "body":{ + "moleculeName": "label", + "text":"Body" + } + } + },{ + "moleculeName":"listLVRBAll", + "radioButton":{ + "moleculeName":"radioButton" + }, + "eyebrowHeadlineBodyLink": { + "moleculeName": "eyebrowHeadlineBodyLink", + "eyebrow":{ + "moleculeName": "label", + "text":"Eyebrow" + }, + "headline":{ + "moleculeName": "label", + "text":"Headline" + }, + "body":{ + "moleculeName": "label", + "text":"Body" + }, + "link":{ + "moleculeName": "link", + "title":"TextButton", + "action": { + "actionType": "cancel" + } + } + } + } + ] } } + diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/RadioButtonGroup.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/RadioButtonGroup.json new file mode 100644 index 0000000..3b7e8d9 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/RadioButtonGroup.json @@ -0,0 +1,112 @@ +{ + "ResponseInfo": { + "locale": "EN", + "server": "loghost-mf_postpayss01", + "userMessage": "0", + "code": "00000", + "appSessionExtended": true, + "message": "0", + "mdn": "5162733172", + "buildNumber": "1287", + "type": "Success", + "requestId": "288cb5d1-52a1-4a71-aeaf-55e4d0bcc275", + "topAlertTime": 0 + }, + "Page": { + "footer": { + "moleculeName": "footer", + "molecule": { + "moleculeName": "twoButtonView", + "primaryButton": { + "groupName": "default", + "moleculeName": "button", + "title": "Call Us", + "action": { + "actionType": "openPage", + "pageType": "callUsSubmit" + } + } + } + }, + "formRules": [ + { + "groupName": "default", + "rules": [ + { + "type": "anyRequired", + "fields": [ + "rB1" + ] + } + ] + } + ], + "pageType": "list", + "molecules": [ + { + "moleculeName": "listLVRBBdy", + "radioButton": { + "moleculeName": "radioButton", + "fieldKey": "rB1", + "fieldValue": "Radio Button 1" + }, + "headlineBody": { + "moleculeName": "headlineBody", + "headline": { + "moleculeName": "label", + "text": "Lorem ipsum dolor sit amet" + }, + "body": { + "moleculeName": "label", + "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + } + } + }, + { + "moleculeName": "listLVRBBdy", + "radioButton": { + "moleculeName": "radioButton", + "fieldKey": "rB1", + "fieldValue": "Radio Button 2" + }, + "headlineBody": { + "moleculeName": "headlineBody", + "headline": { + "moleculeName": "label", + "text": "Lorem ipsum dolor sit amet" + }, + "body": { + "moleculeName": "label", + "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + } + } + }, + { + "moleculeName": "listLVRBBdy", + "radioButton": { + "moleculeName": "radioButton", + "fieldKey": "rB1", + "fieldValue": "Radio Button 3" + }, + "headlineBody": { + "moleculeName": "headlineBody", + "headline": { + "moleculeName": "label", + "text": "Lorem ipsum dolor sit amet" + }, + "body": { + "moleculeName": "label", + "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit" + } + } + } + ], + "suppressPostLaunchRequests": false, + "tabBarHidden": false, + "template": "list", + "navigationBar": { + "moleculeName": "navigationBar", + "title": "Call Us" + } + } +} diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/TabsSample.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/TabsSample.json index f9a61ae..92caec3 100644 --- a/JSONCreator_iOS/JSONCreator/JSON/Samples/TabsSample.json +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/TabsSample.json @@ -44,7 +44,8 @@ } }, - "molecules": [{ + "molecules": [ + { "moleculeName": "tabsListItem", "tabs": { "moleculeName": "tabs", diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/TileleListSample.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/TileleListSample.json new file mode 100644 index 0000000..4e56264 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/TileleListSample.json @@ -0,0 +1,110 @@ +{ + "ResponseInfo": {}, + "Page": { + "pageType": "contactUs", + "screenHeading": "Select an international plan", + "template": "list", + "molecules": [ + { + "moleculeName": "listItem", + "molecule": { + "directionalIcon": { + "size": "medium" + }, + "moleculeName": "tilelet", + "subTitle": { + "text": "You are enrolled in Auto Pay & paper-free billing." + }, + "title": { + "text": "You’re getting $50 off on your monthly bill." + }, + "action": { + "actionType": "openPage", + "analyticsData": { + "vzdl.page.displayChannel": "mva", + "vzwi.mvmapp.pageTypeLink": "settingsLanding|Auto Pay discount", + "vzdl.page.id": "settingslanding", + "vzdl.page.linkName": "Auto Pay discount", + "vzdl.page.sourceChannel": "mva", + "vzdl.page.name": "settings landing" + }, + "pageType": "managePmts", + "presentationStyle": "push", + "requestURL": "https://mobile-exp-qa2.vzw.com/mobile/nsa/nos/gw/launchapp/l2/webview", + "extraParameters": { + "pageTitle": "Auto Pay discount", + "screenHeading": "Auto Pay discount", + "browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/payment/settings#/enrollAandP", + "locale": "EN", + "flowName": "accountsettings" + }, + "title": "Auto Pay discount" + } + } + }, + { + "moleculeName":"listItem", + "molecule": { + "moleculeName": "twoButtonView", + "primaryButton": { + "moleculeName": "button", + "title": "Edit", + "groupName": "default", + "action": { + "actionType": "openPage", + "pageType": "updateProfile", + "extraParameters": { + "from": "none" + }, + "presentationStyle": "push" + } + } + } + },{ + "moleculeName":"listItem", + "molecule": { + "moleculeName": "label", + "text": "afa\ndasfdsa\nadfadfda\nasadfsafa\nafsafsa\nafsadfas\nadffafaf" + } + },{ + "moleculeName":"listItem", + "molecule": { + "moleculeName": "label", + "text": "afa\ndasfdsa\nadfadfda\nasadfsafa\nafsafsa\nafsadfas\nadffafaf" + } + },{ + "moleculeName":"listItem", + "molecule": { + "moleculeName": "label", + "text": "afa\ndasfdsa\nadfadfda\nasadfsafa\nafsafsa\nafsadfas\nadffafaf\ndafsdssfafs" + } + },{ + "moleculeName":"listItem", + "molecule": { + "moleculeName": "label", + "text": "afa\ndasfdsa\nadfadfda\nasadfsafa\nafsafsa\nafsadfas\nadffafaf\n\nadsfa\nadfs" + } + },{ + "moleculeName":"listItem", + "molecule": { + "moleculeName": "label", + "text": "afa\ndasfdsa\nadfadfda\nasadfsafa\nafsafsa\nafsadfas\nadffafafttttt" + } + },{ + "moleculeName":"listItem", + "molecule": { + "moleculeName": "label", + "text": "afa\ndasfdsa\nadfadfda\nasadfsafa\nafsafsa\nafsadfas\nadffafaf\n\nadsfa\nadfs" + } + },{ + "moleculeName":"listItem", + "molecule": { + "moleculeName": "label", + "text": "afa\ndasfdsa\nadfadfda\nasadfsafa\nafsafsa\nafsadfas\nadffafafttttt" + } + } + ], + "middle": { + } + } +} diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/TileletSample.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/TileletSample.json new file mode 100644 index 0000000..e9b5dda --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/TileletSample.json @@ -0,0 +1,64 @@ +{ + "Page": { + "template": "stack", + "pageType": "moleculeStack", + "screenHeading": "Tilet Sample", + "hideFabOverlay": true, + "suppressPostLaunchRequests": false, + "tabBarHidden": true, + "header": { + "moleculeName": "header", + "molecule": { + "moleculeName": "headlineBody", + "headline": { + "moleculeName": "label", + "text": "Tilet Variations" + } + } + }, + "stack": { + "moleculeName": "stack", + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": + { + "directionalIcon": { + "size": "medium" + }, + "moleculeName": "tilelet", + "subTitle": { + "text": "You are enrolled in Auto Pay & paper-free billing." + }, + "title": { + "text": "You’re getting $50 off on your monthly bill." + }, + "action": { + "actionType": "openPage", + "analyticsData": { + "vzdl.page.displayChannel": "mva", + "vzwi.mvmapp.pageTypeLink": "settingsLanding|Auto Pay discount", + "vzdl.page.id": "settingslanding", + "vzdl.page.linkName": "Auto Pay discount", + "vzdl.page.sourceChannel": "mva", + "vzdl.page.name": "settings landing" + }, + "pageType": "managePmts", + "presentationStyle": "push", + "requestURL": "https://mobile-exp-qa2.vzw.com/mobile/nsa/nos/gw/launchapp/l2/webview", + "extraParameters": { + "pageTitle": "Auto Pay discount", + "screenHeading": "Auto Pay discount", + "browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/payment/settings#/enrollAandP", + "locale": "EN", + "flowName": "accountsettings" + }, + "title": "Auto Pay discount" + } + } + } + ] + }, + "footer": {} + } +} diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/ToggleSample.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/ToggleSample.json new file mode 100644 index 0000000..e9a76a0 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/ToggleSample.json @@ -0,0 +1,60 @@ +{ + "Page": { + "template": "stack", + "pageType": "moleculeStack", + "screenHeading": "Manage Profile", + "hideFabOverlay": true, + "suppressPostLaunchRequests": false, + "tabBarHidden": true, + "header": { + "moleculeName": "header", + "molecule": { + "moleculeName": "headlineBody", + "headline": { + "moleculeName": "label", + "text": "Zenkey" + } + } + }, + "stack": { + "moleculeName": "stack", + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "toggle", + "fieldKey": "isActive" + } + } + ] + }, + "footer": { + "moleculeName": "footer", + "molecule": { + "moleculeName": "twoButtonView", + "primaryButton": { + "moleculeName": "button", + "title": "Edit", + "groupName": "default", + "action": { + "actionType": "openPage", + "pageType": "updateProfile", + "extraParameters": { + "from": "none" + }, + "presentationStyle": "push" + } + } + } + }, + "formRules": [ + { + "groupName": "default", + "rules": [ + + ] + } + ] + } +} + diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/Wifi/Wifi-1.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/Wifi/Wifi-1.json new file mode 100644 index 0000000..952922d --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/Wifi/Wifi-1.json @@ -0,0 +1,637 @@ +{ + "Page": { + "template": "list", + "pageType": "wifiSample1", + "screenHeading": "Network Management", + "hideFabOverlay": true, + "suppressPostLaunchRequests": false, + "tabBarHidden": true, + "line": { + "moleculeName": "line", + "type": "none" + }, + "header": { + "moleculeName": "header", + "molecule": { + "moleculeName": "headlineBody", + "headline": { + "moleculeName": "label", + "text": "Wi-Fi networks" + } + }, + "line": { + "moleculeName": "line", + "type": "none" + } + }, + "molecules": [ + { + "moleculeName": "tabsListItem", + "tabs": { + "moleculeName": "tabs", + "tabs": [ + { + "label": { + "moleculeName": "label", + "text": "Primary" + } + }, + { + "label": { + "moleculeName": "label", + "text": "Guest" + } + }, + { + "label": { + "moleculeName": "label", + "text": "IoT" + } + } + ] + }, + "molecules": [ + [ + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "container", + "backgroundColor": "#F6F6F6", + "molecule": { + "moleculeName": "stack", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 16, + "useHorizontalMargins": true, + "useVerticalMargins": true, + "spacing": 16, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "stack", + "spacing": 8, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "labelToggle", + "label": { + "moleculeName": "label", + "text": "West Coast Avocado Toast", + "fontStyle": "BoldBodyLarge" + }, + "toggle": { + "moleculeName": "toggle", + "state": true, + "action": { + "actionType": "toggleWifi", + "enabled": true, + "wifiId": "primary-1" + } + } + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "label", + "text": "Primary Wi-Fi (2.4 Ghz)", + "fontStyle": "RegularMicro" + } + } + ] + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "textField", + "fieldKey": "preferredLastName", + "type": "password", + "text": "12345", + "readOnly": true + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "stack", + "axis": "horizontal", + "spacing": 8, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "link", + "title": "Edit Wi-Fi details", + "action": { + "actionType": "openPage", + "pageType": "editWifi", + "extraParameters": { + "wifiType": "primary" + } + } + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "link", + "title": "Share Wi-Fi", + "action": { + "actionType": "share", + "sharedText": "123456", + "sharedType": "Text" + } + } + } + ] + } + } + ] + } + } + }, + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "container", + "backgroundColor": "#F6F6F6", + "molecule": { + "moleculeName": "stack", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 16, + "useHorizontalMargins": true, + "useVerticalMargins": true, + "spacing": 16, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "stack", + "spacing": 8, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "labelToggle", + "label": { + "moleculeName": "label", + "text": "New England Clam Router", + "fontStyle": "BoldBodyLarge" + }, + "toggle": { + "moleculeName": "toggle", + "state": false, + "action": { + "actionType": "toggleWifi", + "enabled": false, + "wifiId": "primary-2" + } + } + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "label", + "text": "Primary Wi-Fi (5 Ghz)", + "fontStyle": "RegularMicro" + } + } + ] + } + } + ] + } + } + }, + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "line": { + "moleculeName": "line", + "type": "standard" + }, + "molecule": { + "moleculeName": "container", + "backgroundColor": "#F6F6F6", + "molecule": { + "moleculeName": "stack", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 16, + "useHorizontalMargins": true, + "useVerticalMargins": true, + "spacing": 16, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "stack", + "spacing": 8, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "labelToggle", + "label": { + "moleculeName": "label", + "text": "Chicago Pizza", + "fontStyle": "BoldBodyLarge" + }, + "toggle": { + "moleculeName": "toggle", + "state": true, + "action": { + "actionType": "toggleWifi", + "enabled": true, + "wifiId": "primary-3" + } + } + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "label", + "text": "Primary Wi-Fi (5 Ghz 2)", + "fontStyle": "RegularMicro" + } + } + ] + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "textField", + "fieldKey": "preferredLastName", + "type": "password", + "text": "12345", + "readOnly": true + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "stack", + "axis": "horizontal", + "spacing": 8, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "link", + "title": "Edit Wi-Fi details", + "action": { + "actionType": "openPage", + "pageType": "editWifi", + "extraParameters": { + "wifiType": "primary" + } + } + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "link", + "title": "Share Wi-Fi", + "action": { + "actionType": "share", + "sharedText": "123456", + "sharedType": "Text" + } + } + } + ] + } + } + ] + } + } + }, + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "container", + "backgroundColor": "#F6F6F6", + "molecule": { + "moleculeName": "stack", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 16, + "useHorizontalMargins": true, + "useVerticalMargins": true, + "spacing": 16, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "label", + "backgroundColor": "#000000", + "textColor": "#ffffff", + "text": "Disabled" + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "label", + "fontStyle": "BoldBodyLarge", + "text": "SON (Self-Organizing Network)" + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "label", + "text": "Enable SON to automatically connect your devices to the fastest available Wi-Fi network." + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "link", + "title": "Details about SON", + "action": { + "actionType": "openPage", + "pageType": "sonDetails", + "extraParameters": { + "wifiType": "primary" + } + } + } + } + ] + } + } + } + ], + [ + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "label", + "text": "Limits access to devices in your home, recommended for guests" + } + }, + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "container", + "backgroundColor": "#F6F6F6", + "molecule": { + "moleculeName": "stack", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 16, + "useHorizontalMargins": true, + "useVerticalMargins": true, + "spacing": 16, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "stack", + "spacing": 8, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "labelToggle", + "label": { + "moleculeName": "label", + "text": "You Don't Belong Here", + "fontStyle": "BoldBodyLarge" + }, + "toggle": { + "moleculeName": "toggle", + "state": true, + "action": { + "actionType": "toggleWifi", + "enabled": true, + "wifiId": "guest-1" + } + } + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "label", + "text": "Guest Wi-Fi (2.4 Ghz)", + "fontStyle": "RegularMicro" + } + } + ] + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "textField", + "fieldKey": "preferredLastName", + "type": "password", + "text": "12345", + "readOnly": true + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "stack", + "axis": "horizontal", + "spacing": 8, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "link", + "title": "Edit Wi-Fi details", + "action": { + "actionType": "openPage", + "pageType": "editWifi", + "extraParameters": { + "wifiType": "primary" + } + } + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "link", + "title": "Share Wi-Fi", + "action": { + "actionType": "share", + "sharedText": "123456", + "sharedType": "Text" + } + } + } + ] + } + } + ] + } + } + } + ], + [ + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "label", + "text": "Recommended for optimal connectivity to \"Internet of Things\" (IoT) devices, for example smart speakers, plugs, and light switches." + } + }, + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "container", + "backgroundColor": "#F6F6F6", + "molecule": { + "moleculeName": "stack", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 16, + "useHorizontalMargins": true, + "useVerticalMargins": true, + "spacing": 16, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "stack", + "spacing": 8, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "labelToggle", + "label": { + "moleculeName": "label", + "text": "Connected Homies", + "fontStyle": "BoldBodyLarge" + }, + "toggle": { + "moleculeName": "toggle", + "state": true, + "action": { + "actionType": "toggleWifi", + "enabled": true, + "wifiId": "iot-1" + } + } + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "label", + "text": "IoT (2.4 Ghz)", + "fontStyle": "RegularMicro" + } + } + ] + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "textField", + "fieldKey": "preferredLastName", + "type": "password", + "text": "12345", + "readOnly": true + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "stack", + "axis": "horizontal", + "spacing": 8, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "link", + "title": "Edit Wi-Fi details", + "action": { + "actionType": "openPage", + "pageType": "editWifi", + "extraParameters": { + "wifiType": "primary" + } + } + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "link", + "title": "Share Wi-Fi", + "action": { + "actionType": "share", + "sharedText": "123456", + "sharedType": "Text" + } + } + } + ] + } + } + ] + } + } + } + ] + ] + } + ], + "footer": {} + } +} + diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/Wifi/Wifi-2.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/Wifi/Wifi-2.json new file mode 100644 index 0000000..77b30cb --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/Wifi/Wifi-2.json @@ -0,0 +1,357 @@ +{ + "Page": { + "template": "list", + "pageType": "wifiSample1", + "screenHeading": "Network Management", + "hideFabOverlay": true, + "suppressPostLaunchRequests": false, + "tabBarHidden": true, + "line": { + "moleculeName": "line", + "type": "none" + }, + "header": { + "moleculeName": "header", + "molecule": { + "moleculeName": "headlineBody", + "headline": { + "moleculeName": "label", + "text": "Wi-Fi networks" + } + }, + "line": { + "moleculeName": "line", + "type": "none" + } + }, + "molecules": [ + { + "moleculeName": "tabsListItem", + "tabs": { + "moleculeName": "tabs", + "tabs": [ + { + "label": { + "moleculeName": "label", + "text": "Primary" + } + }, + { + "label": { + "moleculeName": "label", + "text": "Guest" + } + }, + { + "label": { + "moleculeName": "label", + "text": "IoT" + } + } + ] + }, + "molecules": [ + [ + { + "moleculeName": "listItem", + "verticalAlignment": "leading", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "enabled": false, + "molecule": { + "moleculeName": "toggle" + } + }, + { + "moleculeName": "listItem", + "verticalAlignment": "leading", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "disabled": true, + "molecule": { + "moleculeName": "testToggle" + } + }, + { + "moleculeName": "listItem", + "verticalAlignment": "leading", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "wifiWidget", + "backgroundColor": "#F6F6F6", + "wifiId": "primary-1", + "title": "West Coast Avocado Toast", + "enabled": true, + "enabledAction": { + "actionType": "toggleWifi", + "enabled": true, + "wifiId": "primary-1", + }, + "description": "Primary Wi-Fi (2.4 Ghz)", + "password": "1234567", + "editTitle": "Edit Wi-Fi details", + "editAction": { + "actionType": "openPage", + "pageType": "editWifi", + "extraParameters": { + "wifiId": "primary-1" + } + }, + "shareTitle": "Share Wi-Fi", + "shareAction": { + "actionType": "share", + "sharedText": "ssid: primary-1 password: 123456", + "sharedType": "Text" + } + } + }, + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "wifiWidget", + "backgroundColor": "#F6F6F6", + "wifiId": "primary-2", + "title": "New England Clam Router", + "enabled": false, + "enabledAction": { + "actionType": "toggleWifi", + "enabled": false, + "wifiId": "primary-2" + }, + "description": "Primary Wi-Fi (5.0 Ghz)", + "password": "1234567", + "editTitle": "Edit Wi-Fi details", + "editAction": { + "actionType": "openPage", + "pageType": "editWifi", + "extraParameters": { + "wifiId": "primary-2" + } + }, + "shareTitle": "Share Wi-Fi", + "shareAction": { + "actionType": "share", + "sharedText": "ssid: primary-2 password: 123456", + "sharedType": "Text" + } + } + }, + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "line": { + "moleculeName": "line", + "type": "standard" + }, + "molecule": { + "moleculeName": "wifiWidget", + "backgroundColor": "#F6F6F6", + "wifiId": "primary-3", + "title": "Chicago Pizza", + "enabled": true, + "enabledAction": { + "actionType": "toggleWifi", + "enabled": true, + "wifiId": "primary-3" + }, + "description": "Primary Wi-Fi (5.0 Ghz 2)", + "password": "1234567", + "editTitle": "Edit Wi-Fi details", + "editAction": { + "actionType": "openPage", + "pageType": "editWifi", + "extraParameters": { + "wifiId": "primary-3" + } + }, + "shareTitle": "Share Wi-Fi", + "shareAction": { + "actionType": "share", + "sharedText": "ssid: primary-3 password: 123456", + "sharedType": "Text" + } + } + }, + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "container", + "backgroundColor": "#F6F6F6", + "molecule": { + "moleculeName": "stack", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 16, + "useHorizontalMargins": true, + "useVerticalMargins": true, + "spacing": 16, + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "label", + "leftPadding": 5, + "rightPadding": 5, + "topPadding": 2, + "bottomPadding": 2, + "backgroundColor": "#000000", + "textColor": "#ffffff", + "text": "Disabled" + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName":"headlineBodyLink", + "headlineBody":{ + "moleculeName": "headlineBody", + "headline":{ + "moleculeName": "label", + "fontStyle": "BoldBodyLarge", + "text": "SON (Self-Organizing Network)" + }, + "body":{ + "moleculeName": "label", + "text": "Enable SON to automatically connect your devices to the fastest available Wi-Fi network." + } + }, + "link":{ + "moleculeName": "link", + "title": "Details about SON", + "action": { + "actionType": "openPage", + "pageType": "sonDetails", + "extraParameters": { + "wifiType": "primary" + } + } + } + } + } + ] + } + } + } + ], + [ + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "label", + "text": "Limits access to devices in your home, recommended for guests" + } + }, + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "wifiWidget", + "backgroundColor": "#F6F6F6", + "wifiId": "guest-1", + "title": "You Don't Belong Here", + "enabled": true, + "enabledAction": { + "actionType": "toggleWifi", + "enabled": true, + "wifiId": "guest-1" + }, + "description": "Guest Wi-Fi (2.4 Ghz)", + "password": "1234567", + "editTitle": "Edit Wi-Fi details", + "editAction": { + "actionType": "openPage", + "pageType": "editWifi", + "extraParameters": { + "wifiId": "guest-1" + } + }, + "shareTitle": "Share Wi-Fi", + "shareAction": { + "actionType": "share", + "sharedText": "ssid: guest-1 password: 123456", + "sharedType": "Text" + } + } + } + ], + [ + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "label", + "text": "Recommended for optimal connectivity to \"Internet of Things\" (IoT) devices, for example smart speakers, plugs, and light switches." + } + }, + { + "moleculeName": "listItem", + "leftPadding": 16, + "rightPadding": 16, + "topPadding": 16, + "bottomPadding": 8, + "molecule": { + "moleculeName": "wifiWidget", + "backgroundColor": "#F6F6F6", + "wifiId": "iot-2", + "title": "Connected Homies", + "enabled": true, + "enabledAction": { + "actionType": "toggleWifi", + "enabled": true, + "wifiId": "iot-1" + }, + "description": "IoT (2.4 Ghz)", + "password": "1234567", + "editTitle": "Edit Wi-Fi details", + "editAction": { + "actionType": "openPage", + "pageType": "editWifi", + "extraParameters": { + "wifiId": "iot-1" + } + }, + "shareTitle": "Share Wi-Fi", + "shareAction": { + "actionType": "share", + "sharedText": "ssid: iot-1 password: 123456", + "sharedType": "Text" + } + } + } + ] + ] + } + ], + "footer": {} + } +} + diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/accordionViews.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/accordionViews.json new file mode 100644 index 0000000..5d4b6b3 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/accordionViews.json @@ -0,0 +1,67 @@ + +{ + "ResponseInfo" : { + "code" : "00000", + "type" : "Success" + }, + "Page": { + "pageType":"x", + "template":"list", + "header": { + "moleculeName":"header", + "molecule": { + "moleculeName": "headlineBody", + "headline":{ + "moleculeName": "label", + "text":"Your lines are on Unlimited plans." + }, + "body":{ + "moleculeName": "label", + "text":"Need something different? Take a minute to explore other plan options." + } + } + + }, + "footer":{ + "moleculeName":"footer", + "molecule": { + "moleculeName":"twoButtonView", + "primaryButton":{ + "moleculeName": "button", + "title":"Explore", + "action": { + "actionType": "openPage", + "pageType": "explore" + } + }, + "secondaryButton":{ + "moleculeName": "button", + "title":"Recommend", + "action": { + "actionType": "openPage", + "pageType": "recommend" + } + } + } + + }, + "molecules": [ + { + "moleculeName": "accordionListItem", + "molecule": { + "moleculeName": "label", + "text": "Testing" + }, + "molecules": [ + { + "moleculeName":"listItem", + "molecule": { + "moleculeName":"image", + "image": "https://mobile.vzw.com/hybridClient/is/image/VerizonWireless/iPhoneXr_Black_PureAngles", + "contentMode": "scaleAspectFit" + } + } + ] + }] + } +} diff --git a/JSONCreator_iOS/JSONCreator/MasterViewController.swift b/JSONCreator_iOS/JSONCreator/MasterViewController.swift index 2fe0259..7ca61f1 100644 --- a/JSONCreator_iOS/JSONCreator/MasterViewController.swift +++ b/JSONCreator_iOS/JSONCreator/MasterViewController.swift @@ -8,61 +8,64 @@ import UIKit +struct Section { + var name: String + var paths: [String] = [] + mutating func addPath(path: String){ + paths.append(path) + } + mutating func sort() { + paths = paths.sorted{ $0 < $1 } + } +} + class MasterViewController: UITableViewController { let folderPath = Bundle.main.resourcePath! + "/JSON" - var sectionsMap: [AnyHashable: Any] = [:] - + var sections: [Section] = [] override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. // setup the data - sectionsMap = [:] - let sections = try! FileManager.default.contentsOfDirectory(atPath: folderPath).sorted { $0 < $1 } - for section in sections { - let paths = try! FileManager.default.subpathsOfDirectory(atPath: "\(folderPath)/\(section)") - var array: [String] = [] + let sectionNames = try! FileManager.default.contentsOfDirectory(atPath: folderPath).sorted { $0 < $1 } + for sectionName in sectionNames { + var section = Section(name: sectionName) + let paths = try! FileManager.default.subpathsOfDirectory(atPath: "\(folderPath)/\(sectionName)") for path in paths { if path.hasSuffix(".json") { - array.append(path) + section.addPath(path: path) } } - sectionsMap[section] = array.sorted{ $0 < $1 } + section.sort() + sections.append(section) } } func getData(for indexPath: IndexPath) -> String? { - let sectionName = self.tableView(tableView, titleForHeaderInSection: indexPath.section) - let array = sectionsMap[sectionName!]! as! Array - let subPath = (array[indexPath.row] as! String) - let path = "\(folderPath)/\(sectionName!)/\(subPath)" + let section = sections[indexPath.section] + let path = "\(folderPath)/\(section.name)/\(section.paths[indexPath.row])" return try! String.init(contentsOfFile: path) } // MARK: - Table View override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return (((sectionsMap as NSDictionary).allKeys[section]) as! String) + return sections[section].name } override func numberOfSections(in tableView: UITableView) -> Int { - return sectionsMap.count + return sections.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let sectionName = self.tableView(tableView, titleForHeaderInSection: section) - let array = sectionsMap.arrayForKey(sectionName!) - return array.count + return sections[section].paths.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) - let sectionName = self.tableView(tableView, titleForHeaderInSection: indexPath.section) - let array = sectionsMap[sectionName!]! as! Array - cell.textLabel?.text = (array[indexPath.row] as! String) - + cell.textLabel?.text = sections[indexPath.section].paths[indexPath.row] if cell.interactions.first == nil { let dragInteraction = UIDragInteraction(delegate: self) cell.addInteraction(dragInteraction) diff --git a/JSONCreator_iOS/JSONCreator/TestToggle.swift b/JSONCreator_iOS/JSONCreator/TestToggle.swift new file mode 100644 index 0000000..61c7c08 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/TestToggle.swift @@ -0,0 +1,206 @@ +// +// TestToggle.swift +// JSONCreator +// +// Created by Matt Bruce on 10/21/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation +import MVMCore +import MVMCoreUI +import UIKit +import VDS + +/** + A custom implementation of Apple's UISwitch. + + By default this class begins in the off state. + + Container: The background of the toggle control. + Knob: The circular indicator that slides on the container. + */ +open class TestToggle: VDS.Toggle, VDSMoleculeViewProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var viewModel: TestToggleModel! + public var delegateObject: MVMCoreUIDelegateObject? + public var additionalData: [AnyHashable: Any]? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + public override func initialSetup() { + super.initialSetup() + publisher(for: .valueChanged) + .sink {[weak self] toggle in + guard let self = self else { return } + self.valueChanged(isOn: toggle.isOn) + }.store(in: &subscribers) + } + + // MARK:- MVMCoreViewProtocol + open func updateView(_ size: CGFloat) {} + + override open func updateView() { + super.updateView() + + //accessibility + accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel") + accessibilityHint = isEnabled ? MVMCoreUIUtility.hardcodedString(withKey: "AccToggleHint") : MVMCoreUIUtility.hardcodedString(withKey: "AccDisabled") + accessibilityValue = isOn ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") + } + + open func viewModelDidUpdate() { + guard let viewModel else { return } + + additionalData = additionalData.dictionaryAdding(key: KeySourceModel, value: viewModel) + } + + private func valueChanged(isOn: Bool){ + guard let viewModel else { return } + //sync the value on the viewModel + viewModel.selected = isOn + + //tell the form you changed + _ = FormValidator.validate(delegate: self.delegateObject?.formHolderDelegate) + + if viewModel.action != nil || viewModel.alternateAction != nil { + var action: ActionModelProtocol? + if isOn { + action = viewModel.action + } else { + action = viewModel.alternateAction ?? viewModel.action + } + if let action { + MVMCoreUIActionHandler.performActionUnstructured(with: action, + sourceModel: viewModel, + additionalData: additionalData, + delegateObject: delegateObject) + } + } + + print("toggle value changed to: \(isOn)") + print("viewModel server value: \(viewModel.formFieldServerValue()!)") + } + + public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 44 + } + + private typealias ActionDefinition = (model: ActionModelProtocol, + sourceModel: MoleculeModelProtocol?) + + private func performActionUnstructured(definition: ActionDefinition) { + MVMCoreUIActionHandler.performActionUnstructured(with: definition.model, + sourceModel: definition.sourceModel, + additionalData: additionalData, + delegateObject: delegateObject) + } +} +// MARK: - MVMCoreUIViewConstrainingProtocol +extension TestToggle { + + public func needsToBeConstrained() -> Bool { true } + + public func horizontalAlignment() -> UIStackView.Alignment { .trailing } +} + +public class TestToggleModel: MoleculeModelProtocol, FormFieldProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "testToggle" + public var backgroundColor: Color? //not used + + public var selected: Bool = false + public var enabled: Bool = true + public var readOnly: Bool = false + public var action: ActionModelProtocol? + public var alternateAction: ActionModelProtocol? + public var accessibilityText: String? + public var fieldKey: String? + public var groupName: String = FormValidator.defaultGroupName + public var baseValue: AnyHashable? + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case state + case enabled + case readOnly + case action + case accessibilityIdentifier + case alternateAction + case accessibilityText + case fieldKey + case groupName + } + + //-------------------------------------------------- + // MARK: - Form Valdiation + //-------------------------------------------------- + + public func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } + return selected + } + + //-------------------------------------------------- + // MARK: - Server Value + //-------------------------------------------------- + open func formFieldServerValue() -> AnyHashable? { + return formFieldValue() + } + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(_ state: Bool) { + selected = state + baseValue = state + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + + if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) { + selected = state + } + action = try typeContainer.decodeModelIfPresent(codingKey: .action) + alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction) + accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) + baseValue = selected + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + if let gName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { + groupName = gName + } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false + + + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) + try container.encodeModelIfPresent(action, forKey: .action) + try container.encodeModelIfPresent(alternateAction, forKey: .alternateAction) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(selected, forKey: .state) + try container.encode(enabled, forKey: .enabled) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + try container.encodeIfPresent(groupName, forKey: .groupName) + try container.encode(readOnly, forKey: .readOnly) + } +} diff --git a/Supporting Files/Artifactory/Artifactory.sh b/Supporting Files/Artifactory/Artifactory.sh new file mode 100755 index 0000000..6ad5af6 --- /dev/null +++ b/Supporting Files/Artifactory/Artifactory.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +#First arg should be the local path, second arg should be the remote path. +VERSION="2.0" +#Update onces stable +#APITOKEN=AKCp5cbwXBA2Rarq6WagmFFxQxcxsARGxSq5g1H8NaGm7837KTgwdWPqsp7FdgRa13B7AcpGN +#URL=https://oneartifactorycloud.verizon.com/artifactory + +APITOKEN=AKCp5ZmHP8B1dkLtdSh23bMcWHtrWzoB3SfhoCNpEC5e3dKNhiKsn8TPMZQafXzm2qkeXFXE6 +URL=https://oneartifactoryprod.verizon.com/artifactory + +LOCALPATH="${1}" +REMOTEPATH="${2}" +LOGFILE=$3 +LOCALDIR=$(dirname "${LOCALPATH}") +LOCALBASE=$(basename "${LOCALPATH}") +NEWFILEPATH="${LOCALDIR}"/$(basename "${REMOTEPATH}") +VERSIONFILE=./Checksums/"${LOCALBASE}".txt + +if [ -z $LOGFILE ]; then +LOGFILE="/tmp/${LOCALBASE}.txt" +fi + +#first argument is error message. +exit_with_error () { +echo "Error: $1" >> "${LOGFILE}" +if [ -f "${NEWFILEPATH}" ]; then +rm -rf "${NEWFILEPATH}" 2>>"${LOGFILE}" +fi +exit 1 +} + +echo "----------------------------------------------------------" >> $LOGFILE +echo "Logs for ${LOCALBASE}" >> $LOGFILE + +echo -e "Local Target: ${LOCALPATH}" >> $LOGFILE +echo -e "Remote Source: ${REMOTEPATH}" >> $LOGFILE + +if [ -z "$LOCALPATH" ]; then +exit_with_error "Missing local path argument" +fi + +if [ -z "$REMOTEPATH" ]; then +exit_with_error "Missing filename path argument" +fi + +#get local and remote checksums for comparison +echo -e "Getting checksums..." >> $LOGFILE +echo -e "URL: ${URL}/api/storage/${REMOTEPATH}" >> $LOGFILE +JSON=$(curl --header "X-JFrog-Art-Api: ${APITOKEN}" -X GET "${URL}/api/storage/${REMOTEPATH}" 2>>"${LOGFILE}") +CHECKSUM=$(echo "$JSON" | python3 -c 'import sys, json; print(json.load(sys.stdin)["checksums"]["sha1"])') 2>>"${LOGFILE}" +if [[ -z "$CHECKSUM" ]]; then +exit_with_error "No Checksum found in json: ${JSON}" +fi +echo "Remote checksum ${CHECKSUM}" >> "${LOGFILE}" +OLDCHECKSUM=$(cat "${VERSIONFILE}" 2>/dev/null) +echo "Local checksum ${OLDCHECKSUM}" >> "${LOGFILE}" + +#get new framework if no original framework, no local checksum, or remote checksum is different from local. +if [ ! -e "${LOCALPATH}" ] || [ -z "$OLDCHECKSUM" ] || [ "$CHECKSUM" != "$OLDCHECKSUM" ]; then +echo "Downloading..." >> "${LOGFILE}" +echo -e "URL: ${URL}/${REMOTEPATH}" >> $LOGFILE +curl --header "X-JFrog-Art-Api: ${APITOKEN}" -f -X GET "$URL/$REMOTEPATH" --output "${NEWFILEPATH}" 2>>"${LOGFILE}" +if [ $? -eq 0 ] && [ -e "${NEWFILEPATH}" ]; then +echo "Finished Downloading, begin unzip" >> "${LOGFILE}" +unzip -q -o "${NEWFILEPATH}" -d "${LOCALDIR}" 2>>"${LOGFILE}" +if [ $? -eq 0 ]; then +echo "Finished unzipping, remove zip" >> "${LOGFILE}" +rm -rf "${NEWFILEPATH}" 2>>"${LOGFILE}" +echo "Writing new checksum to file" >> "${LOGFILE}" +echo "${CHECKSUM}" > "${VERSIONFILE}" 2>>"${LOGFILE}" +echo "Successfully downloaded and unzipped archive." >> "${LOGFILE}" +else +exit_with_error "Error unzipping" +fi +else +exit_with_error "Failed to download" +fi +else +echo "Successful, No New Version" >> "${LOGFILE}" +fi diff --git a/Supporting Files/Artifactory/ArtifactoryItems.txt b/Supporting Files/Artifactory/ArtifactoryItems.txt new file mode 100644 index 0000000..8ee8db3 --- /dev/null +++ b/Supporting Files/Artifactory/ArtifactoryItems.txt @@ -0,0 +1,3 @@ +../../SharedFrameworks/VDSColorTokens.xcframework GVJV_VDS_Maven/%40vds-tokens/ios/VDSColorTokens.1.0.6.xcframework.zip +../../SharedFrameworks/VDSFormControlsTokens.xcframework GVJV_VDS_Maven/@vds-tokens/ios/VDSFormControlsTokens.1.0.7.xcframework.zip +../../SharedFrameworks/VDSTypographyTokens.xcframework GVJV_VDS_Maven/@vds-tokens/ios/VDSTypographyTokens.2.0.0.xcframework.zip diff --git a/Supporting Files/Artifactory/DownloadArtifactoryItems.sh b/Supporting Files/Artifactory/DownloadArtifactoryItems.sh new file mode 100644 index 0000000..7ab8500 --- /dev/null +++ b/Supporting Files/Artifactory/DownloadArtifactoryItems.sh @@ -0,0 +1,79 @@ +#!/bin/sh + +# DownloadArtifactoryItems.sh +# myverizon +# +# Created by Kyle on 3/2/20. +# Copyright © 2020 Verizon Wireless Inc. All rights reserved. + +ARTIFACTORYITEMS=./ArtifactoryItems.txt +ARTIFACTORY=Artifactory.sh + +update_artifactory_item () { + #echo "Run Artifactory for ${1} from ${2}" + ./${ARTIFACTORY} "${1}" "${2}" +} + +#Loop through items needed to download and download them. +PIDARRAY=() +LOGFILEARRAY=() +while read -r LOCALFILE REMOTEFILE; do + + #for if the directory has a parameter, such as PROJECT_DIR + FILE=$(eval echo ${LOCALFILE}) + LOGFILE="/tmp/$(basename ${FILE}).txt" + rm $LOGFILE 2> /dev/null + touch $LOGFILE + + #download them in parallel and store the PIDS. + update_artifactory_item "${FILE}" $REMOTEFILE $LOGFILE & + PIDARRAY+=($!) + LASTPID=${PIDARRAY[${#PIDARRAY[@]}-1]} + LOGFILEARRAY+=($LOGFILE) + echo "Process ${LASTPID} spawned for ${REMOTEFILE##*/}" + +done < "${ARTIFACTORYITEMS}" + +#wait for all processes to finish and fail if one of them fails. +INDEX=-1 +TOTAL=${#PIDARRAY[@]} +for i in "${PIDARRAY[@]}"; do + + INDEX=$((INDEX + 1)) + LOGFILE=${LOGFILEARRAY[$((INDEX))]} + + echo "\n\n$((INDEX + 1)) / ${TOTAL} (PID: ${i}) " + + # tail the subprocess log + tail -n +1 -f $LOGFILE & + READ_PID=$! + + # wait for subprocess to finish + sleep 0.05 # Allow tail -n +1 to print + wait $i + + # catch any subprocess non-zero status + status=$? + if [[ $status -gt 0 ]]; then + FAILED_PID_STATUS=$status + FAILED_PID=$i + fi + + # kill the running tail, consume the kill output + kill $READ_PID + wait $READ_PID > /dev/null 2>&1 + + # Need proper way to terminate children. + #if [[ -n $FAILED_PID ]] && [[ -n $FAIL_EARLY ]]; then + # echo "\n\nProcess ${FAILED_PID} failed with exit code ${FAILED_PID_STATUS}" + # echo $( ps -o pgid $FAILED_PID | grep [0-9] | tr -d ' ' ) + # kill -9 $(printf '%s ' "${PIDARRAY[@]}") + # exit $FAILED_PID_STATUS + #fi + +done + +if [[ -n $FAILED_PID_STATUS ]]; then + echo "\n\nProcess ${FAILED_PID} failed with exit code ${FAILED_PID_STATUS}" + exit $FAILED_PID_STATUS +fi diff --git a/dependency.txt b/dependency.txt index 8f6613c..259fd92 100644 --- a/dependency.txt +++ b/dependency.txt @@ -3,3 +3,5 @@ vds_ios https://gitlab.verizon.com/BPHV_MIPS/vds_ios.git develop mvm_core https://gitlab.verizon.com/BPHV_MIPS/mvm_core.git develop mvm_core_ui https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git develop + +vds_ios https://gitlab.verizon.com/BPHV_MIPS/vds_ios.git develop