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