merge develop

This commit is contained in:
Scott Pfeil 2023-06-27 16:34:24 -04:00
commit 435c93aefc
57 changed files with 6630 additions and 125 deletions

4
.gitignore vendored
View File

@ -22,6 +22,10 @@ SharedFrameworks/*
# Exception to the rule above (Keeps the folder around)
!SharedFrameworks/.gitkeep
VDSTypographyTokens.framework
VDSFormControlsTokens.framework
VDSColorTokens.framework
__MACOSX
# Xcode

View File

@ -5,10 +5,10 @@
location = "group:vds_ios/VDS.xcodeproj">
</FileRef>
<FileRef
location = "group:mvm_core_ui/MVMCoreUI.xcodeproj">
location = "group:mvm_core/MVMCore/MVMCore.xcodeproj">
</FileRef>
<FileRef
location = "group:mvm_core/MVMCore/MVMCore.xcodeproj">
location = "group:mvm_core_ui/MVMCoreUI.xcodeproj">
</FileRef>
<FileRef
location = "group:JSONCreator_iOS/JSONCreator.xcodeproj">

View File

@ -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 = "<group>"; };
D2431DEA25E93A4F001C7AAC /* buttimag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = buttimag.swift; sourceTree = "<group>"; };
D27564B42590FADB003CA713 /* ListDeviceRightVariableCaretModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListDeviceRightVariableCaretModel.swift; sourceTree = "<group>"; };
@ -90,8 +138,47 @@
D2FC4FAA25897ACB00061EA4 /* OrderTrackerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTrackerModel.swift; sourceTree = "<group>"; };
D2FC4FAB25897ACB00061EA4 /* Step.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Step.swift; sourceTree = "<group>"; };
D2FC4FAC25897ACB00061EA4 /* OrderTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderTracker.swift; sourceTree = "<group>"; };
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 = "<group>"; };
EA09CDC0282C40CB00A7835F /* GMFGBluetoothPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGBluetoothPair.swift; sourceTree = "<group>"; };
EA09CDC4282C40CB00A7835F /* GMFGConstant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGConstant.swift; sourceTree = "<group>"; };
EA09CDC7282C40CC00A7835F /* GMFGBLEHandlerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGBLEHandlerProtocol.swift; sourceTree = "<group>"; };
EA09CDC9282C40CC00A7835F /* GMFG5GCBandSignalHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFG5GCBandSignalHandler.swift; sourceTree = "<group>"; };
EA09CDCA282C40CC00A7835F /* GMFGOperationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGOperationHandler.swift; sourceTree = "<group>"; };
EA09CDCB282C40CC00A7835F /* GMFGPublicInternetAccessHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGPublicInternetAccessHandler.swift; sourceTree = "<group>"; };
EA09CDCC282C40CC00A7835F /* GMFGFotaHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGFotaHandler.swift; sourceTree = "<group>"; };
EA09CDCD282C40CC00A7835F /* GMFGSpeedTestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGSpeedTestHandler.swift; sourceTree = "<group>"; };
EA09CDCE282C40CC00A7835F /* GMFG5GSignalHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFG5GSignalHandler.swift; sourceTree = "<group>"; };
EA09CDCF282C40CC00A7835F /* GMFGRouterWifiHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGRouterWifiHandler.swift; sourceTree = "<group>"; };
EA09CDE2282C416C00A7835F /* BluetoothDebuggableProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothDebuggableProtocol.swift; sourceTree = "<group>"; };
EA09CDE3282C416C00A7835F /* MulticastDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MulticastDelegate.swift; sourceTree = "<group>"; };
EA09CDE4282C416C00A7835F /* BluetoothDebugger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothDebugger.swift; sourceTree = "<group>"; };
EA09CDE5282C416C00A7835F /* BluetoothDebuggerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothDebuggerView.swift; sourceTree = "<group>"; };
EA09CDEA282C422900A7835F /* GMFGStorageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGStorageManager.swift; sourceTree = "<group>"; };
EA09CDEC282C423E00A7835F /* GMFGLocationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GMFGLocationManager.swift; sourceTree = "<group>"; };
EA09CDEE282C429800A7835F /* GMFGTestScreenData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GMFGTestScreenData.swift; sourceTree = "<group>"; };
EA09CDF1282C430400A7835F /* BluetoothPairingProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothPairingProtocol.swift; sourceTree = "<group>"; };
EA09CDF2282C430400A7835F /* BluetoothPairableProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothPairableProtocol.swift; sourceTree = "<group>"; };
EA09CDF4282C430400A7835F /* BluetoothConfigModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothConfigModel.swift; sourceTree = "<group>"; };
EA09CDF5282C430400A7835F /* PeripheralModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = "<group>"; };
EA09CDF6282C430400A7835F /* CharacteristicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacteristicModel.swift; sourceTree = "<group>"; };
EA09CDF7282C430400A7835F /* ServiceModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceModel.swift; sourceTree = "<group>"; };
EA09CDFE282C437C00A7835F /* MFFGHSAnalyticsProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFFGHSAnalyticsProtocol.swift; sourceTree = "<group>"; };
EA09CE00282C43E800A7835F /* KeyedDecodingContainer+Decode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KeyedDecodingContainer+Decode.swift"; sourceTree = "<group>"; };
EA09CE02282C44A100A7835F /* MFFGHSUtility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFFGHSUtility.swift; sourceTree = "<group>"; };
EA09CE04282C45C200A7835F /* BluetoothPairBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothPairBehavior.swift; sourceTree = "<group>"; };
EA2ED278285BB3F400781478 /* MVMCoreUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MVMCoreUI.xcframework; path = ../SharedFrameworks/MVMCoreUI.xcframework; sourceTree = "<group>"; };
EA2ED279285BB3F400781478 /* MVMCore.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MVMCore.xcframework; path = ../SharedFrameworks/MVMCore.xcframework; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
EACA5E5D2853DBC900CBA65B /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = "<group>"; };
/* 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 = "<group>";
};
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 = "<group>";
};
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 = "<group>";
};
EA09CDC6282C40CC00A7835F /* Protocols */ = {
isa = PBXGroup;
children = (
EA09CDC7282C40CC00A7835F /* GMFGBLEHandlerProtocol.swift */,
);
path = Protocols;
sourceTree = "<group>";
};
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 = "<group>";
};
EA09CDE1282C416C00A7835F /* Debugger */ = {
isa = PBXGroup;
children = (
EA09CDE2282C416C00A7835F /* BluetoothDebuggableProtocol.swift */,
EA09CDE3282C416C00A7835F /* MulticastDelegate.swift */,
EA09CDE4282C416C00A7835F /* BluetoothDebugger.swift */,
EA09CDE5282C416C00A7835F /* BluetoothDebuggerView.swift */,
);
path = Debugger;
sourceTree = "<group>";
};
EA09CDF0282C430400A7835F /* Protocols */ = {
isa = PBXGroup;
children = (
EA09CDF1282C430400A7835F /* BluetoothPairingProtocol.swift */,
EA09CDF2282C430400A7835F /* BluetoothPairableProtocol.swift */,
);
path = Protocols;
sourceTree = "<group>";
};
EA09CDF3282C430400A7835F /* Models */ = {
isa = PBXGroup;
children = (
EA09CDF4282C430400A7835F /* BluetoothConfigModel.swift */,
EA09CDF5282C430400A7835F /* PeripheralModel.swift */,
EA09CDF6282C430400A7835F /* CharacteristicModel.swift */,
EA09CDF7282C430400A7835F /* ServiceModel.swift */,
);
path = Models;
sourceTree = "<group>";
};
/* 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 */;

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1400"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D2B1E3EE22F4A68F0065F95C"
BuildableName = "JSONCreator.app"
BlueprintName = "JSONCreator"
ReferencedContainer = "container:JSONCreator.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D2B1E3EE22F4A68F0065F95C"
BuildableName = "JSONCreator.app"
BlueprintName = "JSONCreator"
ReferencedContainer = "container:JSONCreator.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D2B1E3EE22F4A68F0065F95C"
BuildableName = "JSONCreator.app"
BlueprintName = "JSONCreator"
ReferencedContainer = "container:JSONCreator.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,9 @@
//
// BluetoothPairBehavior.swift
// JSONCreator
//
// Created by Matt Bruce on 5/11/22.
// Copyright © 2022 Verizon Wireless. All rights reserved.
//
import Foundation

View File

@ -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
}
}

View File

@ -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<String> = [] { didSet{ onChange() } }
var deviceMismatch: Set<String> = [] { didSet{ onChange() } }
var signalStatus: String? { didSet{ onChange() } }
var otherErrors: Set<String> = [] { didSet{ onChange() } }
var logs: Set<String> = [] { 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: "")
}
}

View File

@ -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()
}
}

View File

@ -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<T> {
// 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) }
}
}

View File

@ -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<T: Decodable, K: Decodable>(_ 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
}
}

View File

@ -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
}
}

View File

@ -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 } ?? []
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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))
}
}
}

View File

@ -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)
}
}

View File

@ -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<GMFG5GCBandSignalDelegate>()
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
}
}

View File

@ -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<GMFG5GSignalDelegate>()
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
}
}

View File

@ -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<GMFGFotaDelegate>()
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) }
}
}

View File

@ -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
}
}

View File

@ -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<GMFGPublicInternetAccessDelegate>()
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) }
}
}

View File

@ -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<GMFGRouterWifiDelegate>()
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) }
}
}

View File

@ -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<GMFGSpeedTestDelegate>()
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)
}
}

View File

@ -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
}
}

View File

@ -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"
}

View File

@ -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] }
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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")
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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 }
}
}

View File

@ -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

View File

@ -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)

View File

@ -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 dont have a window that faces an antenna, or if you prefer to mount the Receiver to a wall, make sure its 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 doesnt 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 havent 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"
}
}
]
}
}
}
}

View File

@ -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"
]
}
]
}
]
}
}

View File

@ -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"
]
}
]
}
]
}
}

View File

@ -22,7 +22,7 @@
{
"moleculeName": "stackItem",
"molecule": {
"moleculeName": "textField",
"moleculeName": "textView",
"fieldKey": "firstName",
"type": "text",
"errorMessage": "Please enter a valid first name.",

View File

@ -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
}
}

View File

@ -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"
}
}
}
}
]
}
}

View File

@ -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"
}
}
}

View File

@ -44,7 +44,8 @@
}
},
"molecules": [{
"molecules": [
{
"moleculeName": "tabsListItem",
"tabs": {
"moleculeName": "tabs",

View File

@ -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": "Youre 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": {
}
}
}

View File

@ -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": "Youre 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": {}
}
}

View File

@ -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": [
]
}
]
}
}

View File

@ -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": {}
}
}

View File

@ -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": {}
}
}

View File

@ -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"
}
}
]
}]
}
}

View File

@ -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<Any>
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<Any>
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)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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