From e069e267a52b3ce8b17f281862c037665f6e60ef Mon Sep 17 00:00:00 2001 From: Kamil Czajka Date: Tue, 12 Jan 2021 13:29:12 +0100 Subject: [PATCH] Release 2.2.0 --- Podfile.lock | 6 +- README.md | 6 +- SiliconLabsApp-Bridging-Header.h | 12 + SiliconLabsApp.xcodeproj/project.pbxproj | 242 ++- .../RangeTest/RangeTestStoryboard.storyboard | 848 ----------- ...ILRangeTestModeSelectionViewController.xib | 372 ----- .../SILAdvertiserService.swift | 10 + .../SILBluetoothDisabledAlert.swift | 56 + .../BluetoothControllers/SILCentralManager.h | 1 + .../BluetoothControllers/SILCentralManager.m | 20 +- .../SILDiscoveredPeripheral.h | 1 + .../SILDiscoveredPeripheral.m | 6 +- .../Categories/NSObject+SILAssociatedObject.h | 20 + .../Categories/NSObject+SILAssociatedObject.m | 31 + .../Categories/SILStoryboard+Constants.h | 1 + .../Categories/SILStoryboard+Constants.m | 1 + SiliconLabsApp/Categories/UIImage+SILImages.h | 11 + SiliconLabsApp/Categories/UIImage+SILImages.m | 13 +- .../Categories/UIViewController+Alert.swift | 22 + .../UIViewController+SILContext.swift | 28 + .../SILCharacteristicTableModel.m | 6 +- .../SILDescriptorTableModel.h | 5 + .../SILDescriptorTableModel.m | 42 + .../SILFilterBarViewModel.swift | 57 + .../BluetoothBrowser/Sort/SILSortOption.h | 20 + .../Sort/SILSortViewModel.swift | 84 ++ .../SILBrowserDescriptorValueParser.swift | 252 ++++ .../Browser/SILDescryptorTypeUUID.swift | 71 + .../Browser/SILFavoritePeripheral.swift | 5 + .../Models/SILAdTypeEddystoneDecoder.swift | 13 +- .../Models/SILAdvertiserSettings.swift | 6 + SiliconLabsApp/Models/SILApp.h | 2 + SiliconLabsApp/Models/SILApp.m | 8 +- SiliconLabsApp/Models/SILBeacon.m | 10 +- SiliconLabsApp/Models/SILLogDataModel.h | 2 + SiliconLabsApp/Models/SILLogDataModel.m | 25 + .../SILAppTypeConnectedLighting.storyboard | 314 +++- .../SILAppTypeHealthThermometer.storyboard | 70 +- .../DemoApps/SILAppTypeRangeTest.storyboard | 1325 ++++++++++++++++- .../DevelopApps/SILAppAdvertiser.storyboard | 10 +- .../SILAppAdvertiserDetails.storyboard | 37 +- .../SILAppBluetoothBrowser.storyboard | 219 ++- .../SILAppBluetoothBrowserDetails.storyboard | 136 +- .../DevelopApps/SILKeychain.storyboard | 35 +- .../RXMode.imageset/Contents.json | 23 + .../RXMode.imageset/icon - RX_Mode.png | Bin 0 -> 1466 bytes .../RXMode.imageset/icon - RX_Mode@2x.png | Bin 0 -> 3849 bytes .../RXMode.imageset/icon - RX_Mode@3x.png | Bin 0 -> 6027 bytes .../RXModeArrow.imageset/Contents.json | 23 + .../icon - RX_Mode - Arrow.png | Bin 0 -> 393 bytes .../icon - RX_Mode - Arrow@2x.png | Bin 0 -> 571 bytes .../icon - RX_Mode - Arrow@3x.png | Bin 0 -> 1169 bytes .../TXMode.imageset/Contents.json | 23 + .../TXMode.imageset/icon - TX_Mode.png | Bin 0 -> 1479 bytes .../TXMode.imageset/icon - TX_Mode@2x.png | Bin 0 -> 3898 bytes .../TXMode.imageset/icon - TX_Mode@3x.png | Bin 0 -> 6125 bytes .../TXModeArrow.imageset/Contents.json | 23 + .../icon - TX_Mode - Arrow.png | Bin 0 -> 362 bytes .../icon - TX_Mode - Arrow@2x.png | Bin 0 -> 540 bytes .../icon - TX_Mode - Arrow@3x.png | Bin 0 -> 1149 bytes .../blue_arrow.imageset/Contents.json | 33 - .../blue_arrow.imageset/blue_arrow~ipad.png | Bin 2203 -> 0 bytes .../blue_arrow~ipad@2x.png | Bin 3127 -> 0 bytes .../blue_arrow.imageset/blue_arrow~iphone.png | Bin 1937 -> 0 bytes .../blue_arrow~iphone@2x.png | Bin 2617 -> 0 bytes .../blue_arrow~iphone@3x.png | Bin 3630 -> 0 bytes .../Contents.json | 23 + .../icon - demo - range_test@x1.png | Bin 0 -> 1451 bytes .../icon - demo - range_test@x2.png | Bin 0 -> 2959 bytes .../icon - demo - range_test@x3.png | Bin 0 -> 4367 bytes .../Contents.json | 23 + .../icon - demo - range_test_big@x1.png | Bin 0 -> 4367 bytes .../icon - demo - range_test_big@x2.png | Bin 0 -> 9092 bytes .../icon - demo - range_test_big@x3.png | Bin 0 -> 13871 bytes .../Contents.json | 12 + .../icon - descending - off.svg | 7 + .../Contents.json | 12 + .../icon - descending - on.svg | 7 + .../Contents.json | 12 + .../icon - sort - AZ - off.svg | 6 + .../Contents.json | 12 + .../icon - sort - AZ - on.svg | 4 + .../Contents.json | 12 + .../icon - sort - ZA - off.svg | 6 + .../Contents.json | 12 + .../icon - sort - ZA - on.svg | 4 + .../Contents.json | 12 + .../icon - sort - ascending - off.svg | 7 + .../Contents.json | 12 + .../icon - sort - ascending - on.svg | 7 + .../icon - sort - off.imageset/Contents.json | 12 + .../icon - sort - off.svg | 4 + .../icon - sort - on.imageset/Contents.json | 12 + .../icon - sort - on.svg | 4 + .../lightOff.imageset/Contents.json | 18 +- .../graphic - light_demo - light - off.png | Bin 0 -> 8071 bytes .../graphic - light_demo - light - off@2x.png | Bin 0 -> 19105 bytes .../graphic - light_demo - light - off@3x.png | Bin 0 -> 33040 bytes .../lightOff.imageset/lightOff.png | Bin 9370 -> 0 bytes .../lightOff.imageset/lightOff@2x.png | Bin 24059 -> 0 bytes .../lightOff.imageset/lightOff@3x.png | Bin 43486 -> 0 bytes .../lightOn.imageset/Contents.json | 18 +- .../graphic - light_demo - light - on.png | Bin 0 -> 8180 bytes .../graphic - light_demo - light - on@2x.png | Bin 0 -> 19513 bytes .../graphic - light_demo - light - on@3x.png | Bin 0 -> 33763 bytes .../lightOn.imageset/lightOn.png | Bin 10959 -> 0 bytes .../lightOn.imageset/lightOn@2x.png | Bin 27765 -> 0 bytes .../lightOn.imageset/lightOn@3x.png | Bin 49202 -> 0 bytes .../red_arrow.imageset/Contents.json | 33 - .../red_arrow.imageset/red_arrow~ipad.png | Bin 855 -> 0 bytes .../red_arrow.imageset/red_arrow~ipad@2x.png | Bin 1760 -> 0 bytes .../red_arrow.imageset/red_arrow~iphone.png | Bin 580 -> 0 bytes .../red_arrow~iphone@2x.png | Bin 1256 -> 0 bytes .../red_arrow~iphone@3x.png | Bin 2168 -> 0 bytes .../SILBaseWireframe+ContextMenu.swift | 13 +- .../ContextMenu/SILContextMenu.swift | 26 + ...SILContextMenuPresentationController.swift | 65 +- .../SILContextMenuViewController.swift | 4 +- .../SILAdvertiserDetailsViewController.swift | 9 +- .../SILAdvertiserDetailsWireframe.swift | 7 + ...SILExitAdvertiserPopupViewController.swift | 45 + .../SILExitAdvertiserPopupViewController.xib | 210 +++ .../Home/SILAdvertiserHomeWireframe.swift | 6 + .../SILDeviceSelectionViewController.m | 35 +- .../SILAppSelectionCollectionViewCell.h | 2 - .../SILAppSelectionCollectionViewCell.m | 44 - .../SILAppSelectionCollectionViewCell.xib | 119 +- .../SILAppSelectionViewController.m | 55 +- .../SILBrowserConnectionsTableViewCell.m | 3 +- .../Cells/SILSavedSearchesTableViewCell.m | 3 +- .../SILFilterBarViewController.swift | 60 + .../SILBrowserMappingsSegmentedControl.swift | 22 + .../KeyChains/SILKeychainViewController.swift | 4 +- .../RefreshImageView/SILRefreshImageView.m | 18 +- .../SILBluetoothBrowserViewController.m | 81 +- .../Sort/SILSortModeViewCell.swift | 27 + .../Sort/SILSortTypeViewCell.swift | 15 + .../Sort/SILSortViewController.swift | 72 + ...SILBluetoothBrowserExpandableViewManager.h | 33 - ...SILBluetoothBrowserExpandableViewManager.m | 327 ---- ...luetoothBrowserExpandableViewManager.swift | 359 +++++ .../SILConnectedLightingViewController.m | 47 +- .../SILConnectedLightingViewController.xib | 103 -- .../SILDebugCharacteristicTableViewCell.h | 1 + .../SILDebugCharacteristicTableViewCell.m | 54 +- .../SILDebugServicesViewController.m | 51 +- .../SILDescriptorTableViewCell.swift | 34 + .../SILHealthThermometerAppViewController.m | 4 +- .../SILRangeTestCharacteristic.swift | 13 +- .../Peripheral/SILRangeTestPeripheral.swift | 38 +- ...LRangeTestAppContainerViewController.swift | 67 + .../SILRangeTestAppViewController.swift | 161 +- .../SILRangeTestAppViewModel.swift | 103 +- ...RangeTestModeSelectionViewController.swift | 35 +- ...LRangeTestSelectDeviceViewController.swift | 100 ++ .../SILAdvertiserDetailsViewModel.swift | 59 +- .../SILExitAdvertiserPopupViewModel.swift | 35 + .../Home/SILAdvertiserHomeViewModel.swift | 6 + .../ViewModels/DebugDeviceViewModel.swift | 114 +- .../ViewModels/SILDeviceSelectionViewModel.h | 1 + .../ViewModels/SILDeviceSelectionViewModel.m | 9 +- SiliconLabsApp/Views/SILBigButton.swift | 28 + .../SILSegmentedControl.swift} | 19 +- ...rol.h => SILThermometerSegmentedControl.h} | 4 +- ...rol.m => SILThermometerSegmentedControl.m} | 8 +- 165 files changed, 5205 insertions(+), 2430 deletions(-) delete mode 100644 SiliconLabsApp/Base.lproj/RangeTest/RangeTestStoryboard.storyboard delete mode 100644 SiliconLabsApp/Base.lproj/RangeTest/SILRangeTestModeSelectionViewController.xib create mode 100644 SiliconLabsApp/BluetoothControllers/SILBluetoothDisabledAlert.swift create mode 100644 SiliconLabsApp/Categories/NSObject+SILAssociatedObject.h create mode 100644 SiliconLabsApp/Categories/NSObject+SILAssociatedObject.m create mode 100644 SiliconLabsApp/Categories/UIViewController+Alert.swift create mode 100644 SiliconLabsApp/Categories/UIViewController+SILContext.swift create mode 100644 SiliconLabsApp/Models/BluetoothBrowser/SILFilterBarViewModel.swift create mode 100644 SiliconLabsApp/Models/BluetoothBrowser/Sort/SILSortOption.h create mode 100644 SiliconLabsApp/Models/BluetoothBrowser/Sort/SILSortViewModel.swift create mode 100644 SiliconLabsApp/Models/Browser/SILBrowserDescriptorValueParser.swift create mode 100644 SiliconLabsApp/Models/Browser/SILDescryptorTypeUUID.swift create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/RXMode.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/RXMode.imageset/icon - RX_Mode.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/RXMode.imageset/icon - RX_Mode@2x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/RXMode.imageset/icon - RX_Mode@3x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/RXModeArrow.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/RXModeArrow.imageset/icon - RX_Mode - Arrow.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/RXModeArrow.imageset/icon - RX_Mode - Arrow@2x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/RXModeArrow.imageset/icon - RX_Mode - Arrow@3x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/TXMode.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/TXMode.imageset/icon - TX_Mode.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/TXMode.imageset/icon - TX_Mode@2x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/TXMode.imageset/icon - TX_Mode@3x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/TXModeArrow.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/TXModeArrow.imageset/icon - TX_Mode - Arrow.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/TXModeArrow.imageset/icon - TX_Mode - Arrow@2x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/TXModeArrow.imageset/icon - TX_Mode - Arrow@3x.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/blue_arrow.imageset/Contents.json delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/blue_arrow.imageset/blue_arrow~ipad.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/blue_arrow.imageset/blue_arrow~ipad@2x.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/blue_arrow.imageset/blue_arrow~iphone.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/blue_arrow.imageset/blue_arrow~iphone@2x.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/blue_arrow.imageset/blue_arrow~iphone@3x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - demo - range_test.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - demo - range_test.imageset/icon - demo - range_test@x1.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - demo - range_test.imageset/icon - demo - range_test@x2.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - demo - range_test.imageset/icon - demo - range_test@x3.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - demo - range_test_big.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - demo - range_test_big.imageset/icon - demo - range_test_big@x1.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - demo - range_test_big.imageset/icon - demo - range_test_big@x2.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - demo - range_test_big.imageset/icon - demo - range_test_big@x3.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - descending - off.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - descending - off.imageset/icon - descending - off.svg create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - descending - on.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - descending - on.imageset/icon - descending - on.svg create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - AZ - off.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - AZ - off.imageset/icon - sort - AZ - off.svg create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - AZ - on.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - AZ - on.imageset/icon - sort - AZ - on.svg create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - ZA - off.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - ZA - off.imageset/icon - sort - ZA - off.svg create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - ZA - on.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - ZA - on.imageset/icon - sort - ZA - on.svg create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - ascending - off.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - ascending - off.imageset/icon - sort - ascending - off.svg create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - ascending - on.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - ascending - on.imageset/icon - sort - ascending - on.svg create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - off.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - off.imageset/icon - sort - off.svg create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - on.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/icon - sort - on.imageset/icon - sort - on.svg create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOff.imageset/graphic - light_demo - light - off.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOff.imageset/graphic - light_demo - light - off@2x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOff.imageset/graphic - light_demo - light - off@3x.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOff.imageset/lightOff.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOff.imageset/lightOff@2x.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOff.imageset/lightOff@3x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOn.imageset/graphic - light_demo - light - on.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOn.imageset/graphic - light_demo - light - on@2x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOn.imageset/graphic - light_demo - light - on@3x.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOn.imageset/lightOn.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOn.imageset/lightOn@2x.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/lightOn.imageset/lightOn@3x.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/red_arrow.imageset/Contents.json delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/red_arrow.imageset/red_arrow~ipad.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/red_arrow.imageset/red_arrow~ipad@2x.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/red_arrow.imageset/red_arrow~iphone.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/red_arrow.imageset/red_arrow~iphone@2x.png delete mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/red_arrow.imageset/red_arrow~iphone@3x.png create mode 100644 SiliconLabsApp/ViewControllers/Advertiser/ContextMenu/SILContextMenu.swift create mode 100644 SiliconLabsApp/ViewControllers/Advertiser/ExitAdvertiserPopup/SILExitAdvertiserPopupViewController.swift create mode 100644 SiliconLabsApp/ViewControllers/Advertiser/ExitAdvertiserPopup/SILExitAdvertiserPopupViewController.xib create mode 100644 SiliconLabsApp/ViewControllers/BluetoothBrowser/FilterBar/SILFilterBarViewController.swift create mode 100644 SiliconLabsApp/ViewControllers/BluetoothBrowser/KeyChains/SILBrowserMappingsSegmentedControl.swift create mode 100644 SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortModeViewCell.swift create mode 100644 SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortTypeViewCell.swift create mode 100644 SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortViewController.swift delete mode 100644 SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.h delete mode 100644 SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.m create mode 100644 SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.swift delete mode 100644 SiliconLabsApp/ViewControllers/ConnectedLightingApp/SILConnectedLightingViewController.xib create mode 100644 SiliconLabsApp/ViewControllers/Descriptors/SILDescriptorTableViewCell.swift create mode 100644 SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppContainerViewController.swift create mode 100644 SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestSelectDeviceViewController.swift create mode 100644 SiliconLabsApp/ViewModels/Advertiser/ExitAdvertiserPopup/SILExitAdvertiserPopupViewModel.swift create mode 100644 SiliconLabsApp/Views/SILBigButton.swift rename SiliconLabsApp/{ViewControllers/BluetoothBrowser/KeyChains/SILBrowserSegmentedControl.swift => Views/SILSegmentedControl.swift} (54%) rename SiliconLabsApp/Views/{SILSegmentedControl.h => SILThermometerSegmentedControl.h} (90%) rename SiliconLabsApp/Views/{SILSegmentedControl.m => SILThermometerSegmentedControl.m} (92%) diff --git a/Podfile.lock b/Podfile.lock index 71a44ea4..6f15cdad 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -74,9 +74,9 @@ SPEC CHECKSUMS: ActionSheetPicker-3.0: eef157d75e151f255c5333d26656c7fbfe905a51 ChameleonFramework: d21a3cc247abfe5e37609a283a8238b03575cf64 Charts: e0dd4cd8f257bccf98407b58183ddca8e8d5b578 - Crashlytics: 07fb167b1694128c1c9a5a5cc319b0e9c3ca0933 + Crashlytics: a33af323773f73904037dc2e684cd2f0d29f4fe2 Eddystone: a56b4cb97540f43617d6fc488a9ff75eab1d66a4 - Fabric: f988e33c97f08930a413e08123064d2e5f68d655 + Fabric: 09ef2d9b99b104702bede1acaf469fb8f20a9146 IP-UIKit-Wisdom: b395a065344071b33659e5f6b918043a97c48a44 KVOController: d72ace34afea42468329623b3379ab3cd1d286b6 MZTimerLabel: cd9bfb9304540ef2a9e163384fca9c978f0dbf83 @@ -90,4 +90,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 2c4307b5fdc69d190078ca339757cbf3833d671b -COCOAPODS: 1.8.4 +COCOAPODS: 1.10.0 diff --git a/README.md b/README.md index a1aceda4..099c48b0 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,13 @@ The app is divided into two main functional areas, the demo and the develop view The demo view lists a number of demos that are meant for quickly testing some of the sample apps in the Silicon Labs Bluetooth SDK. The currently supported demos are: - **Health Thermometer demo:** Connects to an EFR32/BGM device running the soc-thermometer sample application from the Bluetooth SDK and displays the temperature read from the SI7021 sensor on the WSTK mainboard. +- **Connect Lighting DMP demo:** Leverages the DMP sample apps to control a DMP light node from both a mobile app as well as the protocol specific switch node (Zigbee, proprietary) while keeping the light status in sync across all devices. +- **Range Test demo:** Allows visualizing the RSSI and other RF performance data on the mobile phone while running Range Test sample application on a pair of Silicon Labs radio boards. The develop view contains functionalities focused on helping developers create and troubleshoot their Bluetooth applications based on EFR32/BGM devices. The currently supported functionalities are: - **Bluetooth Browser:** This is a generic and powerful tool that allows you to explore the BLE devices around you. Key features of the browser include: - - Scan results with rich data set + - Scan and sort results with rich data set - Connectable/non-connectable - Beacon type - Advertisement interval @@ -37,7 +39,7 @@ The develop view contains functionalities focused on helping developers create a - Over-the-air (OTA) device firmware upgrade (DFU) with both reliable and speed modes - Configurable MTU and connection interval - Support for all GATT operations - - **Bluetooth Advertiser**: Allows you to create multiple advertisement sets and enable them in parallel +- **Bluetooth Advertiser**: Allows you to create multiple advertisement sets and enable them in parallel - Ability to manually start/stop advertisement, or stop based on time limit - Support for multiple AD types diff --git a/SiliconLabsApp-Bridging-Header.h b/SiliconLabsApp-Bridging-Header.h index b94ccd50..ed7980c4 100644 --- a/SiliconLabsApp-Bridging-Header.h +++ b/SiliconLabsApp-Bridging-Header.h @@ -34,3 +34,15 @@ #import "SILBluetoothBitModel.h" #import "SILBluetoothXMLParser.h" #import "UIView+SILShadow.h" +#import "SILDeviceSelectionViewModel.h" +#import "SILDeviceSelectionViewController.h" +#import "SILCentralManagerBuilder.h" +#import "NSObject+SILAssociatedObject.h" +#import "SILDescriptorTableModel.h" +#import "SILSortOption.h" +#import "UIImage+SILImages.h" +#import "SILBrowserLogViewController.h" +#import "SILBrowserFilterViewController.h" +#import "SILBrowserConnectionsViewController.h" +#import "SILStoryboard+Constants.h" +#import "SILBrowserBeaconType.h" diff --git a/SiliconLabsApp.xcodeproj/project.pbxproj b/SiliconLabsApp.xcodeproj/project.pbxproj index 875069f8..78cd9506 100644 --- a/SiliconLabsApp.xcodeproj/project.pbxproj +++ b/SiliconLabsApp.xcodeproj/project.pbxproj @@ -115,7 +115,7 @@ 0C2FCB321F9A542300F4F259 /* CBPeripheral+Services.m in Sources */ = {isa = PBXBuildFile; fileRef = 4924BA6A1E7057F800AE9E56 /* CBPeripheral+Services.m */; }; 0C2FCB331F9A542300F4F259 /* SILDebugProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A89B1BC5F872001948C1 /* SILDebugProperty.m */; }; 0C2FCB341F9A542300F4F259 /* SILPopoverViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 49FB4BEA1E7A2EC200223F3E /* SILPopoverViewController.m */; }; - 0C2FCB351F9A542300F4F259 /* SILSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBEA1A77C75400676C5C /* SILSegmentedControl.m */; }; + 0C2FCB351F9A542300F4F259 /* SILThermometerSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBEA1A77C75400676C5C /* SILThermometerSegmentedControl.m */; }; 0C2FCB361F9A542300F4F259 /* SILRetailBeaconDetailsHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA5F62EA1F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.m */; }; 0C2FCB371F9A542300F4F259 /* SILOTAHUDPeripheralViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4E508D1E92E507004FD829 /* SILOTAHUDPeripheralViewModel.m */; }; 0C2FCB381F9A542300F4F259 /* SILDebugAdvDetailTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138481BE99502001EFE7E /* SILDebugAdvDetailTableViewCell.m */; }; @@ -234,13 +234,10 @@ 0C2FCBDD1F9A675A00F4F259 /* HomeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCACBA3C1EA7A047001039B5 /* HomeKit.framework */; }; 0C39082B1FA233F900934AD1 /* SILDeviceSelectionViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CC658141F96C3C200953CDC /* SILDeviceSelectionViewModel.m */; }; 0C3908301FA8AC7A00934AD1 /* SILConnectedLightingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C39082E1FA8AC7A00934AD1 /* SILConnectedLightingViewController.m */; }; - 0C3908311FA8AC7A00934AD1 /* SILConnectedLightingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C39082F1FA8AC7A00934AD1 /* SILConnectedLightingViewController.xib */; }; 0C6745FF1FAC033F0032CBF5 /* SILCentralManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E6A37E401A82809B00510E39 /* SILCentralManager.m */; }; 0CC658151F96C3C200953CDC /* SILDeviceSelectionViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CC658141F96C3C200953CDC /* SILDeviceSelectionViewModel.m */; }; - 0F34409520AF0E250067397C /* RangeTestStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0F34409420AF0E250067397C /* RangeTestStoryboard.storyboard */; }; - 0F34409620AF0E250067397C /* RangeTestStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0F34409420AF0E250067397C /* RangeTestStoryboard.storyboard */; }; - 0F34409820AF0E570067397C /* SILRangeTestModeSelectionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0F34409720AF0E570067397C /* SILRangeTestModeSelectionViewController.xib */; }; - 0F34409920AF0E570067397C /* SILRangeTestModeSelectionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0F34409720AF0E570067397C /* SILRangeTestModeSelectionViewController.xib */; }; + 0F34409520AF0E250067397C /* SILAppTypeRangeTest.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0F34409420AF0E250067397C /* SILAppTypeRangeTest.storyboard */; }; + 0F34409620AF0E250067397C /* SILAppTypeRangeTest.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0F34409420AF0E250067397C /* SILAppTypeRangeTest.storyboard */; }; 0F34409B20AF18BA0067397C /* SILRangeTestAppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F34409A20AF18BA0067397C /* SILRangeTestAppViewController.swift */; }; 0F34409C20AF18BA0067397C /* SILRangeTestAppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F34409A20AF18BA0067397C /* SILRangeTestAppViewController.swift */; }; 0F392514214FA15F00D74963 /* SILRangeTestMovingAverageCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FFDD024210F09A000CFFB5E /* SILRangeTestMovingAverageCalculator.swift */; }; @@ -293,7 +290,7 @@ 0F4E512321525FDC00F58ACE /* CBPeripheral+Services.m in Sources */ = {isa = PBXBuildFile; fileRef = 4924BA6A1E7057F800AE9E56 /* CBPeripheral+Services.m */; }; 0F4E512421525FDC00F58ACE /* SILDebugProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A89B1BC5F872001948C1 /* SILDebugProperty.m */; }; 0F4E512521525FDC00F58ACE /* SILPopoverViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 49FB4BEA1E7A2EC200223F3E /* SILPopoverViewController.m */; }; - 0F4E512621525FDC00F58ACE /* SILSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBEA1A77C75400676C5C /* SILSegmentedControl.m */; }; + 0F4E512621525FDC00F58ACE /* SILThermometerSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBEA1A77C75400676C5C /* SILThermometerSegmentedControl.m */; }; 0F4E512721525FDC00F58ACE /* SILRetailBeaconDetailsHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA5F62EA1F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.m */; }; 0F4E512821525FDC00F58ACE /* SILOTAHUDPeripheralViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4E508D1E92E507004FD829 /* SILOTAHUDPeripheralViewModel.m */; }; 0F4E512921525FDC00F58ACE /* SILDebugAdvDetailTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138481BE99502001EFE7E /* SILDebugAdvDetailTableViewCell.m */; }; @@ -391,17 +388,15 @@ 0F4E519121525FDC00F58ACE /* SILOTAProgressViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 49FB4BF01E7A33C900223F3E /* SILOTAProgressViewController.xib */; }; 0F4E519221525FDC00F58ACE /* SILDebugDeviceFilterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AAD9E9331F279B910054C387 /* SILDebugDeviceFilterViewController.xib */; }; 0F4E519321525FDC00F58ACE /* SILBeaconRegistryEntryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 07140FA01BFD80E8001CD217 /* SILBeaconRegistryEntryCell.xib */; }; - 0F4E519421525FDC00F58ACE /* SILConnectedLightingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C39082F1FA8AC7A00934AD1 /* SILConnectedLightingViewController.xib */; }; 0F4E519521525FDC00F58ACE /* SILDebugDeviceTableViewCell~iphone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 078137EC1BE40A8C001EFE7E /* SILDebugDeviceTableViewCell~iphone.xib */; }; 0F4E519621525FDC00F58ACE /* SILDebugDeviceTableViewCell~ipad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 078137EB1BE40A8C001EFE7E /* SILDebugDeviceTableViewCell~ipad.xib */; }; 0F4E519721525FDC00F58ACE /* SILRetailBeaconDetailsHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA5F62EC1F0A859F007EDAAC /* SILRetailBeaconDetailsHeaderView.xib */; }; 0F4E519821525FDC00F58ACE /* SILDebugAdvDetailTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0763AC4A1BFE8F5400EBE94B /* SILDebugAdvDetailTableViewCell.xib */; }; - 0F4E519921525FDC00F58ACE /* SILRangeTestModeSelectionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0F34409720AF0E570067397C /* SILRangeTestModeSelectionViewController.xib */; }; 0F4E519A21525FDC00F58ACE /* SILDebugDeviceViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 078137ED1BE40A8C001EFE7E /* SILDebugDeviceViewController.xib */; }; 0F4E519B21525FDC00F58ACE /* SILDebugCharacteristicToggleFieldTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 078137FD1BE40AA5001EFE7E /* SILDebugCharacteristicToggleFieldTableViewCell.xib */; }; 0F4E519C21525FDC00F58ACE /* SILOTAHUDView.xib in Resources */ = {isa = PBXBuildFile; fileRef = DCB431121E8315EF00EE94F0 /* SILOTAHUDView.xib */; }; 0F4E519D21525FDC00F58ACE /* SILDebugCharacteristicValueFieldTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 078137FE1BE40AA5001EFE7E /* SILDebugCharacteristicValueFieldTableViewCell.xib */; }; - 0F4E519E21525FDC00F58ACE /* RangeTestStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0F34409420AF0E250067397C /* RangeTestStoryboard.storyboard */; }; + 0F4E519E21525FDC00F58ACE /* SILAppTypeRangeTest.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0F34409420AF0E250067397C /* SILAppTypeRangeTest.storyboard */; }; 0F4E519F21525FDC00F58ACE /* SILActivityBarViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 492CA4B01E4E0DD700502AC9 /* SILActivityBarViewController.xib */; }; 0F4E51A121525FDC00F58ACE /* SILDebugAdvDetailCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 078137E81BE40A8C001EFE7E /* SILDebugAdvDetailCollectionViewCell.xib */; }; 0F4E51A221525FDC00F58ACE /* SILDebugServiceTableViewCell~iphone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 078138021BE40AA5001EFE7E /* SILDebugServiceTableViewCell~iphone.xib */; }; @@ -436,6 +431,15 @@ 133D396325540A6600BFB484 /* ContextMenuOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D396225540A6600BFB484 /* ContextMenuOption.swift */; }; 133D396D25540B5900BFB484 /* SILContextMenuCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D396C25540B5900BFB484 /* SILContextMenuCellView.swift */; }; 133D39C32554514600BFB484 /* SILAdvertiserNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133D39C22554514600BFB484 /* SILAdvertiserNotification.swift */; }; + 1394F95E2566BD4400795E5A /* SILBigButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1394F95D2566BD4400795E5A /* SILBigButton.swift */; }; + 1394F95F2566BD4400795E5A /* SILBigButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1394F95D2566BD4400795E5A /* SILBigButton.swift */; }; + 1394F9602566BD4400795E5A /* SILBigButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1394F95D2566BD4400795E5A /* SILBigButton.swift */; }; + 13ACBA7F256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13ACBA7E256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift */; }; + 13ACBA80256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13ACBA7E256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift */; }; + 13ACBA81256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13ACBA7E256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift */; }; + 13ACBA87256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13ACBA86256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift */; }; + 13ACBA88256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13ACBA86256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift */; }; + 13ACBA89256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13ACBA86256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift */; }; 19AC27E79B49CB3AD403E6EA /* SILEncodingPseudoFieldRowModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 19AC2956BD095FCA31424BC0 /* SILEncodingPseudoFieldRowModel.m */; }; 1E113D08244ECD6600442752 /* SILDiscoveredPeripheralIdentifierProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E113D07244ECD6600442752 /* SILDiscoveredPeripheralIdentifierProvider.swift */; }; 1E1907B123FD850700DD014C /* SILSavedSearchesRealmModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E1907B023FD850700DD014C /* SILSavedSearchesRealmModel.m */; }; @@ -511,13 +515,14 @@ 1E26ECD425529C3F002FFAAB /* SILScanResponseValueCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E26ECD325529C3F002FFAAB /* SILScanResponseValueCellView.swift */; }; 1E2D26D8244050430006B84A /* SILDocumentPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2D26D7244050430006B84A /* SILDocumentPickerViewController.swift */; }; 1E2D9CAC23BA48D600816EC0 /* SILBluetoothBrowserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E2D9CAB23BA48D600816EC0 /* SILBluetoothBrowserViewController.m */; }; + 1E3A15F8258B82EA00776A00 /* SILFilterBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3A15F7258B82EA00776A00 /* SILFilterBarViewModel.swift */; }; + 1E3A15FF258B830B00776A00 /* SILFilterBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3A15FE258B830B00776A00 /* SILFilterBarViewController.swift */; }; 1E3FC7D8252B5E40002F740D /* UITextField+DoneButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E3FC7D7252B5E40002F740D /* UITextField+DoneButton.swift */; }; 1E48A1E92484E27300C188C0 /* SILAnimatedUIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E48A1E82484E27300C188C0 /* SILAnimatedUIButton.swift */; }; 1E48A1EF2484FC0B00C188C0 /* SILToastModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E48A1EE2484FC0B00C188C0 /* SILToastModelType.swift */; }; 1E4C37A42429095300C822E4 /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1E4C37A12429095300C822E4 /* Roboto-Bold.ttf */; }; 1E4C37A52429095300C822E4 /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1E4C37A22429095300C822E4 /* Roboto-Medium.ttf */; }; 1E4C37A62429095300C822E4 /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1E4C37A32429095300C822E4 /* Roboto-Regular.ttf */; }; - 1E4C37D5242C996400C822E4 /* SILBluetoothBrowserExpandableViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E4C37D4242C996400C822E4 /* SILBluetoothBrowserExpandableViewManager.m */; }; 1E56E9E52416800B00D5B92A /* NSString+SILBrowserNotifications.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E56E9E42416800B00D5B92A /* NSString+SILBrowserNotifications.m */; }; 1E56E9E92416934D00D5B92A /* SILBluetoothBrowser+Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E56E9E82416934D00D5B92A /* SILBluetoothBrowser+Constants.m */; }; 1E56E9EB2416974500D5B92A /* SILStoryboard+Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E56E9EA2416974500D5B92A /* SILStoryboard+Constants.m */; }; @@ -527,7 +532,15 @@ 1E6AF7FE23CDB31500EE8280 /* SILBrowserLogViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E6AF7FD23CDB31500EE8280 /* SILBrowserLogViewController.m */; }; 1E6AF80123CDB32800EE8280 /* SILBrowserConnectionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E6AF80023CDB32800EE8280 /* SILBrowserConnectionsViewController.m */; }; 1E6AF80423CDB33600EE8280 /* SILBrowserFilterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E6AF80323CDB33600EE8280 /* SILBrowserFilterViewController.m */; }; + 1E6CBCF12588AE1E00B11648 /* SILExitAdvertiserPopupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E6CBCF02588AE1E00B11648 /* SILExitAdvertiserPopupViewModel.swift */; }; + 1E6CBCF92588AE3500B11648 /* SILExitAdvertiserPopupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E6CBCF72588AE3500B11648 /* SILExitAdvertiserPopupViewController.swift */; }; + 1E6CBCFA2588AE3500B11648 /* SILExitAdvertiserPopupViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1E6CBCF82588AE3500B11648 /* SILExitAdvertiserPopupViewController.xib */; }; + 1E6CBD402588D23700B11648 /* SILSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E6CBD3F2588D23700B11648 /* SILSegmentedControl.swift */; }; + 1E6CBD412588D23700B11648 /* SILSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E6CBD3F2588D23700B11648 /* SILSegmentedControl.swift */; }; + 1E6CBD422588D23700B11648 /* SILSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E6CBD3F2588D23700B11648 /* SILSegmentedControl.swift */; }; 1E70A97B240403320021C51B /* SILBrowserConnectionsViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E70A97A240403320021C51B /* SILBrowserConnectionsViewModel.m */; }; + 1E8529832591F6480064F419 /* UIViewController+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8529822591F6480064F419 /* UIViewController+Alert.swift */; }; + 1E8529C125920C3B0064F419 /* SILBluetoothDisabledAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8529C025920C3B0064F419 /* SILBluetoothDisabledAlert.swift */; }; 1E90F5E42473E73E0013AABD /* SILDebugServicesMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E90F5E32473E73E0013AABD /* SILDebugServicesMenuViewController.swift */; }; 1E90F5E72473E8340013AABD /* SILDebugServicesMenuTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E90F5E52473E8340013AABD /* SILDebugServicesMenuTableViewCell.swift */; }; 1E90F5E82473E8340013AABD /* SILDebugServicesMenuTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1E90F5E62473E8340013AABD /* SILDebugServicesMenuTableViewCell.xib */; }; @@ -557,7 +570,6 @@ 1EC92EF723A3957D00FD2219 /* SILAppTypeRetailBeacon.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC92EF623A3957D00FD2219 /* SILAppTypeRetailBeacon.storyboard */; }; 1EC92EF923A3959200FD2219 /* SILAppBluetoothBrowser.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC92EF823A3959200FD2219 /* SILAppBluetoothBrowser.storyboard */; }; 1EC92EFB23A395B200FD2219 /* SILAppTypeHomeKitDebug.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC92EFA23A395B200FD2219 /* SILAppTypeHomeKitDebug.storyboard */; }; - 1EC92EFD23A395C200FD2219 /* SILAppTypeRangeTest.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC92EFC23A395C200FD2219 /* SILAppTypeRangeTest.storyboard */; }; 1EC92F0623A3962C00FD2219 /* SILHomeTabBarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EC92F0523A3962C00FD2219 /* SILHomeTabBarViewController.m */; }; 1EDA0A09253472990031961D /* SILAttErrorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EDA0A08253472990031961D /* SILAttErrorModel.swift */; }; 1EDA0A0B253472B50031961D /* SILErrorDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EDA0A0A253472B50031961D /* SILErrorDetailsViewModel.swift */; }; @@ -593,6 +605,9 @@ 1EEFB22A2524B45000DD2DD7 /* SILCharacteristicWriteEnumOptionsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EEFB2282524B45000DD2DD7 /* SILCharacteristicWriteEnumOptionsTableViewCell.swift */; }; 1EEFB22B2524B45000DD2DD7 /* SILCharacteristicWriteEnumOptionsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1EEFB2292524B45000DD2DD7 /* SILCharacteristicWriteEnumOptionsTableViewCell.xib */; }; 2B257CB817ADC2635883B007 /* Pods_WirelessGecko.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E7E1967C516A49F37A0A7B8 /* Pods_WirelessGecko.framework */; }; + 2F0DE91D258B7F4D00BEFF76 /* SILContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0DE91C258B7F4D00BEFF76 /* SILContextMenu.swift */; }; + 2FD579D7257933DD001D7E9E /* NSObject+SILAssociatedObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 2FD579D6257933DD001D7E9E /* NSObject+SILAssociatedObject.m */; }; + 2FD579DD2579352C001D7E9E /* UIViewController+SILContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD579DC2579352C001D7E9E /* UIViewController+SILContext.swift */; }; 491ADCBB1E71EEA300AC2E69 /* SILOTAUICoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 491ADCBA1E71EEA300AC2E69 /* SILOTAUICoordinator.m */; }; 491ADCBF1E72E58300AC2E69 /* SILOTASetupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 491ADCBD1E72E58300AC2E69 /* SILOTASetupViewController.m */; }; 491ADCC01E72E58300AC2E69 /* SILOTASetupViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 491ADCBE1E72E58300AC2E69 /* SILOTASetupViewController.xib */; }; @@ -628,7 +643,7 @@ 4C2CB43E240EBC550079040D /* SILMapNameEditorViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C2CB43C240EBC550079040D /* SILMapNameEditorViewController.xib */; }; 4C3BA285240D558A00CF3268 /* SILKeychain.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4C3BA284240D558A00CF3268 /* SILKeychain.storyboard */; }; 4C3BA289240D58DF00CF3268 /* SILKeychainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BA288240D58DF00CF3268 /* SILKeychainViewController.swift */; }; - 4C3BA28B240D599200CF3268 /* SILBrowserSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BA28A240D599200CF3268 /* SILBrowserSegmentedControl.swift */; }; + 4C3BA28B240D599200CF3268 /* SILBrowserMappingsSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BA28A240D599200CF3268 /* SILBrowserMappingsSegmentedControl.swift */; }; 4C4F2DE124080E50005D43BB /* SILRoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4F2DE024080E50005D43BB /* SILRoundedButton.swift */; }; 4C95EB7723FEC8600091FB5A /* SILAppBluetoothBrowserDetails.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4C95EB7623FEC8600091FB5A /* SILAppBluetoothBrowserDetails.storyboard */; }; 4C97A51123E86525000C6894 /* SILBrowserDeviceAdTypeViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C97A51023E86525000C6894 /* SILBrowserDeviceAdTypeViewCell.swift */; }; @@ -670,6 +685,10 @@ 79C40C6D1EC9F59B00B729BF /* SILHomeKitDebugDeviceTableViewCell~iphone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 79C40C6B1EC9F59B00B729BF /* SILHomeKitDebugDeviceTableViewCell~iphone.xib */; }; 79C40C701EC9F6A800B729BF /* SILHomeKitDebugDeviceTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 79C40C6F1EC9F6A800B729BF /* SILHomeKitDebugDeviceTableViewCell.m */; }; 79E62B4F1ECCA29400344CAA /* SILBluetoothSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 79E62B4E1ECCA29400344CAA /* SILBluetoothSearch.m */; }; + 8041CCC8256E5A0900C5C368 /* SILDescriptorTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8041CCC7256E5A0900C5C368 /* SILDescriptorTableViewCell.swift */; }; + 80521478255A7A510021385F /* SILSortViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80521477255A7A510021385F /* SILSortViewController.swift */; }; + 8052147E255A80F40021385F /* SILSortTypeViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8052147D255A80F40021385F /* SILSortTypeViewCell.swift */; }; + 80521484255A81070021385F /* SILSortModeViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80521483255A81070021385F /* SILSortModeViewCell.swift */; }; 807B139B2523355A0056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 807B139A2523355A0056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.xib */; }; 807B139C2523355A0056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 807B139A2523355A0056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.xib */; }; 807B139D2523355A0056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 807B139A2523355A0056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.xib */; }; @@ -679,6 +698,9 @@ 807B13D7252347390056CFCC /* SILEncodingTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 807B13D6252347390056CFCC /* SILEncodingTextField.m */; }; 807B13D8252347390056CFCC /* SILEncodingTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 807B13D6252347390056CFCC /* SILEncodingTextField.m */; }; 807B13D9252347390056CFCC /* SILEncodingTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 807B13D6252347390056CFCC /* SILEncodingTextField.m */; }; + 808B001E255D7B830062E954 /* SILSortViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 808B001D255D7B830062E954 /* SILSortViewModel.swift */; }; + 808E0D8A257E1A4400A3E4A8 /* SILBrowserDescriptorValueParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 808E0D89257E1A4400A3E4A8 /* SILBrowserDescriptorValueParser.swift */; }; + 808E0DC0257E42FE00A3E4A8 /* SILDescryptorTypeUUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 808E0DBF257E42FE00A3E4A8 /* SILDescryptorTypeUUID.swift */; }; 8096567B2524A6E4000E98F5 /* SILDebugCharacteristicEncodingFieldView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8096567A2524A6E4000E98F5 /* SILDebugCharacteristicEncodingFieldView.xib */; }; 8096567C2524A6E4000E98F5 /* SILDebugCharacteristicEncodingFieldView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8096567A2524A6E4000E98F5 /* SILDebugCharacteristicEncodingFieldView.xib */; }; 8096567D2524A6E4000E98F5 /* SILDebugCharacteristicEncodingFieldView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8096567A2524A6E4000E98F5 /* SILDebugCharacteristicEncodingFieldView.xib */; }; @@ -691,6 +713,7 @@ 80C6706925516A450083D20C /* SILTimeLimitRadioButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C6706825516A450083D20C /* SILTimeLimitRadioButtonState.swift */; }; 80C6706A25516A450083D20C /* SILTimeLimitRadioButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C6706825516A450083D20C /* SILTimeLimitRadioButtonState.swift */; }; 80C6706B25516A450083D20C /* SILTimeLimitRadioButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C6706825516A450083D20C /* SILTimeLimitRadioButtonState.swift */; }; + 80CCC0652564F8780018D649 /* SILBluetoothBrowserExpandableViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80CCC0642564F8780018D649 /* SILBluetoothBrowserExpandableViewManager.swift */; }; 9450946334099D8FAA384ABE /* Pods_BlueGeckoWithHomeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84342BE824D4F9248E13114A /* Pods_BlueGeckoWithHomeKit.framework */; }; 9B44734425137C6100355E0A /* Colours.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9B44734325137C6100355E0A /* Colours.xcassets */; }; 9B44734525137C6100355E0A /* Colours.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9B44734325137C6100355E0A /* Colours.xcassets */; }; @@ -725,7 +748,6 @@ DCB431111E8315CE00EE94F0 /* SILOTAHUDView.m in Sources */ = {isa = PBXBuildFile; fileRef = DCB431101E8315CE00EE94F0 /* SILOTAHUDView.m */; }; DCB431131E8315EF00EE94F0 /* SILOTAHUDView.xib in Resources */ = {isa = PBXBuildFile; fileRef = DCB431121E8315EF00EE94F0 /* SILOTAHUDView.xib */; }; DCE784DF1E7AF0930038C87A /* SILBeaconRegistryEntryViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = DCE784DE1E7AF0930038C87A /* SILBeaconRegistryEntryViewModel.m */; }; - E48275BE20D812BE006D1478 /* SILConnectedLightingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C39082F1FA8AC7A00934AD1 /* SILConnectedLightingViewController.xib */; }; E607A34E1A8D1F3000DAAFD3 /* SILCalibrationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E607A34D1A8D1F3000DAAFD3 /* SILCalibrationViewController.m */; }; E607A3511A8D4FD100DAAFD3 /* SILSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = E607A3501A8D4FD100DAAFD3 /* SILSettings.m */; }; E607A36C1A9627E600DAAFD3 /* SILTemperatureMeasurementTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E607A36B1A9627E600DAAFD3 /* SILTemperatureMeasurementTests.m */; }; @@ -743,7 +765,7 @@ E666CBE11A76B67900676C5C /* SILApp+AttributedProfiles.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBE01A76B67900676C5C /* SILApp+AttributedProfiles.m */; }; E666CBE51A76C0E800676C5C /* SILAppSelectionInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBE41A76C0E800676C5C /* SILAppSelectionInfoViewController.m */; }; E666CBE71A77298900676C5C /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E666CBE61A77298900676C5C /* QuartzCore.framework */; }; - E666CBEB1A77C75400676C5C /* SILSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBEA1A77C75400676C5C /* SILSegmentedControl.m */; }; + E666CBEB1A77C75400676C5C /* SILThermometerSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBEA1A77C75400676C5C /* SILThermometerSegmentedControl.m */; }; E6A37E411A82809B00510E39 /* SILCentralManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E6A37E401A82809B00510E39 /* SILCentralManager.m */; }; E6A37E471A82C1C400510E39 /* SILConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = E6A37E461A82C1C400510E39 /* SILConstants.m */; }; E6B3EC9C1AADD9BD005A687F /* SILRoundedViewBehaviour.m in Sources */ = {isa = PBXBuildFile; fileRef = E6B3EC9B1AADD9BD005A687F /* SILRoundedViewBehaviour.m */; }; @@ -909,15 +931,13 @@ 0C2FCBE11F9A7F7700F4F259 /* Entitlements.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Entitlements.entitlements; sourceTree = ""; }; 0C39082D1FA8AC7A00934AD1 /* SILConnectedLightingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SILConnectedLightingViewController.h; path = ConnectedLightingApp/SILConnectedLightingViewController.h; sourceTree = ""; }; 0C39082E1FA8AC7A00934AD1 /* SILConnectedLightingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SILConnectedLightingViewController.m; path = ConnectedLightingApp/SILConnectedLightingViewController.m; sourceTree = ""; }; - 0C39082F1FA8AC7A00934AD1 /* SILConnectedLightingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = SILConnectedLightingViewController.xib; path = ConnectedLightingApp/SILConnectedLightingViewController.xib; sourceTree = ""; }; 0C744A481FB369FD0016B3C5 /* ExportOptions.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = ExportOptions.plist; sourceTree = ""; }; 0CA91D794559B3C4861B03B4 /* Pods-WirelessGecko.release-wireless-gecko.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WirelessGecko.release-wireless-gecko.xcconfig"; path = "Pods/Target Support Files/Pods-WirelessGecko/Pods-WirelessGecko.release-wireless-gecko.xcconfig"; sourceTree = ""; }; 0CB9F1571F9F7DC60089DC65 /* BlueGeckoWithHomeKit.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BlueGeckoWithHomeKit.pch; sourceTree = ""; }; 0CB9F1581F9F7DE80089DC65 /* BlueGecko.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BlueGecko.pch; sourceTree = ""; }; 0CC658131F96C3C200953CDC /* SILDeviceSelectionViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SILDeviceSelectionViewModel.h; path = ../ViewModels/SILDeviceSelectionViewModel.h; sourceTree = ""; }; 0CC658141F96C3C200953CDC /* SILDeviceSelectionViewModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SILDeviceSelectionViewModel.m; path = ../ViewModels/SILDeviceSelectionViewModel.m; sourceTree = ""; }; - 0F34409420AF0E250067397C /* RangeTestStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = RangeTestStoryboard.storyboard; sourceTree = ""; }; - 0F34409720AF0E570067397C /* SILRangeTestModeSelectionViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SILRangeTestModeSelectionViewController.xib; sourceTree = ""; }; + 0F34409420AF0E250067397C /* SILAppTypeRangeTest.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILAppTypeRangeTest.storyboard; sourceTree = ""; }; 0F34409A20AF18BA0067397C /* SILRangeTestAppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRangeTestAppViewController.swift; sourceTree = ""; }; 0F3AC36520ADAABF0010091C /* SILRangeTestModeSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRangeTestModeSelectionViewController.swift; sourceTree = ""; }; 0F3EB6B620C524C30062A7C1 /* SILRangeTestAppViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRangeTestAppViewModel.swift; sourceTree = ""; }; @@ -936,6 +956,9 @@ 133D396225540A6600BFB484 /* ContextMenuOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuOption.swift; sourceTree = ""; }; 133D396C25540B5900BFB484 /* SILContextMenuCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILContextMenuCellView.swift; sourceTree = ""; }; 133D39C22554514600BFB484 /* SILAdvertiserNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILAdvertiserNotification.swift; sourceTree = ""; }; + 1394F95D2566BD4400795E5A /* SILBigButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILBigButton.swift; sourceTree = ""; }; + 13ACBA7E256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRangeTestSelectDeviceViewController.swift; sourceTree = ""; }; + 13ACBA86256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRangeTestAppContainerViewController.swift; sourceTree = ""; }; 14222593B3D5CABF925418CF /* Pods_SiliconLabsApp_Dev.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SiliconLabsApp_Dev.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 19AC23CE45AA832127AAE6DD /* SILEncodingPseudoFieldRowModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILEncodingPseudoFieldRowModel.h; sourceTree = ""; }; 19AC2956BD095FCA31424BC0 /* SILEncodingPseudoFieldRowModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILEncodingPseudoFieldRowModel.m; sourceTree = ""; }; @@ -1020,14 +1043,14 @@ 1E2D26D7244050430006B84A /* SILDocumentPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILDocumentPickerViewController.swift; sourceTree = ""; }; 1E2D9CAA23BA48D600816EC0 /* SILBluetoothBrowserViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILBluetoothBrowserViewController.h; sourceTree = ""; }; 1E2D9CAB23BA48D600816EC0 /* SILBluetoothBrowserViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SILBluetoothBrowserViewController.m; sourceTree = ""; }; + 1E3A15F7258B82EA00776A00 /* SILFilterBarViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILFilterBarViewModel.swift; sourceTree = ""; }; + 1E3A15FE258B830B00776A00 /* SILFilterBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILFilterBarViewController.swift; sourceTree = ""; }; 1E3FC7D7252B5E40002F740D /* UITextField+DoneButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+DoneButton.swift"; sourceTree = ""; }; 1E48A1E82484E27300C188C0 /* SILAnimatedUIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILAnimatedUIButton.swift; sourceTree = ""; }; 1E48A1EE2484FC0B00C188C0 /* SILToastModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILToastModelType.swift; sourceTree = ""; }; 1E4C37A12429095300C822E4 /* Roboto-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Bold.ttf"; sourceTree = ""; }; 1E4C37A22429095300C822E4 /* Roboto-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Medium.ttf"; sourceTree = ""; }; 1E4C37A32429095300C822E4 /* Roboto-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Regular.ttf"; sourceTree = ""; }; - 1E4C37D4242C996400C822E4 /* SILBluetoothBrowserExpandableViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SILBluetoothBrowserExpandableViewManager.m; sourceTree = ""; }; - 1E4C37D6242C997B00C822E4 /* SILBluetoothBrowserExpandableViewManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILBluetoothBrowserExpandableViewManager.h; sourceTree = ""; }; 1E56E9E42416800B00D5B92A /* NSString+SILBrowserNotifications.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+SILBrowserNotifications.m"; sourceTree = ""; }; 1E56E9E62416802200D5B92A /* NSString+SILBrowserNotifications.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+SILBrowserNotifications.h"; sourceTree = ""; }; 1E56E9E72416933300D5B92A /* SILBluetoothBrowser+Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SILBluetoothBrowser+Constants.h"; sourceTree = ""; }; @@ -1047,8 +1070,14 @@ 1E6AF80523CDB3C000EE8280 /* SILBrowserLogViewControllerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILBrowserLogViewControllerDelegate.h; sourceTree = ""; }; 1E6AF80623CDB47500EE8280 /* SILBrowserConnectionsViewControllerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILBrowserConnectionsViewControllerDelegate.h; sourceTree = ""; }; 1E6AF80723CDB4DF00EE8280 /* SILBrowserFilterViewControllerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILBrowserFilterViewControllerDelegate.h; sourceTree = ""; }; + 1E6CBCF02588AE1E00B11648 /* SILExitAdvertiserPopupViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILExitAdvertiserPopupViewModel.swift; sourceTree = ""; }; + 1E6CBCF72588AE3500B11648 /* SILExitAdvertiserPopupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILExitAdvertiserPopupViewController.swift; sourceTree = ""; }; + 1E6CBCF82588AE3500B11648 /* SILExitAdvertiserPopupViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SILExitAdvertiserPopupViewController.xib; sourceTree = ""; }; + 1E6CBD3F2588D23700B11648 /* SILSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILSegmentedControl.swift; sourceTree = ""; }; 1E70A97A240403320021C51B /* SILBrowserConnectionsViewModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SILBrowserConnectionsViewModel.m; sourceTree = ""; }; 1E70A97C240403470021C51B /* SILBrowserConnectionsViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILBrowserConnectionsViewModel.h; sourceTree = ""; }; + 1E8529822591F6480064F419 /* UIViewController+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Alert.swift"; sourceTree = ""; }; + 1E8529C025920C3B0064F419 /* SILBluetoothDisabledAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILBluetoothDisabledAlert.swift; sourceTree = ""; }; 1E90F5E32473E73E0013AABD /* SILDebugServicesMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILDebugServicesMenuViewController.swift; sourceTree = ""; }; 1E90F5E52473E8340013AABD /* SILDebugServicesMenuTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILDebugServicesMenuTableViewCell.swift; sourceTree = ""; }; 1E90F5E62473E8340013AABD /* SILDebugServicesMenuTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SILDebugServicesMenuTableViewCell.xib; sourceTree = ""; }; @@ -1087,7 +1116,6 @@ 1EC92EF623A3957D00FD2219 /* SILAppTypeRetailBeacon.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILAppTypeRetailBeacon.storyboard; sourceTree = ""; }; 1EC92EF823A3959200FD2219 /* SILAppBluetoothBrowser.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILAppBluetoothBrowser.storyboard; sourceTree = ""; }; 1EC92EFA23A395B200FD2219 /* SILAppTypeHomeKitDebug.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILAppTypeHomeKitDebug.storyboard; sourceTree = ""; }; - 1EC92EFC23A395C200FD2219 /* SILAppTypeRangeTest.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILAppTypeRangeTest.storyboard; sourceTree = ""; }; 1EC92F0423A3962C00FD2219 /* SILHomeTabBarViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILHomeTabBarViewController.h; sourceTree = ""; }; 1EC92F0523A3962C00FD2219 /* SILHomeTabBarViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SILHomeTabBarViewController.m; sourceTree = ""; }; 1EDA0A08253472990031961D /* SILAttErrorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILAttErrorModel.swift; sourceTree = ""; }; @@ -1135,6 +1163,10 @@ 1EEFB2282524B45000DD2DD7 /* SILCharacteristicWriteEnumOptionsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILCharacteristicWriteEnumOptionsTableViewCell.swift; sourceTree = ""; }; 1EEFB2292524B45000DD2DD7 /* SILCharacteristicWriteEnumOptionsTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SILCharacteristicWriteEnumOptionsTableViewCell.xib; sourceTree = ""; }; 257E756EE6E82EC7E361629D /* Pods-SiliconLabsApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SiliconLabsApp.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SiliconLabsApp/Pods-SiliconLabsApp.debug.xcconfig"; sourceTree = ""; }; + 2F0DE91C258B7F4D00BEFF76 /* SILContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILContextMenu.swift; sourceTree = ""; }; + 2FD579D5257933DD001D7E9E /* NSObject+SILAssociatedObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+SILAssociatedObject.h"; sourceTree = ""; }; + 2FD579D6257933DD001D7E9E /* NSObject+SILAssociatedObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SILAssociatedObject.m"; sourceTree = ""; }; + 2FD579DC2579352C001D7E9E /* UIViewController+SILContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+SILContext.swift"; sourceTree = ""; }; 40F66980CDB9137B6A8DE02E /* Pods-Wireless Gecko.debug-wireless-gecko.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Wireless Gecko.debug-wireless-gecko.xcconfig"; path = "Pods/Target Support Files/Pods-Wireless Gecko/Pods-Wireless Gecko.debug-wireless-gecko.xcconfig"; sourceTree = ""; }; 426AC50AF4550762BDD32A6E /* Pods_SiliconLabsApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SiliconLabsApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4491900FB788A5F0E74824C6 /* Pods-BlueGecko.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlueGecko.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BlueGecko/Pods-BlueGecko.debug.xcconfig"; sourceTree = ""; }; @@ -1195,7 +1227,7 @@ 4C2CB43C240EBC550079040D /* SILMapNameEditorViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SILMapNameEditorViewController.xib; sourceTree = ""; }; 4C3BA284240D558A00CF3268 /* SILKeychain.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILKeychain.storyboard; sourceTree = ""; }; 4C3BA288240D58DF00CF3268 /* SILKeychainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILKeychainViewController.swift; sourceTree = ""; }; - 4C3BA28A240D599200CF3268 /* SILBrowserSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILBrowserSegmentedControl.swift; sourceTree = ""; }; + 4C3BA28A240D599200CF3268 /* SILBrowserMappingsSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILBrowserMappingsSegmentedControl.swift; sourceTree = ""; }; 4C4F2DE024080E50005D43BB /* SILRoundedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRoundedButton.swift; sourceTree = ""; }; 4C95EB7623FEC8600091FB5A /* SILAppBluetoothBrowserDetails.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILAppBluetoothBrowserDetails.storyboard; sourceTree = ""; }; 4C97A51023E86525000C6894 /* SILBrowserDeviceAdTypeViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILBrowserDeviceAdTypeViewCell.swift; sourceTree = ""; }; @@ -1255,16 +1287,25 @@ 79E62B4E1ECCA29400344CAA /* SILBluetoothSearch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILBluetoothSearch.m; sourceTree = ""; }; 7AAD8E27028334FE640216CD /* Pods_SiliconLabsAppWithoutHomeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SiliconLabsAppWithoutHomeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7D7E90CFE18ED0DFB6F387D5 /* Pods-SiliconLabsAppWithoutHomeKit Dev.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SiliconLabsAppWithoutHomeKit Dev.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SiliconLabsAppWithoutHomeKit Dev/Pods-SiliconLabsAppWithoutHomeKit Dev.debug.xcconfig"; sourceTree = ""; }; + 800E3A8C25627DA300A55565 /* SILSortOption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILSortOption.h; sourceTree = ""; }; + 8041CCC7256E5A0900C5C368 /* SILDescriptorTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILDescriptorTableViewCell.swift; sourceTree = ""; }; + 80521477255A7A510021385F /* SILSortViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILSortViewController.swift; sourceTree = ""; }; + 8052147D255A80F40021385F /* SILSortTypeViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILSortTypeViewCell.swift; sourceTree = ""; }; + 80521483255A81070021385F /* SILSortModeViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILSortModeViewCell.swift; sourceTree = ""; }; 807B139A2523355A0056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SILDebugCharacteristicEncodingFieldEntryCell.xib; sourceTree = ""; }; 807B13A6252335A50056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILDebugCharacteristicEncodingFieldEntryCell.h; sourceTree = ""; }; 807B13A7252335A50056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SILDebugCharacteristicEncodingFieldEntryCell.m; sourceTree = ""; }; 807B13D5252347390056CFCC /* SILEncodingTextField.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILEncodingTextField.h; sourceTree = ""; }; 807B13D6252347390056CFCC /* SILEncodingTextField.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SILEncodingTextField.m; sourceTree = ""; }; + 808B001D255D7B830062E954 /* SILSortViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILSortViewModel.swift; sourceTree = ""; }; + 808E0D89257E1A4400A3E4A8 /* SILBrowserDescriptorValueParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILBrowserDescriptorValueParser.swift; sourceTree = ""; }; + 808E0DBF257E42FE00A3E4A8 /* SILDescryptorTypeUUID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILDescryptorTypeUUID.swift; sourceTree = ""; }; 8096567A2524A6E4000E98F5 /* SILDebugCharacteristicEncodingFieldView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SILDebugCharacteristicEncodingFieldView.xib; sourceTree = ""; }; 809656822524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILDebugCharacteristicEncodingFieldView.h; sourceTree = ""; }; 809656832524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SILDebugCharacteristicEncodingFieldView.m; sourceTree = ""; }; 80C67058255169950083D20C /* SILRadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRadioButton.swift; sourceTree = ""; }; 80C6706825516A450083D20C /* SILTimeLimitRadioButtonState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILTimeLimitRadioButtonState.swift; sourceTree = ""; }; + 80CCC0642564F8780018D649 /* SILBluetoothBrowserExpandableViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILBluetoothBrowserExpandableViewManager.swift; sourceTree = ""; }; 84342BE824D4F9248E13114A /* Pods_BlueGeckoWithHomeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BlueGeckoWithHomeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8A73A6BB1453DAE49320F05E /* Pods-SiliconLabsAppWithoutHomeKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SiliconLabsAppWithoutHomeKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-SiliconLabsAppWithoutHomeKit/Pods-SiliconLabsAppWithoutHomeKit.release.xcconfig"; sourceTree = ""; }; 8E2432EAF11ADE0582587F26 /* Pods-SiliconLabsApp Dev.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SiliconLabsApp Dev.release.xcconfig"; path = "Pods/Target Support Files/Pods-SiliconLabsApp Dev/Pods-SiliconLabsApp Dev.release.xcconfig"; sourceTree = ""; }; @@ -1354,8 +1395,8 @@ E666CBE31A76C0E800676C5C /* SILAppSelectionInfoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILAppSelectionInfoViewController.h; sourceTree = ""; }; E666CBE41A76C0E800676C5C /* SILAppSelectionInfoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILAppSelectionInfoViewController.m; sourceTree = ""; }; E666CBE61A77298900676C5C /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - E666CBE91A77C75400676C5C /* SILSegmentedControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILSegmentedControl.h; sourceTree = ""; }; - E666CBEA1A77C75400676C5C /* SILSegmentedControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILSegmentedControl.m; sourceTree = ""; }; + E666CBE91A77C75400676C5C /* SILThermometerSegmentedControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILThermometerSegmentedControl.h; sourceTree = ""; }; + E666CBEA1A77C75400676C5C /* SILThermometerSegmentedControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILThermometerSegmentedControl.m; sourceTree = ""; }; E6A37E3F1A82809B00510E39 /* SILCentralManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILCentralManager.h; sourceTree = ""; }; E6A37E401A82809B00510E39 /* SILCentralManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILCentralManager.m; sourceTree = ""; }; E6A37E451A82C1C400510E39 /* SILConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILConstants.h; sourceTree = ""; }; @@ -1552,6 +1593,7 @@ 07B1AD741BFCFF6900D4D454 /* ViewModels */ = { isa = PBXGroup; children = ( + 808B001B255D7B540062E954 /* BluetoothBrowser */, 1E26ECA025501A0A002FFAAB /* Advertiser */, 1EDA0A0A253472B50031961D /* SILErrorDetailsViewModel.swift */, 49BB94B01E68F2DC0068670E /* SILAdvertisementDataViewModel.h */, @@ -1629,6 +1671,7 @@ 07B8A8AB1BCD4D6E001948C1 /* ServicesTable */ = { isa = PBXGroup; children = ( + 803B32CA256E3D4500CC5EFA /* Descriptors */, 809656742524A6AA000E98F5 /* EncodingField */, 07B8A8C81BCD6E3B001948C1 /* SILDebugCharacteristicPropertyView.h */, 07B8A8C91BCD6E3B001948C1 /* SILDebugCharacteristicPropertyView.m */, @@ -1723,7 +1766,6 @@ 0C3908321FA9C02100934AD1 /* ConnectedLighting */ = { isa = PBXGroup; children = ( - 0C39082F1FA8AC7A00934AD1 /* SILConnectedLightingViewController.xib */, 0C39082D1FA8AC7A00934AD1 /* SILConnectedLightingViewController.h */, 0C39082E1FA8AC7A00934AD1 /* SILConnectedLightingViewController.m */, ); @@ -1733,8 +1775,6 @@ 0F34406020ADAB580067397C /* RangeTest */ = { isa = PBXGroup; children = ( - 0F34409720AF0E570067397C /* SILRangeTestModeSelectionViewController.xib */, - 0F34409420AF0E250067397C /* RangeTestStoryboard.storyboard */, ); path = RangeTest; sourceTree = ""; @@ -1750,6 +1790,8 @@ 0F3EB6B620C524C30062A7C1 /* SILRangeTestAppViewModel.swift */, 0FFDD024210F09A000CFFB5E /* SILRangeTestMovingAverageCalculator.swift */, 0F75B9D021395E3200E8D9E9 /* SILRangeTestTXValueUpdater.swift */, + 13ACBA7E256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift */, + 13ACBA86256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift */, ); path = RangeTestApp; sourceTree = ""; @@ -1826,6 +1868,7 @@ 1E26EC6B255019E0002FFAAB /* DropDown */, 1E26EC73255019E0002FFAAB /* ContextMenu */, 1E26EC76255019E0002FFAAB /* Details */, + 1E6CBCF62588AE3500B11648 /* ExitAdvertiserPopup */, 1E26EC7D255019E0002FFAAB /* Add16BitServiceDialog */, 1E26EC80255019E0002FFAAB /* RemoveServiceListWarningDialog */, 1E26EC71255019E0002FFAAB /* SILBaseWireframe.swift */, @@ -1896,6 +1939,7 @@ 1E26EC75255019E0002FFAAB /* SILContextMenuTransitioningDelegate.swift */, 133D39502554089500BFB484 /* SILContextMenuViewController.swift */, 133D396C25540B5900BFB484 /* SILContextMenuCellView.swift */, + 2F0DE91C258B7F4D00BEFF76 /* SILContextMenu.swift */, ); path = ContextMenu; sourceTree = ""; @@ -1940,6 +1984,7 @@ 1E26ECA125501A0A002FFAAB /* Add128BitServiceDialog */, 1E26ECA325501A0A002FFAAB /* Home */, 1E26ECAB25501A0A002FFAAB /* Details */, + 1E6CBCEF2588AE1E00B11648 /* ExitAdvertiserPopup */, 1E26ECB125501A0A002FFAAB /* Add16BitServiceDialog */, 1E26ECB425501A0A002FFAAB /* RemoveServiceListWarningDialog */, 1E26ECB325501A0A002FFAAB /* SILCellViewModel.swift */, @@ -2019,6 +2064,7 @@ 1E2D9CA923BA48B100816EC0 /* BluetoothBrowser */ = { isa = PBXGroup; children = ( + 80521472255A786D0021385F /* Sort */, 1EEFB2012521CDDE00DD2DD7 /* RefreshImageView */, 9B44734F25139BFD00355E0A /* ExitPopup */, 1E4C37D3242C992300C822E4 /* Utils */, @@ -2026,6 +2072,7 @@ 1E56E9ED24169C1A00D5B92A /* KeyChains */, 1EA0119223E08E7E003B3E62 /* Details */, 1E6AF7FB23CDB2F600EE8280 /* Filter */, + 1E3A15FD258B830B00776A00 /* FilterBar */, 1E6AF7FA23CDB2EF00EE8280 /* Connections */, 1E6AF7F923CDB2E500EE8280 /* Log */, 4C97A50B23E806A1000C6894 /* Views */, @@ -2035,6 +2082,14 @@ path = BluetoothBrowser; sourceTree = ""; }; + 1E3A15FD258B830B00776A00 /* FilterBar */ = { + isa = PBXGroup; + children = ( + 1E3A15FE258B830B00776A00 /* SILFilterBarViewController.swift */, + ); + path = FilterBar; + sourceTree = ""; + }; 1E48A1F02484FCA300C188C0 /* Toast */ = { isa = PBXGroup; children = ( @@ -2048,8 +2103,7 @@ 1E4C37D3242C992300C822E4 /* Utils */ = { isa = PBXGroup; children = ( - 1E4C37D6242C997B00C822E4 /* SILBluetoothBrowserExpandableViewManager.h */, - 1E4C37D4242C996400C822E4 /* SILBluetoothBrowserExpandableViewManager.m */, + 80CCC0642564F8780018D649 /* SILBluetoothBrowserExpandableViewManager.swift */, 1E48A1E82484E27300C188C0 /* SILAnimatedUIButton.swift */, 1E3FC7D7252B5E40002F740D /* UITextField+DoneButton.swift */, ); @@ -2063,7 +2117,7 @@ 4C3BA288240D58DF00CF3268 /* SILKeychainViewController.swift */, 4C2CB438240E7C840079040D /* SILMapCell.swift */, 4CEE629C241276EC00D88354 /* SILCell.swift */, - 4C3BA28A240D599200CF3268 /* SILBrowserSegmentedControl.swift */, + 4C3BA28A240D599200CF3268 /* SILBrowserMappingsSegmentedControl.swift */, ); path = KeyChains; sourceTree = ""; @@ -2147,6 +2201,23 @@ path = Filter; sourceTree = ""; }; + 1E6CBCEF2588AE1E00B11648 /* ExitAdvertiserPopup */ = { + isa = PBXGroup; + children = ( + 1E6CBCF02588AE1E00B11648 /* SILExitAdvertiserPopupViewModel.swift */, + ); + path = ExitAdvertiserPopup; + sourceTree = ""; + }; + 1E6CBCF62588AE3500B11648 /* ExitAdvertiserPopup */ = { + isa = PBXGroup; + children = ( + 1E6CBCF72588AE3500B11648 /* SILExitAdvertiserPopupViewController.swift */, + 1E6CBCF82588AE3500B11648 /* SILExitAdvertiserPopupViewController.xib */, + ); + path = ExitAdvertiserPopup; + sourceTree = ""; + }; 1E90F5E22473E7220013AABD /* Menu */ = { isa = PBXGroup; children = ( @@ -2226,7 +2297,7 @@ 1EC92F0923A39EDC00FD2219 /* DemoApps */ = { isa = PBXGroup; children = ( - 1EC92EFC23A395C200FD2219 /* SILAppTypeRangeTest.storyboard */, + 0F34409420AF0E250067397C /* SILAppTypeRangeTest.storyboard */, 1EC92EF423A3956B00FD2219 /* SILAppTypeHealthThermometer.storyboard */, 1EC92EF223A3955000FD2219 /* SILAppTypeConnectedLighting.storyboard */, ); @@ -2322,6 +2393,8 @@ 4C2C63372409242D0080CE76 /* SILFavoritePeripheral.swift */, 9BCA4F102514C2BB00E3AADD /* SILBrowserSettings.h */, 9BCA4F112514C2BB00E3AADD /* SILBrowserSettings.m */, + 808E0D89257E1A4400A3E4A8 /* SILBrowserDescriptorValueParser.swift */, + 808E0DBF257E42FE00A3E4A8 /* SILDescryptorTypeUUID.swift */, ); path = Browser; sourceTree = ""; @@ -2465,6 +2538,42 @@ name = HomeKit; sourceTree = ""; }; + 803B32CA256E3D4500CC5EFA /* Descriptors */ = { + isa = PBXGroup; + children = ( + 8041CCC7256E5A0900C5C368 /* SILDescriptorTableViewCell.swift */, + ); + path = Descriptors; + sourceTree = ""; + }; + 80521472255A786D0021385F /* Sort */ = { + isa = PBXGroup; + children = ( + 80521477255A7A510021385F /* SILSortViewController.swift */, + 8052147D255A80F40021385F /* SILSortTypeViewCell.swift */, + 80521483255A81070021385F /* SILSortModeViewCell.swift */, + ); + path = Sort; + sourceTree = ""; + }; + 808B001B255D7B540062E954 /* BluetoothBrowser */ = { + isa = PBXGroup; + children = ( + 1E3A15F7258B82EA00776A00 /* SILFilterBarViewModel.swift */, + 808B001C255D7B650062E954 /* Sort */, + ); + path = BluetoothBrowser; + sourceTree = ""; + }; + 808B001C255D7B650062E954 /* Sort */ = { + isa = PBXGroup; + children = ( + 808B001D255D7B830062E954 /* SILSortViewModel.swift */, + 800E3A8C25627DA300A55565 /* SILSortOption.h */, + ); + path = Sort; + sourceTree = ""; + }; 809656742524A6AA000E98F5 /* EncodingField */ = { isa = PBXGroup; children = ( @@ -2691,6 +2800,10 @@ 1E56E9EC2416976600D5B92A /* SILStoryboard+Constants.h */, 1E56E9EA2416974500D5B92A /* SILStoryboard+Constants.m */, 1EBC4E3C2452DFBF005B8767 /* CBAttribute+UUID.swift */, + 2FD579D5257933DD001D7E9E /* NSObject+SILAssociatedObject.h */, + 2FD579D6257933DD001D7E9E /* NSObject+SILAssociatedObject.m */, + 2FD579DC2579352C001D7E9E /* UIViewController+SILContext.swift */, + 1E8529822591F6480064F419 /* UIViewController+Alert.swift */, ); path = Categories; sourceTree = ""; @@ -2735,15 +2848,17 @@ E666CBE81A77C70700676C5C /* Views */ = { isa = PBXGroup; children = ( + 1394F95D2566BD4400795E5A /* SILBigButton.swift */, 1E26ECC425501A20002FFAAB /* SILPrimaryButton.swift */, 1E26ECC525501A20002FFAAB /* SILSwitch.swift */, 1E26EC35254B1528002FFAAB /* SILShadowView.h */, 1E26EC36254B1528002FFAAB /* SILShadowView.m */, 496A42F11E7C582F006E87F2 /* SILBigRedButton.h */, 496A42F21E7C582F006E87F2 /* SILBigRedButton.m */, - E666CBE91A77C75400676C5C /* SILSegmentedControl.h */, - E666CBEA1A77C75400676C5C /* SILSegmentedControl.m */, + E666CBE91A77C75400676C5C /* SILThermometerSegmentedControl.h */, + E666CBEA1A77C75400676C5C /* SILThermometerSegmentedControl.m */, 80C67058255169950083D20C /* SILRadioButton.swift */, + 1E6CBD3F2588D23700B11648 /* SILSegmentedControl.swift */, ); path = Views; sourceTree = ""; @@ -2769,6 +2884,7 @@ E642661C1A8527C0006C6B2F /* SILRSSIMeasurementTable.m */, E64266181A84F1D6006C6B2F /* SILRSSIMeasurement.h */, E64266191A84F1D6006C6B2F /* SILRSSIMeasurement.m */, + 1E8529C025920C3B0064F419 /* SILBluetoothDisabledAlert.swift */, ); path = BluetoothControllers; sourceTree = ""; @@ -3059,24 +3175,22 @@ 0C2FCB9F1F9A542300F4F259 /* SILBeaconRegistryEntryCell.xib in Resources */, 1E26EC45254B1BFC002FFAAB /* SILDebugCharacteristicEncodingFieldTableViewCell.xib in Resources */, 1EEFB215252218DD00DD2DD7 /* SILCharacteristicWriteFieldTableViewCell.xib in Resources */, - E48275BE20D812BE006D1478 /* SILConnectedLightingViewController.xib in Resources */, 0C2FCBA01F9A542300F4F259 /* SILDebugDeviceTableViewCell~iphone.xib in Resources */, 0C2FCBA11F9A542300F4F259 /* SILDebugDeviceTableViewCell~ipad.xib in Resources */, 0C2FCBA21F9A542300F4F259 /* SILRetailBeaconDetailsHeaderView.xib in Resources */, 0C2FCBA31F9A542300F4F259 /* SILDebugAdvDetailTableViewCell.xib in Resources */, 1EABF869251B7511006358C8 /* SILCharacteristicWriteViewController.xib in Resources */, 1EEFB2252524B13A00DD2DD7 /* SILCharacteristicWriteEnumOptionsTableViewController.xib in Resources */, - 1EC92EFD23A395C200FD2219 /* SILAppTypeRangeTest.storyboard in Resources */, 1E5DB81223A3B09100929EBB /* SILAppSelectionCollectionViewCell.xib in Resources */, - 0F34409920AF0E570067397C /* SILRangeTestModeSelectionViewController.xib in Resources */, 0C2FCBA41F9A542300F4F259 /* SILDebugDeviceViewController.xib in Resources */, 133D395D2554093900BFB484 /* SILContextMenuStoryboard.storyboard in Resources */, 0C2FCBA51F9A542300F4F259 /* SILDebugCharacteristicToggleFieldTableViewCell.xib in Resources */, + 1E6CBCFA2588AE3500B11648 /* SILExitAdvertiserPopupViewController.xib in Resources */, 1E4C37A42429095300C822E4 /* Roboto-Bold.ttf in Resources */, 4C9EAB6424042FB00023FFB1 /* Roboto-ThinItalic.ttf in Resources */, 0C2FCBA61F9A542300F4F259 /* SILOTAHUDView.xib in Resources */, 0C2FCBA71F9A542300F4F259 /* SILDebugCharacteristicValueFieldTableViewCell.xib in Resources */, - 0F34409620AF0E250067397C /* RangeTestStoryboard.storyboard in Resources */, + 0F34409620AF0E250067397C /* SILAppTypeRangeTest.storyboard in Resources */, 0C2FCBA81F9A542300F4F259 /* SILActivityBarViewController.xib in Resources */, 1EEFB22B2524B45000DD2DD7 /* SILCharacteristicWriteEnumOptionsTableViewCell.xib in Resources */, 0C2FCBAA1F9A542300F4F259 /* SILDebugAdvDetailCollectionViewCell.xib in Resources */, @@ -3147,19 +3261,17 @@ 0F4E519121525FDC00F58ACE /* SILOTAProgressViewController.xib in Resources */, 0F4E519221525FDC00F58ACE /* SILDebugDeviceFilterViewController.xib in Resources */, 0F4E519321525FDC00F58ACE /* SILBeaconRegistryEntryCell.xib in Resources */, - 0F4E519421525FDC00F58ACE /* SILConnectedLightingViewController.xib in Resources */, 0F4E519521525FDC00F58ACE /* SILDebugDeviceTableViewCell~iphone.xib in Resources */, 0F4E519621525FDC00F58ACE /* SILDebugDeviceTableViewCell~ipad.xib in Resources */, 0F4E519721525FDC00F58ACE /* SILRetailBeaconDetailsHeaderView.xib in Resources */, 0F4E519821525FDC00F58ACE /* SILDebugAdvDetailTableViewCell.xib in Resources */, - 0F4E519921525FDC00F58ACE /* SILRangeTestModeSelectionViewController.xib in Resources */, 0F4E519A21525FDC00F58ACE /* SILDebugDeviceViewController.xib in Resources */, 9B44736C25139CA100355E0A /* SILExitPopupViewController.xib in Resources */, 0F4E519B21525FDC00F58ACE /* SILDebugCharacteristicToggleFieldTableViewCell.xib in Resources */, 9B44734625137C6100355E0A /* Colours.xcassets in Resources */, 0F4E519C21525FDC00F58ACE /* SILOTAHUDView.xib in Resources */, 0F4E519D21525FDC00F58ACE /* SILDebugCharacteristicValueFieldTableViewCell.xib in Resources */, - 0F4E519E21525FDC00F58ACE /* RangeTestStoryboard.storyboard in Resources */, + 0F4E519E21525FDC00F58ACE /* SILAppTypeRangeTest.storyboard in Resources */, 0F4E519F21525FDC00F58ACE /* SILActivityBarViewController.xib in Resources */, 0F4E51A121525FDC00F58ACE /* SILDebugAdvDetailCollectionViewCell.xib in Resources */, 0F4E51A221525FDC00F58ACE /* SILDebugServiceTableViewCell~iphone.xib in Resources */, @@ -3202,7 +3314,6 @@ 078138071BE40AA5001EFE7E /* SILDebugCharacteristicToggleFieldTableViewCell.xib in Resources */, DCB431131E8315EF00EE94F0 /* SILOTAHUDView.xib in Resources */, 078138081BE40AA5001EFE7E /* SILDebugCharacteristicValueFieldTableViewCell.xib in Resources */, - 0F34409820AF0E570067397C /* SILRangeTestModeSelectionViewController.xib in Resources */, 492CA4B21E4E0DD700502AC9 /* SILActivityBarViewController.xib in Resources */, 078137F01BE40A8C001EFE7E /* SILDebugAdvDetailCollectionViewCell.xib in Resources */, 8096567C2524A6E4000E98F5 /* SILDebugCharacteristicEncodingFieldView.xib in Resources */, @@ -3213,10 +3324,9 @@ 07BBA7501BD599F800C2B07E /* XML in Resources */, 078138031BE40AA5001EFE7E /* SILDebugCharacteristicEnumerationFieldTableViewCell.xib in Resources */, 49FB4BED1E7A2EC200223F3E /* SILPopoverViewController.xib in Resources */, - 0F34409520AF0E250067397C /* RangeTestStoryboard.storyboard in Resources */, + 0F34409520AF0E250067397C /* SILAppTypeRangeTest.storyboard in Resources */, 0763AC4C1BFE8F5400EBE94B /* SILDebugAdvDetailsViewController.xib in Resources */, AA0FF4081EFC11AD007C14D3 /* SILRetailBeaconDetailsViewController.xib in Resources */, - 0C3908311FA8AC7A00934AD1 /* SILConnectedLightingViewController.xib in Resources */, 078138041BE40AA5001EFE7E /* SILDebugCharacteristicPropertyView.xib in Resources */, 0781381B1BE40D4C001EFE7E /* SILRetailBeaconAppViewController.xib in Resources */, 0781380B1BE40AA5001EFE7E /* SILDebugServiceTableViewCell~ipad.xib in Resources */, @@ -3476,17 +3586,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1E3A15FF258B830B00776A00 /* SILFilterBarViewController.swift in Sources */, 1E90F5F42477B1D00013AABD /* SILKeychainInfoViewControllerDelegate.swift in Sources */, 1EEE315D23F58C210076E731 /* SILBrowserFilterBeaconTypeViewController.m in Sources */, 1E1907B123FD850700DD014C /* SILSavedSearchesRealmModel.m in Sources */, 1E56E9E92416934D00D5B92A /* SILBluetoothBrowser+Constants.m in Sources */, 1E26ECC725501A20002FFAAB /* SILSwitch.swift in Sources */, 1EEE316023F58C4D0076E731 /* SILBrowserFilterSavedSearchesViewController.m in Sources */, + 2FD579DD2579352C001D7E9E /* UIViewController+SILContext.swift in Sources */, 1E26EC99255019E0002FFAAB /* SILAdvertiserDetailsWireframe.swift in Sources */, 0C2FCB031F9A542300F4F259 /* SILBluetoothFieldModel.m in Sources */, + 1E3A15F8258B82EA00776A00 /* SILFilterBarViewModel.swift in Sources */, 1E26ECC625501A20002FFAAB /* SILPrimaryButton.swift in Sources */, 0C2FCB051F9A542300F4F259 /* SILOTAUICoordinator.m in Sources */, 1E93CDE423A8EBF90022640E /* SILUILabels.m in Sources */, + 8041CCC8256E5A0900C5C368 /* SILDescriptorTableViewCell.swift in Sources */, 0C2FCB061F9A542300F4F259 /* SILActivityBarViewController.m in Sources */, 1E26EC53255019A1002FFAAB /* SILAdvertisingSetEntity.swift in Sources */, 1E90F5EC24767C680013AABD /* UIViewController+Toast.swift in Sources */, @@ -3519,6 +3633,7 @@ 0C2FCB171F9A542300F4F259 /* SILCharacteristicFieldValueResolver.m in Sources */, 809656842524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.m in Sources */, 1E26EC9A255019E0002FFAAB /* SILAdvertisingDataValueCellView.swift in Sources */, + 13ACBA7F256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift in Sources */, 4C97A51323E87364000C6894 /* SILBrowserDeviceViewCell.swift in Sources */, 1E93CDEA23A91FA20022640E /* SILAppSelectionViewController.m in Sources */, 1EEE315A23F57CED0076E731 /* SILSavedSearchesTableViewCell.m in Sources */, @@ -3541,6 +3656,7 @@ 1E26ECB725501A0A002FFAAB /* SILAdvertiserCellViewModel.swift in Sources */, 0C2FCB1F1F9A542300F4F259 /* SILDoubleKeyDictionaryPair.m in Sources */, 1E1A43C1246EA3CB0052DE8D /* SILAdTypeCBPeripheralDecoder.swift in Sources */, + 8052147E255A80F40021385F /* SILSortTypeViewCell.swift in Sources */, 1E586CE023FBFEF900E2C385 /* SILBrowserSavedSearches.m in Sources */, 0C2FCB211F9A542300F4F259 /* SILBluetoothEnumerationModel.m in Sources */, 0C2FCB221F9A542300F4F259 /* RSSISliderTableViewCell.swift in Sources */, @@ -3549,6 +3665,7 @@ 4C2C63382409242D0080CE76 /* SILFavoritePeripheral.swift in Sources */, 807B13A8252335A50056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.m in Sources */, 4C2CB433240E5A340079040D /* SILCharacteristicMap.swift in Sources */, + 2FD579D7257933DD001D7E9E /* NSObject+SILAssociatedObject.m in Sources */, 0C2FCB281F9A542300F4F259 /* SILDebugCharacteristicValueFieldTableViewCell.m in Sources */, 1EDCA5B823E1C05B00F78B14 /* SILBrowserConnectionsTableViewCell.m in Sources */, 0C2FCB291F9A542300F4F259 /* UIImage+SILImages.m in Sources */, @@ -3561,7 +3678,7 @@ 0C2FCB2E1F9A542300F4F259 /* SILAdvertisementDataModel.m in Sources */, 1EEFB2082521CDDE00DD2DD7 /* SILRefreshImageModel.m in Sources */, 1E26ECC925529B93002FFAAB /* SILScanResponseViewModelBuilder.swift in Sources */, - 4C3BA28B240D599200CF3268 /* SILBrowserSegmentedControl.swift in Sources */, + 4C3BA28B240D599200CF3268 /* SILBrowserMappingsSegmentedControl.swift in Sources */, 0C2FCB2F1F9A542300F4F259 /* SILCharacteristicTableModel.m in Sources */, 1E26EC88255019E0002FFAAB /* SILLocalNameSettingViewController.swift in Sources */, 1EDCA5AF23E1A6CE00F78B14 /* SILBrowserLogFilterViewController.m in Sources */, @@ -3572,9 +3689,8 @@ 0C2FCB311F9A542300F4F259 /* SILDebugCharacteristicPropertyView.m in Sources */, 0C2FCB321F9A542300F4F259 /* CBPeripheral+Services.m in Sources */, 0C2FCB331F9A542300F4F259 /* SILDebugProperty.m in Sources */, - 1E4C37D5242C996400C822E4 /* SILBluetoothBrowserExpandableViewManager.m in Sources */, 0C2FCB341F9A542300F4F259 /* SILPopoverViewController.m in Sources */, - 0C2FCB351F9A542300F4F259 /* SILSegmentedControl.m in Sources */, + 0C2FCB351F9A542300F4F259 /* SILThermometerSegmentedControl.m in Sources */, 1EA0119123DB249F003B3E62 /* SILDevelopNavigationViewController.m in Sources */, 1EEFB2072521CDDE00DD2DD7 /* SILRefreshImageView.m in Sources */, 0C2FCB361F9A542300F4F259 /* SILRetailBeaconDetailsHeaderView.m in Sources */, @@ -3588,11 +3704,15 @@ 4D8E8168238C30AE00E7EC35 /* SILCharacteristicFieldValueResolverContext.m in Sources */, 4C2CB439240E7C840079040D /* SILMapCell.swift in Sources */, 0C2FCB3C1F9A542300F4F259 /* SILHeartRateMeasurement.m in Sources */, + 808B001E255D7B830062E954 /* SILSortViewModel.swift in Sources */, 0C2FCB3D1F9A542300F4F259 /* SILApp+AttributedProfiles.m in Sources */, 1E70A97B240403320021C51B /* SILBrowserConnectionsViewModel.m in Sources */, 0C2FCB3E1F9A542300F4F259 /* SILOTAFirmwareFile.m in Sources */, 1E90F5E42473E73E0013AABD /* SILDebugServicesMenuViewController.swift in Sources */, + 2F0DE91D258B7F4D00BEFF76 /* SILContextMenu.swift in Sources */, + 1E6CBCF92588AE3500B11648 /* SILExitAdvertiserPopupViewController.swift in Sources */, 0C2FCB3F1F9A542300F4F259 /* SILBitRowModel.m in Sources */, + 1E8529832591F6480064F419 /* UIViewController+Alert.swift in Sources */, 1E1A43C324728F770052DE8D /* SILAdTypeEddystoneDecoder.swift in Sources */, 0C2FCB401F9A542300F4F259 /* SILRetailBeaconDetailsViewController.m in Sources */, 0C2FCB411F9A542300F4F259 /* SILCollectionViewRightAlignedFlowLayout.m in Sources */, @@ -3603,6 +3723,7 @@ 0C6745FF1FAC033F0032CBF5 /* SILCentralManager.m in Sources */, 0C2FCB471F9A542300F4F259 /* CBService+Categories.m in Sources */, 1E26EC85255019E0002FFAAB /* SILAdvertiserCellView.swift in Sources */, + 13ACBA87256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift in Sources */, 1E26EC9C255019E0002FFAAB /* SILAdvertiserAdd16BitServiceDialogViewController.swift in Sources */, 0C2FCB481F9A542300F4F259 /* SILEddystoneBeaconViewModel.m in Sources */, 0C2FCB491F9A542300F4F259 /* SILBluetoothDescriptorModel.m in Sources */, @@ -3640,6 +3761,7 @@ 1E90F5E72473E8340013AABD /* SILDebugServicesMenuTableViewCell.swift in Sources */, 0C2FCB531F9A542300F4F259 /* SILBodySensorLocation.m in Sources */, 1E26EC8C255019E0002FFAAB /* SILAdvertiserHomeViewController.swift in Sources */, + 80521484255A81070021385F /* SILSortModeViewCell.swift in Sources */, 0C2FCB541F9A542300F4F259 /* TextFieldTableViewCell.swift in Sources */, 1E26EC91255019E0002FFAAB /* SILDropDownCellView.swift in Sources */, 1E48A1E92484E27300C188C0 /* SILAnimatedUIButton.swift in Sources */, @@ -3652,7 +3774,9 @@ 1E6AF80123CDB32800EE8280 /* SILBrowserConnectionsViewController.m in Sources */, 9B44736125139C5900355E0A /* SILExitPopupViewController.m in Sources */, 4C4F2DE124080E50005D43BB /* SILRoundedButton.swift in Sources */, + 80CCC0652564F8780018D649 /* SILBluetoothBrowserExpandableViewManager.swift in Sources */, 0C2FCB571F9A542300F4F259 /* UIFont+Extensions.swift in Sources */, + 1E6CBCF12588AE1E00B11648 /* SILExitAdvertiserPopupViewModel.swift in Sources */, 0C2FCB581F9A542300F4F259 /* SILDebugServicesViewController.m in Sources */, 4C97A51123E86525000C6894 /* SILBrowserDeviceAdTypeViewCell.swift in Sources */, 4CEE629D241276EC00D88354 /* SILCell.swift in Sources */, @@ -3680,6 +3804,7 @@ 0C2FCB651F9A542300F4F259 /* SILDebugCharacteristicToggleFieldTableViewCell.m in Sources */, 1E26ECBE25501A0A002FFAAB /* SILAdvertisingDataTitleCellViewModel.swift in Sources */, 1E113D08244ECD6600442752 /* SILDiscoveredPeripheralIdentifierProvider.swift in Sources */, + 1394F95E2566BD4400795E5A /* SILBigButton.swift in Sources */, 133D39C32554514600BFB484 /* SILAdvertiserNotification.swift in Sources */, 1E3FC7D8252B5E40002F740D /* UITextField+DoneButton.swift in Sources */, 1E56E9EB2416974500D5B92A /* SILStoryboard+Constants.m in Sources */, @@ -3700,6 +3825,7 @@ 4D9E26262212CA7000617DBA /* SILRangeTestBoardInfo.swift in Sources */, 0C2FCB721F9A542300F4F259 /* SILCentralManagerBuilder.m in Sources */, 0F34409C20AF18BA0067397C /* SILRangeTestAppViewController.swift in Sources */, + 808E0D8A257E1A4400A3E4A8 /* SILBrowserDescriptorValueParser.swift in Sources */, 1EC92F0623A3962C00FD2219 /* SILHomeTabBarViewController.m in Sources */, 0C2FCB731F9A542300F4F259 /* SILOTAFirmwareUpdateViewModel.m in Sources */, 0C2FCB741F9A542300F4F259 /* UIView+SILAnimations.m in Sources */, @@ -3709,9 +3835,11 @@ 0C2FCB781F9A542300F4F259 /* UIImage+SILHelpers.m in Sources */, 1E1AE4AE247FCB7F00E5F238 /* SILRealmConfiguration.swift in Sources */, 1E48A1EF2484FC0B00C188C0 /* SILToastModelType.swift in Sources */, + 808E0DC0257E42FE00A3E4A8 /* SILDescryptorTypeUUID.swift in Sources */, 1E26EC56255019A1002FFAAB /* SILAdvertisingServiceRepository.swift in Sources */, 1E2D9CAC23BA48D600816EC0 /* SILBluetoothBrowserViewController.m in Sources */, 0C2FCB791F9A542300F4F259 /* SILOTAHUDView.m in Sources */, + 80521478255A7A510021385F /* SILSortViewController.swift in Sources */, 0C2FCB7A1F9A542300F4F259 /* SILServiceTableModel.m in Sources */, 0C2FCB7B1F9A542300F4F259 /* SILBluetoothSearch.m in Sources */, 0C2FCB7D1F9A542300F4F259 /* SILBluetoothCharacteristicModel.m in Sources */, @@ -3728,6 +3856,7 @@ 0C2FCB861F9A542300F4F259 /* SILRSSIMeasurement.m in Sources */, 1E26EC55255019A1002FFAAB /* SILAdvertisingSetRepository.swift in Sources */, 1E90F5EA2473EA650013AABD /* SILDebugServicesMenuViewControllerDelegate.swift in Sources */, + 1E6CBD402588D23700B11648 /* SILSegmentedControl.swift in Sources */, 0C2FCB871F9A542300F4F259 /* SILIBeaconViewModel.m in Sources */, 1E26EC8A255019E0002FFAAB /* SILAdvertiserRemoveWarningViewController.swift in Sources */, 4C2CB435240E725C0079040D /* SILMap.swift in Sources */, @@ -3748,6 +3877,7 @@ 1E26EC8F255019E0002FFAAB /* SILPassthroughView.swift in Sources */, 0C2FCB911F9A542300F4F259 /* SILBarGraphCollectionViewCell.m in Sources */, 1EDA0A11253472EE0031961D /* SILErrorDetailsViewControllerDelegate.swift in Sources */, + 1E8529C125920C3B0064F419 /* SILBluetoothDisabledAlert.swift in Sources */, 1E6AF80423CDB33600EE8280 /* SILBrowserFilterViewController.m in Sources */, 0F69656A20ECD32A0083C32A /* SILRangeTestManufacturerData.swift in Sources */, 0C2FCB931F9A542300F4F259 /* NSError+SILHelpers.m in Sources */, @@ -3814,7 +3944,8 @@ 0F4E512321525FDC00F58ACE /* CBPeripheral+Services.m in Sources */, 0F4E512421525FDC00F58ACE /* SILDebugProperty.m in Sources */, 0F4E512521525FDC00F58ACE /* SILPopoverViewController.m in Sources */, - 0F4E512621525FDC00F58ACE /* SILSegmentedControl.m in Sources */, + 0F4E512621525FDC00F58ACE /* SILThermometerSegmentedControl.m in Sources */, + 1E6CBD422588D23700B11648 /* SILSegmentedControl.swift in Sources */, 0F4E512721525FDC00F58ACE /* SILRetailBeaconDetailsHeaderView.m in Sources */, 0F4E512821525FDC00F58ACE /* SILOTAHUDPeripheralViewModel.m in Sources */, 0F4E512921525FDC00F58ACE /* SILDebugAdvDetailTableViewCell.m in Sources */, @@ -3846,9 +3977,11 @@ 0F4E514221525FDC00F58ACE /* SILKeyValueViewModel.m in Sources */, 0F4E514321525FDC00F58ACE /* SILDebugCharacteristicEnumerationFieldTableViewCell.m in Sources */, 0F4E514421525FDC00F58ACE /* SILRangeTestCharacteristic.swift in Sources */, + 13ACBA81256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift in Sources */, 0F4E514521525FDC00F58ACE /* SILTemperatureMeasurement.m in Sources */, 0F4E514621525FDC00F58ACE /* SILRetailBeaconAppViewController.m in Sources */, 0F4E514721525FDC00F58ACE /* SILBeaconDataViewModel.m in Sources */, + 13ACBA89256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift in Sources */, 0F4E514821525FDC00F58ACE /* SILOTAFirmwareUpdate.m in Sources */, 0F4E514921525FDC00F58ACE /* SILBodySensorLocation.m in Sources */, 0F4E514A21525FDC00F58ACE /* TextFieldTableViewCell.swift in Sources */, @@ -3900,6 +4033,7 @@ 0F4E517921525FDC00F58ACE /* SILDebugPopoverViewController.m in Sources */, 0F4E517A21525FDC00F58ACE /* SILRSSIMeasurement.m in Sources */, 0F4E517B21525FDC00F58ACE /* SILIBeaconViewModel.m in Sources */, + 1394F9602566BD4400795E5A /* SILBigButton.swift in Sources */, 0F4E517C21525FDC00F58ACE /* SILBluetoothBitModel.m in Sources */, 0F4E517E21525FDC00F58ACE /* SILRangeTestTXValueUpdater.swift in Sources */, 0F4E517F21525FDC00F58ACE /* NSDictionary+SILErrorCode.m in Sources */, @@ -3973,7 +4107,7 @@ 07B8A89C1BC5F872001948C1 /* SILDebugProperty.m in Sources */, 49FB4BEC1E7A2EC200223F3E /* SILPopoverViewController.m in Sources */, 0F34409B20AF18BA0067397C /* SILRangeTestAppViewController.swift in Sources */, - E666CBEB1A77C75400676C5C /* SILSegmentedControl.m in Sources */, + E666CBEB1A77C75400676C5C /* SILThermometerSegmentedControl.m in Sources */, AA5F62EB1F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.m in Sources */, DC4E508E1E92E507004FD829 /* SILOTAHUDPeripheralViewModel.m in Sources */, 0C3908301FA8AC7A00934AD1 /* SILConnectedLightingViewController.m in Sources */, @@ -3989,6 +4123,7 @@ 494F93CA1E7719D40057C1E0 /* SILOTAFirmwareFile.m in Sources */, 0752B77C1BDED93F0064CBF0 /* SILBitRowModel.m in Sources */, AA0FF40B1EFC368A007C14D3 /* SILRetailBeaconDetailsViewController.m in Sources */, + 1E6CBD412588D23700B11648 /* SILSegmentedControl.swift in Sources */, E6C8FF0F1A71A9A900DF062F /* SILCollectionViewRightAlignedFlowLayout.m in Sources */, E666CBDB1A767DCB00676C5C /* UIColor+SILColors.m in Sources */, 07B1AD8B1BFD4C8F00D4D454 /* SILBeaconRegistryEntryCell.m in Sources */, @@ -4011,6 +4146,7 @@ 0752B7751BDEC2C00064CBF0 /* SILCharacteristicFieldBuilder.m in Sources */, E621B34C1A655B3F00223C5A /* SILApp.m in Sources */, 807B13A9252335A50056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.m in Sources */, + 13ACBA80256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift in Sources */, AA78173F1F33A86100B37B09 /* UIFont+Extensions.swift in Sources */, 07B8A8D31BCD6E3B001948C1 /* SILDebugServicesViewController.m in Sources */, E6CCCFF01A73040C0004B2F4 /* SILBeacon.m in Sources */, @@ -4053,6 +4189,7 @@ 07BBA7531BD5BD3500C2B07E /* SILBluetoothServiceModel.m in Sources */, E6C667241B0A5BD90083C248 /* SILWeakNotificationPair.m in Sources */, AA4D435E1F2A3F15001EE0D2 /* GradientSlider.swift in Sources */, + 13ACBA88256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift in Sources */, E621B34F1A65645A00223C5A /* SILAppSelectionCollectionViewCell.m in Sources */, E6CAA2261A64011900A49DAF /* main.m in Sources */, AAA154A81F20F3D8002078B5 /* DebugDeviceViewModel.swift in Sources */, @@ -4060,6 +4197,7 @@ E642661A1A84F1D6006C6B2F /* SILRSSIMeasurement.m in Sources */, 07B1AD7E1BFD0A2100D4D454 /* SILIBeaconViewModel.m in Sources */, 7985D65F1EC6020F000FA0FB /* HMHomeManager+SILHelpers.m in Sources */, + 1394F95F2566BD4400795E5A /* SILBigButton.swift in Sources */, 07BBA75C1BD6862A00C2B07E /* SILBluetoothBitModel.m in Sources */, 494F93AB1E757FB10057C1E0 /* NSDictionary+SILErrorCode.m in Sources */, AA4D435C1F2A35A3001EE0D2 /* GradientView.swift in Sources */, @@ -4137,7 +4275,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_RESOURCE_RULES_PATH = ""; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 29; + CURRENT_PROJECT_VERSION = 31; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 52444FG85C; DISPLAY_NAME = "EFR Connect"; @@ -4147,7 +4285,7 @@ INFOPLIST_FILE = "$(SRCROOT)/SiliconLabsApp/SupportingFiles/BlueGecko/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 2.1.0; + MARKETING_VERSION = 2.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.silabs.BlueGeckoDemoApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Blue Gecko Development"; @@ -4171,7 +4309,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_RESOURCE_RULES_PATH = ""; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 29; + CURRENT_PROJECT_VERSION = 31; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 52444FG85C; DISPLAY_NAME = "EFR Connect"; @@ -4180,7 +4318,7 @@ INFOPLIST_FILE = "$(SRCROOT)/SiliconLabsApp/SupportingFiles/BlueGecko/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 2.1.0; + MARKETING_VERSION = 2.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.silabs.BlueGeckoDemoApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Blue Gecko Development"; diff --git a/SiliconLabsApp/Base.lproj/RangeTest/RangeTestStoryboard.storyboard b/SiliconLabsApp/Base.lproj/RangeTest/RangeTestStoryboard.storyboard deleted file mode 100644 index 2d909465..00000000 --- a/SiliconLabsApp/Base.lproj/RangeTest/RangeTestStoryboard.storyboard +++ /dev/null @@ -1,848 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SiliconLabsApp/Base.lproj/RangeTest/SILRangeTestModeSelectionViewController.xib b/SiliconLabsApp/Base.lproj/RangeTest/SILRangeTestModeSelectionViewController.xib deleted file mode 100644 index fd03dfc7..00000000 --- a/SiliconLabsApp/Base.lproj/RangeTest/SILRangeTestModeSelectionViewController.xib +++ /dev/null @@ -1,372 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SiliconLabsApp/BluetoothControllers/SILAdvertiserService.swift b/SiliconLabsApp/BluetoothControllers/SILAdvertiserService.swift index ff89135b..a3913a5e 100644 --- a/SiliconLabsApp/BluetoothControllers/SILAdvertiserService.swift +++ b/SiliconLabsApp/BluetoothControllers/SILAdvertiserService.swift @@ -17,6 +17,7 @@ class SILAdvertiserService: NSObject, CBPeripheralManagerDelegate { private let settings: SILAdvertiserSettings let runningAdvertisers: SILObservable<[SILAdvertisingSetEntity]> = SILObservable(initialValue: []) + let blutoothEnabled: SILObservable = SILObservable(initialValue: true) private var runningAdvertisersMap: [String: SILRunningAdvertiser] = [:] init(settings: SILAdvertiserSettings) { @@ -72,6 +73,11 @@ class SILAdvertiserService: NSObject, CBPeripheralManagerDelegate { } if peripheral.state == CBManagerState.poweredOn { + if blutoothEnabled.value == false { + blutoothEnabled.value = true + print("Bluetooth enabled") + } + let advertiser = runningAdvertiser.advertiser var advertisementData: [String: Any] = [:] @@ -104,6 +110,10 @@ class SILAdvertiserService: NSObject, CBPeripheralManagerDelegate { self.stop(advertiser: runningAdvertiser.advertiser) }) } + } else if peripheral.state == CBManagerState.poweredOff { + blutoothEnabled.value = false + print("Bluetooth disabled") + stopAllAdvertisers() } } diff --git a/SiliconLabsApp/BluetoothControllers/SILBluetoothDisabledAlert.swift b/SiliconLabsApp/BluetoothControllers/SILBluetoothDisabledAlert.swift new file mode 100644 index 00000000..7bf2e4a6 --- /dev/null +++ b/SiliconLabsApp/BluetoothControllers/SILBluetoothDisabledAlert.swift @@ -0,0 +1,56 @@ +// +// SILBluetoothDisabledAlert.swift +// BlueGecko +// +// Created by Kamil Czajka on 22.12.2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import Foundation + +@objc +enum SILBluetoothDisabledAlert: Int { + case browser + case advertiser + case healthThermometer + case rangeTest + case connectedLighting + + var title: String { + "Bluetooth Disabled" + } + + var message: String { + let backMsg = "You will back to the home screen." + let turnOnMsg = "Turn on Bluetooth to" + switch self { + case .browser: + return "\(backMsg) \(turnOnMsg) using Browser." + case .advertiser: + return "\(turnOnMsg) start any Advertiser." + case .healthThermometer: + return "\(backMsg) \(turnOnMsg) using Health Thermometer." + case .rangeTest: + return "\(backMsg) \(turnOnMsg) using Range Test." + case .connectedLighting: + return "\(backMsg) \(turnOnMsg) using Connected Lighting." + } + } +} + +@objc +class SILBluetoothDisabledAlertObjc: NSObject { + private let bluetoothDisabledAlert: SILBluetoothDisabledAlert + + @objc init(bluetoothDisabledAlert: SILBluetoothDisabledAlert) { + self.bluetoothDisabledAlert = bluetoothDisabledAlert + } + + @objc func getTitle() -> String { + return bluetoothDisabledAlert.title + } + + @objc func getMessage() -> String { + return bluetoothDisabledAlert.message + } +} diff --git a/SiliconLabsApp/BluetoothControllers/SILCentralManager.h b/SiliconLabsApp/BluetoothControllers/SILCentralManager.h index 40bce7f0..5e6fe3fa 100644 --- a/SiliconLabsApp/BluetoothControllers/SILCentralManager.h +++ b/SiliconLabsApp/BluetoothControllers/SILCentralManager.h @@ -15,6 +15,7 @@ extern NSString * _Nonnull const SILCentralManagerDidConnectPeripheralNotification; extern NSString * _Nonnull const SILCentralManagerDidDisconnectPeripheralNotification; extern NSString * _Nonnull const SILCentralManagerDidFailToConnectPeripheralNotification; +extern NSString * _Nonnull const SILCentralManagerBluetoothDisabledNotification; extern NSString * _Nonnull const SILCentralManagerDiscoveredPeripheralsKey; extern NSString * _Nonnull const SILCentralManagerPeripheralKey; diff --git a/SiliconLabsApp/BluetoothControllers/SILCentralManager.m b/SiliconLabsApp/BluetoothControllers/SILCentralManager.m index ddd9c3ab..b9c8c6ca 100644 --- a/SiliconLabsApp/BluetoothControllers/SILCentralManager.m +++ b/SiliconLabsApp/BluetoothControllers/SILCentralManager.m @@ -25,6 +25,7 @@ NSString * const SILCentralManagerDidConnectPeripheralNotification = @"SILCentralManagerDidConnectPeripheralNotification"; NSString * const SILCentralManagerDidDisconnectPeripheralNotification = @"SILCentralManagerDidDisconnectPeripheralNotification"; NSString * const SILCentralManagerDidFailToConnectPeripheralNotification = @"SILCentralManagerDidFailToConnectPeripheralNotification"; +NSString * const SILCentralManagerBluetoothDisabledNotification = @"SILCentralManagerBluetoothWasDisabledNotification"; NSString * const SILCentralManagerDiscoveredPeripheralsKey = @"SILCentralManagerDiscoveredPeripheralsKey"; NSString * const SILCentralManagerPeripheralKey = @"SILCentralManagerPeripheralKey"; @@ -313,9 +314,25 @@ - (void)stopScanning { #pragma mark - CBCentralManagerDelegate - (void)centralManagerDidUpdateState:(CBCentralManager *)central { + [self checkBluetoothState:central.state]; [self toggleScanning]; } +- (void)checkBluetoothState:(CBManagerState)state { + if (state == CBManagerStatePoweredOff) { + [self postBluetoothWasDisabledNotification]; + NSLog(@"BLUETOOTH DISABLED!"); + } else if (state == CBManagerStatePoweredOn) { + NSLog(@"Blutooth enabled"); + } +} + +- (void)postBluetoothWasDisabledNotification { + [[NSNotificationCenter defaultCenter] postNotificationName:SILCentralManagerBluetoothDisabledNotification + object:self + userInfo:nil]; +} + - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { if (![self isProbablyIBeacon:advertisementData]) { if (peripheral.identifier) { @@ -462,10 +479,11 @@ - (void)postDeleteDisconnectedPeripheral:(CBPeripheral*)peripheral andError:(NSE } - (void)postFailedToConnectPeripheral:(CBPeripheral*)peripheral andError:(NSError*)error { + NSString* peripheralName = peripheral.name ?: @"N/A"; [[NSNotificationCenter defaultCenter] postNotificationName:SILNotificationFailedToConnectPeripheral object:self userInfo:@{ - SILNotificationKeyPeripheralName: peripheral.name, + SILNotificationKeyPeripheralName: peripheralName, SILNotificationKeyError: [NSString stringWithFormat:@"%ld", (long)error.code] }]; } diff --git a/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.h b/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.h index 6e163aa3..4fd7bbd9 100644 --- a/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.h +++ b/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.h @@ -57,5 +57,6 @@ - (void)updateWithIBeacon:(CLBeacon*)iBeacon andDiscoveringTimestamp:(double)timestamp; - (NSString *)rssiDescription; - (void)resetLastTimestampValue; +- (NSNumber *)rssiValue; @end diff --git a/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.m b/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.m index 8b403af0..ac38edb2 100644 --- a/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.m +++ b/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.m @@ -51,7 +51,7 @@ - (instancetype)initWithPeripheral:(CBPeripheral *)peripheral - (void)updateWithAdvertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI andDiscoveringTimestamp:(double)timestamp { - self.advertisedLocalName = advertisementData[CBAdvertisementDataLocalNameKey]; + self.advertisedLocalName = advertisementData[CBAdvertisementDataLocalNameKey] ?: self.peripheral.name; self.advertisedServiceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey]; self.txPowerLevel = advertisementData[CBAdvertisementDataTxPowerLevelKey]; if (!self.isConnectable) { @@ -206,4 +206,8 @@ - (NSString *)rssiDescription { } } +- (NSNumber *)rssiValue { + return self.RSSIMeasurementTable.lastRSSIMeasurement; +} + @end diff --git a/SiliconLabsApp/Categories/NSObject+SILAssociatedObject.h b/SiliconLabsApp/Categories/NSObject+SILAssociatedObject.h new file mode 100644 index 00000000..de9f00c1 --- /dev/null +++ b/SiliconLabsApp/Categories/NSObject+SILAssociatedObject.h @@ -0,0 +1,20 @@ +// +// NSObject+SILAssociatedObject.h +// BlueGecko +// +// Created by Michal Lenart on 03/12/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSObject(SILAssociatedObject) + +- (nullable id)sil_associatedObject:(NSString*)key; +- (void)sil_setAssociatedObject:(nullable id)object forKey:(NSString*)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SiliconLabsApp/Categories/NSObject+SILAssociatedObject.m b/SiliconLabsApp/Categories/NSObject+SILAssociatedObject.m new file mode 100644 index 00000000..b58c1423 --- /dev/null +++ b/SiliconLabsApp/Categories/NSObject+SILAssociatedObject.m @@ -0,0 +1,31 @@ +// +// NSObject+SILAssociatedObject.m +// BlueGecko +// +// Created by Michal Lenart on 03/12/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +#import "NSObject+SILAssociatedObject.h" + +static void* const kSILAssociatedObjectsKey = (void*)&kSILAssociatedObjectsKey; + +@implementation NSObject(SILAssociatedObject) + +- (nullable id)sil_associatedObject:(NSString*)key { + NSDictionary* objects = objc_getAssociatedObject(self, kSILAssociatedObjectsKey); + return [objects valueForKey:key]; +} + +- (void)sil_setAssociatedObject:(nullable id)object forKey:(NSString*)key { + NSMutableDictionary* objects = objc_getAssociatedObject(self, kSILAssociatedObjectsKey); + + if (objects == nil) { + objects = [NSMutableDictionary dictionary]; + objc_setAssociatedObject(self, kSILAssociatedObjectsKey, objects, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + + [objects setValue:object forKey:key]; +} + +@end diff --git a/SiliconLabsApp/Categories/SILStoryboard+Constants.h b/SiliconLabsApp/Categories/SILStoryboard+Constants.h index ada179a3..1b570211 100644 --- a/SiliconLabsApp/Categories/SILStoryboard+Constants.h +++ b/SiliconLabsApp/Categories/SILStoryboard+Constants.h @@ -15,6 +15,7 @@ extern NSString * const SILSceneConnectedDevice; extern NSString * const SILSceneFilter; extern NSString * const SILSceneConnections; extern NSString * const SILSceneLog; +extern NSString * const SILSceneSort; extern NSString * const SILClassBrowserDeviceViewCell; extern NSString * const SILClassBrowserServiceViewCell; diff --git a/SiliconLabsApp/Categories/SILStoryboard+Constants.m b/SiliconLabsApp/Categories/SILStoryboard+Constants.m index 741d9a76..20359c8f 100644 --- a/SiliconLabsApp/Categories/SILStoryboard+Constants.m +++ b/SiliconLabsApp/Categories/SILStoryboard+Constants.m @@ -15,6 +15,7 @@ NSString * const SILSceneFilter = @"Filter"; NSString * const SILSceneConnections = @"Connections"; NSString * const SILSceneLog = @"Log"; +NSString * const SILSceneSort = @"Sort"; NSString * const SILClassBrowserDeviceViewCell = @"SILBrowserDeviceViewCell"; NSString * const SILClassBrowserServiceViewCell = @"SILBrowserServiceViewCell"; diff --git a/SiliconLabsApp/Categories/UIImage+SILImages.h b/SiliconLabsApp/Categories/UIImage+SILImages.h index 3c799e56..c700df90 100644 --- a/SiliconLabsApp/Categories/UIImage+SILImages.h +++ b/SiliconLabsApp/Categories/UIImage+SILImages.h @@ -92,3 +92,14 @@ extern NSString * const SILImageExitView; extern NSString * const SILImageChevronCollapsed; extern NSString * const SILImageChevronExpanded; +extern NSString * const SILImageSortOn; +extern NSString * const SILImageSortOff; +extern NSString * const SILImageSortAscendingOff; +extern NSString * const SILImageSortAscendingOn; +extern NSString * const SILImageSortAZOff; +extern NSString * const SILImageSortAZOn; +extern NSString * const SILImageSortZAOff; +extern NSString * const SILImageSortZAOn; +extern NSString * const SILImageSortDescendingOff; +extern NSString * const SILImageSortDescendingOn; + diff --git a/SiliconLabsApp/Categories/UIImage+SILImages.m b/SiliconLabsApp/Categories/UIImage+SILImages.m index f9a9426d..5d8d7193 100644 --- a/SiliconLabsApp/Categories/UIImage+SILImages.m +++ b/SiliconLabsApp/Categories/UIImage+SILImages.m @@ -23,7 +23,7 @@ NSString * const SILImageNameHomeDebug = @"browser"; NSString * const SILImageNameHomeKitDebug = @"HomeKitDebug"; NSString * const SILImageNameHomeConnectedLighting = @"light"; -NSString * const SILImageNameHomeRangeTestDemo = @"HomeRangeTestDemo"; +NSString * const SILImageNameHomeRangeTestDemo = @"icon - demo - range_test"; NSString * const SILImageNameHomeAdvertiser = @"icon - advertiser"; NSString * const SILImageNameKeyboard = @"Keyboard"; @@ -91,3 +91,14 @@ NSString * const SILImageExitView = @"exitView"; NSString * const SILImageChevronCollapsed = @"chevron_collapsed"; NSString * const SILImageChevronExpanded = @"chevron_expanded"; +NSString * const SILImageSortOn = @"icon - sort - on"; +NSString * const SILImageSortOff = @"icon - sort - off"; +NSString * const SILImageSortAscendingOff = @"icon - sort - ascending - off"; +NSString * const SILImageSortAscendingOn = @"icon - sort - ascending - on"; +NSString * const SILImageSortAZOff = @"icon - sort - AZ - off"; +NSString * const SILImageSortAZOn = @"icon - sort - AZ - on"; +NSString * const SILImageSortZAOff = @"icon - sort - ZA - off"; +NSString * const SILImageSortZAOn = @"icon - sort - ZA - on"; +NSString * const SILImageSortDescendingOff = @"icon - descending - off"; +NSString * const SILImageSortDescendingOn = @"icon - descending - on"; + diff --git a/SiliconLabsApp/Categories/UIViewController+Alert.swift b/SiliconLabsApp/Categories/UIViewController+Alert.swift new file mode 100644 index 00000000..707fb427 --- /dev/null +++ b/SiliconLabsApp/Categories/UIViewController+Alert.swift @@ -0,0 +1,22 @@ +// +// UIViewController+Alert.swift +// BlueGecko +// +// Created by Kamil Czajka on 22.12.2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import Foundation + +@objc +extension UIViewController { + @objc func alertWithOKButton(title: String, message: String, completion: ((UIAlertAction) -> Void)? = nil) { + let alert = UIAlertController(title: title, + message: message, + preferredStyle: .alert) + + let okButton = UIAlertAction(title: "OK", style: .default, handler: completion) + alert.addAction(okButton) + self.present(alert, animated: true, completion: nil) + } +} diff --git a/SiliconLabsApp/Categories/UIViewController+SILContext.swift b/SiliconLabsApp/Categories/UIViewController+SILContext.swift new file mode 100644 index 00000000..c22e5c88 --- /dev/null +++ b/SiliconLabsApp/Categories/UIViewController+SILContext.swift @@ -0,0 +1,28 @@ +// +// UIViewController+Context.swift +// BlueGecko +// +// Created by Michal Lenart on 03/12/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import UIKit + +extension UIViewController { + /// Provide any context down to the UIViewController tree. Context can be retrieved with sil_useContext method in any UIViewController descendant. + func sil_provideContext(type: T.Type, value: T?) { + let key = String(describing: T.self) + self.sil_setAssociatedObject(value, forKey: key) + } + + /// Get context with given type that was provider in any UIViewController ancestor. + func sil_useContext(type: T.Type) -> T? { + let key = String(describing: T.self) + + if let value = self.sil_associatedObject(key) as? T { + return value + } else { + return parent?.sil_useContext(type: type) + } + } +} diff --git a/SiliconLabsApp/Models/AttributeTableModels/SILCharacteristicTableModel.m b/SiliconLabsApp/Models/AttributeTableModels/SILCharacteristicTableModel.m index e75a0f7d..213e60fb 100644 --- a/SiliconLabsApp/Models/AttributeTableModels/SILCharacteristicTableModel.m +++ b/SiliconLabsApp/Models/AttributeTableModels/SILCharacteristicTableModel.m @@ -176,8 +176,8 @@ - (BOOL)writeIfAllowedToPeripheral:(CBPeripheral *)peripheral withWriteType:(CBC writeType = [self checkIfCharacteristicSupportsChosenWriteType:writeType]; [peripheral writeValue:dataToWrite forCharacteristic:self.characteristic type:writeType]; - [self postRegisterLogNotification:[SILLogDataModel prepareLogDescription:@"writeToPeripheral: " andPeripheral:peripheral andError:*error]]; - + [self postRegisterLogNotification: [SILLogDataModel prepareLogDescriptionForWriteValueOfCharacteristic:self.characteristic andPeripheral:peripheral andError:*error andData:dataToWrite]]; + return YES; } @@ -231,7 +231,7 @@ - (BOOL)isFieldMetRequirement:(NSObject *)fieldModel } - (void)postRegisterLogNotification:(NSString*)description { - [[NSNotificationCenter defaultCenter] postNotificationName:@"RegisterLog" object:self userInfo:@{ @"description" : description}]; + [[NSNotificationCenter defaultCenter] postNotificationName:SILNotificationRegisterLog object:self userInfo:@{ @"description" : description}]; } - (BOOL)clearModel { diff --git a/SiliconLabsApp/Models/AttributeTableModels/SILDescriptorTableModel.h b/SiliconLabsApp/Models/AttributeTableModels/SILDescriptorTableModel.h index e2e30d11..4767ad51 100644 --- a/SiliconLabsApp/Models/AttributeTableModels/SILDescriptorTableModel.h +++ b/SiliconLabsApp/Models/AttributeTableModels/SILDescriptorTableModel.h @@ -14,7 +14,12 @@ @interface SILDescriptorTableModel : NSObject @property (strong, nonatomic) CBDescriptor *descriptor; +@property (nonatomic) NSInteger valueLinesNumber; +@property (nonatomic) BOOL shouldReadValue; - (instancetype)initWithDescriptor:(CBDescriptor *)descriptor; +- (NSString*)getDescriptorName; +- (NSString*)getFormattedValue; +- (NSAttributedString*)getAttributedDescriptor; @end diff --git a/SiliconLabsApp/Models/AttributeTableModels/SILDescriptorTableModel.m b/SiliconLabsApp/Models/AttributeTableModels/SILDescriptorTableModel.m index 31ca20c3..8a582168 100644 --- a/SiliconLabsApp/Models/AttributeTableModels/SILDescriptorTableModel.m +++ b/SiliconLabsApp/Models/AttributeTableModels/SILDescriptorTableModel.m @@ -10,6 +10,12 @@ #import "SILDescriptorTableModel.h" #import "SILBluetoothBrowser+Constants.h" +@interface SILDescriptorTableModel () + +@property (strong, nonatomic) SILBrowserDescriptorValueParser* parser; + +@end + @implementation SILDescriptorTableModel @synthesize isExpanded; @@ -20,6 +26,9 @@ - (instancetype)initWithDescriptor:(CBDescriptor *)descriptor { if (self) { self.descriptor = descriptor; self.isExpanded = NO; + self.shouldReadValue = NO; + self.valueLinesNumber = 0; + self.parser = [[SILBrowserDescriptorValueParser alloc] initWithDescriptor:descriptor]; } return self; } @@ -34,6 +43,13 @@ - (void)toggleExpansionIfAllowed { //can't expand } +- (void)setShouldReadValue:(BOOL)shouldReadValue { + _shouldReadValue = shouldReadValue; + if (shouldReadValue) { + self.valueLinesNumber = self.parser.valueLinesNumber; + } +} + - (NSString *)hexUuidString { return [self.descriptor getHexUuidValue]; } @@ -46,5 +62,31 @@ - (NSString *)name { return EmptyText; } +- (NSString*)getDescriptorName { + return [self.parser getDescriptorName]; +} + +- (NSString*)getFormattedValue { + return [self.parser getFormattedValue]; +} + +- (NSAttributedString*)getAttributedDescriptor { + NSString* name = [self getDescriptorName]; + NSString* hexUUID = [self hexUuidString]; + NSMutableAttributedString* result = [NSMutableAttributedString.alloc initWithString: [NSString stringWithFormat:@"%@\n", name]]; + NSMutableAttributedString* uuidString = [NSMutableAttributedString.alloc initWithString:[NSString stringWithFormat:@"UUID: %@", hexUUID]]; + + [uuidString addAttribute:NSForegroundColorAttributeName value:[UIColor sil_masala50pcColor] range:NSMakeRange(0, 5)]; + [result appendAttributedString:uuidString]; + + if (self.shouldReadValue) { + NSString* formattedValue = [self getFormattedValue]; + NSMutableAttributedString* valueString = [NSMutableAttributedString.alloc initWithString:[NSString stringWithFormat:@"\nValue: %@", formattedValue]]; + [valueString addAttribute:NSForegroundColorAttributeName value:[UIColor sil_masala50pcColor] range:NSMakeRange(1, 6)]; + [result appendAttributedString:valueString]; + } + + return result; +} @end diff --git a/SiliconLabsApp/Models/BluetoothBrowser/SILFilterBarViewModel.swift b/SiliconLabsApp/Models/BluetoothBrowser/SILFilterBarViewModel.swift new file mode 100644 index 00000000..e64a403c --- /dev/null +++ b/SiliconLabsApp/Models/BluetoothBrowser/SILFilterBarViewModel.swift @@ -0,0 +1,57 @@ +// +// SILFilterBarViewModel.swift +// BlueGecko +// +// Created by Kamil Czajka on 14.12.2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import Foundation + +class SILFilterBarViewModel { + private var currentFilter: SILBrowserFilterViewModel? + var state: SILObservable = SILObservable(initialValue: false) + var filterParamters: SILObservable = SILObservable(initialValue: "") + + func handleTap() { + self.state.value = !self.state.value + } + + func updateCurrentFilter(filter: SILBrowserFilterViewModel?) { + self.currentFilter = filter + self.state.value = false + self.filterParamters.value = prepareParametersDescription() + } + + private func prepareParametersDescription() -> String { + var description: [String] = [] + if let currentFilter = currentFilter { + if !currentFilter.isFilterActive() { + return "" + } + + if currentFilter.searchByDeviceName != "" { + description.append(currentFilter.searchByDeviceName) + } + + description.append(" > \(currentFilter.dBmValue)dBm") + + let beaconTypes = currentFilter.beaconTypes as! [SILBrowserBeaconType] + for beacon in beaconTypes { + if beacon.isSelected { + description.append("\(String(describing: beacon.beaconName!))") + } + } + + if currentFilter.isFavouriteFilterSet { + description.append("favourites") + } + + if currentFilter.isConnectableFilterSet { + description.append("connectable") + } + } + + return description.joined(separator: ", ") + } +} diff --git a/SiliconLabsApp/Models/BluetoothBrowser/Sort/SILSortOption.h b/SiliconLabsApp/Models/BluetoothBrowser/Sort/SILSortOption.h new file mode 100644 index 00000000..46bdcfad --- /dev/null +++ b/SiliconLabsApp/Models/BluetoothBrowser/Sort/SILSortOption.h @@ -0,0 +1,20 @@ +// +// SILSortOption.h +// SiliconLabsApp +// +// Created by Grzegorz Janosz on 16/11/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +#ifndef SILSortOption_h +#define SILSortOption_h + +typedef NS_ENUM(NSUInteger, SILSortOption) { + SILSortOptionAscendingRSSI, + SILSortOptionDescendingRSSI, + SILSortOptionAZ, + SILSortOptionZA, + SILSortOptionNone, +}; + +#endif /* SILSortOption_h */ diff --git a/SiliconLabsApp/Models/BluetoothBrowser/Sort/SILSortViewModel.swift b/SiliconLabsApp/Models/BluetoothBrowser/Sort/SILSortViewModel.swift new file mode 100644 index 00000000..3f36bded --- /dev/null +++ b/SiliconLabsApp/Models/BluetoothBrowser/Sort/SILSortViewModel.swift @@ -0,0 +1,84 @@ +// +// SILSortViewModel.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 12/11/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import Foundation + +struct SortMode { + var modeName: String + var option: SILSortOption +} + +struct SortSection { + var type: String + var modes: [SortMode] + + init(type: String, modes: [SortMode]) { + self.type = type + self.modes = modes + } +} + +@objc +@objcMembers +class SILSortViewModel: NSObject { + var sections: [SortSection] = [ + SortSection(type: "RSSI", modes: [SortMode(modeName: "Ascending", option: .ascendingRSSI), SortMode(modeName: "Descending", option: .descendingRSSI)]), + SortSection(type: "Name", modes: [SortMode(modeName: "A > Z", option: .AZ), SortMode(modeName: "Z > A", option: .ZA)]) + ] + + var selectedOption: SILSortOption = .none + let typeCellHeight: CGFloat = 44.0 + let modeCellHeight: CGFloat = 37.0 + + static let _sharedInstance = SILSortViewModel() + + private override init(){} + + @objc + class func sharedInstance() -> SILSortViewModel { + return SILSortViewModel._sharedInstance + } + + func cellHeight(forIndexPath indexPath: IndexPath) -> CGFloat { + if indexPath.row == 0 { + return typeCellHeight + } else { + return modeCellHeight + } + } + + @objc func deselectSelectedOption() { + selectedOption = .none + } + + func selectOption(forIndexPath indexPath: IndexPath) { + if indexPath.row == 0 { return } + let sortOption = sections[indexPath.section].modes[indexPath.row - 1].option + if sortOption == selectedOption { + selectedOption = .none + } else { + selectedOption = sortOption + } + } + + func isSelected(forIndexPath indexPath: IndexPath) -> Bool { + if indexPath.row == 0 { return false } + return selectedOption == sections[indexPath.section].modes[indexPath.row - 1].option + } + + @objc + func getViewControllerHeight() -> CGFloat { + var result: CGFloat = 0.0 + for section in sections { + result += CGFloat(section.modes.count) * modeCellHeight + result += typeCellHeight + } + let paddingBottom: CGFloat = 15.0 + return result + paddingBottom + } +} diff --git a/SiliconLabsApp/Models/Browser/SILBrowserDescriptorValueParser.swift b/SiliconLabsApp/Models/Browser/SILBrowserDescriptorValueParser.swift new file mode 100644 index 00000000..2c5f5881 --- /dev/null +++ b/SiliconLabsApp/Models/Browser/SILBrowserDescriptorValueParser.swift @@ -0,0 +1,252 @@ +// +// SILBrowserDescriptorValueParser.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 07/12/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +@objc +@objcMembers +class SILBrowserDescriptorValueParser: NSObject { + + let descriptor: CBDescriptor! + + init(withDescriptor descriptor: CBDescriptor!) { + self.descriptor = descriptor + } + + var valueLinesNumber: Int { + get { + let uuidType = SILDesciptorTypeUUID(rawValue: descriptor.uuid) + if uuidType == .reportReference || uuidType == .validRange { + return 2 + } + return 1 + } + } + + @objc + func getFormattedValue() -> String { + let uuid = descriptor.uuid + let descriptorBytes = getBytesArray(fromDescriptorValue: descriptor.value) + if descriptorBytes.count > 0 { + switch (SILDesciptorTypeUUID(rawValue: uuid)) { + case .environmentalSensingConfiguration: + return getEnvironmentalSensingConfiguration(bytes: descriptorBytes) + case .characteristicExtendedProperties: + return getCharacteristicExtendedProperties(bytes: descriptorBytes) + case .characteristicUserDescription: + return getCharacteristicUserDescription(bytes: descriptorBytes) + case .clientCharacteristicConfiguration: + return getClientCharacteristicConfiguration(bytes: descriptorBytes) + case .serverCharacteristicConfiguration: + return getServerCharacteristicConfiguration(bytes: descriptorBytes) + case .numberOfDigitals: + return getNumberOfDigitals(bytes: descriptorBytes) + case .reportReference: + return getReportReference(bytes: descriptorBytes) + case .validRange: + return getValidRange(bytes: descriptorBytes) + default: + return "0x\(bytesToHexString(descriptorBytes))" + } + } else { + return "" + } + } + + private func getBytesArray(fromDescriptorValue descriptorValue: Any?) -> [UInt8] { + if let intValue = descriptorValue as? Int { + return byteArray(from: intValue) + } else if let nsNumberValue = descriptorValue as? NSNumber { + return byteArray(from: nsNumberValue.intValue) + } else if let dataValue = descriptorValue as? Data { + return dataValue.bytes + } else if let stringValue = descriptorValue as? String { + return stringValue.bytes + } else { + return [] + } + } + + func byteArray(from value: T) -> [UInt8] where T: FixedWidthInteger { + withUnsafeBytes(of: value.bigEndian, Array.init) + } + + private func getEnvironmentalSensingConfiguration(bytes: [UInt8]) -> String { + let firstByte = uint8ToInt(bytes[0]) + + switch (firstByte) { + case 0: return "Boolean AND" + case 1: return "Boolean OR" + default: return "Unknown value: 0x\(bytesToHexString(bytes))" + } + } + + private func getCharacteristicExtendedProperties(bytes: [UInt8]) -> String { + let firstByte = bytes[0] + var result = "" + + if firstByte & 0b0000_0001 == 1 { + result.append("Reliable Write enabled, ") + } else { + result.append("Reliable Write disabled, ") + } + + if firstByte & 0b0000_0010 == 2 { + result.append("Writable Auxiliaries enabled, ") + } else { + result.append("Writable Auxiliaries disabled, ") + } + + if result.hasSuffix(", ") { + result.removeLast(2) + } + + return result + } + + private func getCharacteristicUserDescription(bytes: [UInt8]) -> String { + return String(bytes: bytes, encoding: .utf8)! + } + + private func getClientCharacteristicConfiguration(bytes: [UInt8]) -> String { + let firstByte = bytes[0] + var result = "" + + if firstByte & 0b0000_0001 == 1 { + result.append("Notifications enabled, ") + } else { + result.append("Notifications disabled, ") + } + + if firstByte & 0b0000_0010 == 2 { + result.append("Indications enabled, ") + } else { + result.append("Indications disabled, ") + } + + if result.hasSuffix(", ") { + result.removeLast(2) + } + + return result + } + + private func getServerCharacteristicConfiguration(bytes: [UInt8]) -> String { + let firstByte = bytes[0] + + return firstByte & 0b0000_0001 == 1 ? "Broadcasts enabled" : "Broadcasts disabled" + } + + private func getNumberOfDigitals(bytes: [UInt8]) -> String { + let intValue = uint8ToInt(bytes[0]) + return String(intValue) + } + + private func getReportReference(bytes: [UInt8]) -> String { + if bytes.count != 2 { + return "Unknown value: 0x\(bytesToHexString(bytes))" + } else { + let reportId = bytes[0] + let reportType = bytes[1] + + var result = "" + result.append("Report ID: 0x\(byteToHexString(reportId))\n") + result.append("Report Type: 0x\(byteToHexString(reportType))") + + return result + } + } + + private func getValidRange(bytes: [UInt8]) -> String { + let size = bytes.count + + if size % 2 != 0 { + return "Unknown value: 0x\(bytesToHexString(bytes))" + } else { + var result = "" + + result.append("Lower inclusive value: 0x") + let halfSize = size / 2 + for i in 0.. String { + let uuid = descriptor.uuid + switch (SILDesciptorTypeUUID(rawValue: uuid)) { + case .environmentalSensingConfiguration: return "Environmental Sensing Configuration" + case .environmentalSensingMeasurement: return "Environmental Sensing Measurement" + case .environmentalSensingTriggerSetting: return "Environmental Sensing Trigger Setting" + case .externalReportReference: return "External Report Reference" + case .characteristicAggregateFormat: return "Characteristic Aggregate Format" + case .characteristicExtendedProperties: return "Characteristic Extended Properties" + case .characteristicPresentationFormat: return "Characteristic Presentation Format" + case .characteristicUserDescription: return "Characteristic User Description" + case .clientCharacteristicConfiguration: return "Client Characteristic Configuration" + case .serverCharacteristicConfiguration: return "Server Characteristic Configuration" + case .numberOfDigitals: return "Number of Digitals" + case .reportReference: return "Report Reference" + case .timeTriggerSetting: return "Time Trigger Setting" + case .validRange: return "Valid Range" + case .valueTriggerSetting: return "Value Trigger Setting" + default: return "\(uuid)" + } + } + + // MARK: Value parsers + + func bytesToHexString(_ bytes: [UInt8], spacing: String = "") -> String { + var hexString: String = "" + var count = bytes.count + for byte in bytes + { + hexString.append(byteToHexString(byte)) + count = count - 1 + if count > 0 + { + hexString.append(spacing) + } + } + return hexString + } + + func byteToHexString(_ byte: UInt8) -> String{ + return String(format: "%02X", byte) + } + + func uint8ToInt(_ source: UInt8) -> Int { + var bytes = [UInt8](repeating: 0, count: 4) + bytes[3] = source + let bigEndianUInt32 = bytes.withUnsafeBytes { $0.load(as: UInt32.self) } + let value = CFByteOrderGetCurrent() == CFByteOrder(CFByteOrderLittleEndian.rawValue) + ? UInt32(bigEndian: bigEndianUInt32) + : bigEndianUInt32 + return Int(value) + } +} + +extension StringProtocol { + var data: Data { .init(utf8) } + var bytes: [UInt8] { .init(utf8) } +} + +extension Data { + var bytes: [UInt8] { + var byteArray = [UInt8](repeating: 0, count: self.count) + self.copyBytes(to: &byteArray, count: self.count) + return byteArray + } +} diff --git a/SiliconLabsApp/Models/Browser/SILDescryptorTypeUUID.swift b/SiliconLabsApp/Models/Browser/SILDescryptorTypeUUID.swift new file mode 100644 index 00000000..5b8839c6 --- /dev/null +++ b/SiliconLabsApp/Models/Browser/SILDescryptorTypeUUID.swift @@ -0,0 +1,71 @@ +// +// SILDescryptorTypeUUID.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 07/12/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +enum SILDesciptorTypeUUID { + case environmentalSensingConfiguration + case environmentalSensingMeasurement + case environmentalSensingTriggerSetting + case externalReportReference + case characteristicAggregateFormat + case characteristicExtendedProperties + case characteristicPresentationFormat + case characteristicUserDescription + case clientCharacteristicConfiguration + case serverCharacteristicConfiguration + case numberOfDigitals + case reportReference + case timeTriggerSetting + case validRange + case valueTriggerSetting +} + +extension SILDesciptorTypeUUID: RawRepresentable { + typealias RawValue = CBUUID + + init?(rawValue: RawValue) { + switch rawValue { + case CBUUID(string: "0x290B"): self = .environmentalSensingConfiguration + case CBUUID(string: "0x290C"): self = .environmentalSensingMeasurement + case CBUUID(string: "0x290D"): self = .environmentalSensingTriggerSetting + case CBUUID(string: "0x2907"): self = .externalReportReference + case CBUUID(string: "0x2905"): self = .characteristicAggregateFormat + case CBUUID(string: "0x2900"): self = .characteristicExtendedProperties + case CBUUID(string: "0x2904"): self = .characteristicPresentationFormat + case CBUUID(string: "0x2901"): self = .characteristicUserDescription + case CBUUID(string: "0x2902"): self = .clientCharacteristicConfiguration + case CBUUID(string: "0x2903"): self = .serverCharacteristicConfiguration + case CBUUID(string: "0x2909"): self = .numberOfDigitals + case CBUUID(string: "0x2908"): self = .reportReference + case CBUUID(string: "0x290E"): self = .timeTriggerSetting + case CBUUID(string: "0x2906"): self = .validRange + case CBUUID(string: "0x290A"): self = .valueTriggerSetting + default: + return nil + } + } + + var rawValue: RawValue { + switch self { + case .environmentalSensingConfiguration: return CBUUID(string: "0x290B") + case .environmentalSensingMeasurement: return CBUUID(string: "0x290C") + case .environmentalSensingTriggerSetting: return CBUUID(string: "0x290D") + case .externalReportReference: return CBUUID(string: "0x2907") + case .characteristicAggregateFormat: return CBUUID(string: "0x2905") + case .characteristicExtendedProperties: return CBUUID(string: "0x2900") + case .characteristicPresentationFormat: return CBUUID(string: "0x2904") + case .characteristicUserDescription: return CBUUID(string: "0x2901") + case .clientCharacteristicConfiguration: return CBUUID(string: "0x2902") + case .serverCharacteristicConfiguration: return CBUUID(string: "0x2903") + case .numberOfDigitals: return CBUUID(string: "0x2909") + case .reportReference: return CBUUID(string: "0x2908") + case .timeTriggerSetting: return CBUUID(string: "0x290E") + case .validRange: return CBUUID(string: "0x2906") + case .valueTriggerSetting: return CBUUID(string: "0x290A") + } + } +} diff --git a/SiliconLabsApp/Models/Browser/SILFavoritePeripheral.swift b/SiliconLabsApp/Models/Browser/SILFavoritePeripheral.swift index 753141c6..c501a5fc 100644 --- a/SiliconLabsApp/Models/Browser/SILFavoritePeripheral.swift +++ b/SiliconLabsApp/Models/Browser/SILFavoritePeripheral.swift @@ -29,6 +29,11 @@ public class SILFavoritePeripheral: Object { return fav } + @objc static func areFavoritePeripherals() -> Bool { + let realm = try! Realm() + return realm.objects(SILFavoritePeripheral.self).count > 0 + } + @objc static func add(_ peripheral: SILDiscoveredPeripheralDisplayDataViewModel) { let uuid: String = peripheral.discoveredPeripheralDisplayData.discoveredPeripheral.identityKey let name: String = peripheral.discoveredPeripheralDisplayData.discoveredPeripheral.peripheral?.name ?? "" diff --git a/SiliconLabsApp/Models/SILAdTypeEddystoneDecoder.swift b/SiliconLabsApp/Models/SILAdTypeEddystoneDecoder.swift index 3792c1e9..08f496a5 100644 --- a/SiliconLabsApp/Models/SILAdTypeEddystoneDecoder.swift +++ b/SiliconLabsApp/Models/SILAdTypeEddystoneDecoder.swift @@ -16,13 +16,22 @@ class SILAdTypeEddystoneDecoder { private let EddystoneTLMLength = (encrypted: 18, unencrypted: 14) private let EddystoneEIDLength = 10 - init(eddystoneData: Data) { - self.eddystoneData = [UInt8](eddystoneData) + init(eddystoneData: Data?) { + if let eddystoneData = eddystoneData { + self.eddystoneData = [UInt8](eddystoneData) + } else { + self.eddystoneData = [UInt8]() + } } func decode() -> SILAdvertisementDataModel { let eddystoneDataString: String + if self.eddystoneData.isEmpty { + eddystoneDataString = "PARSING ERROR: Unknown type of Eddystone: No data" + return SILAdvertisementDataModel(value: eddystoneDataString, type: .eddystoneBeacon) + } + switch self.eddystoneData[0] { case 0x00: eddystoneDataString = decodeEddystoneUID() diff --git a/SiliconLabsApp/Models/SILAdvertiserSettings.swift b/SiliconLabsApp/Models/SILAdvertiserSettings.swift index 6bd9549b..9e288fcf 100644 --- a/SiliconLabsApp/Models/SILAdvertiserSettings.swift +++ b/SiliconLabsApp/Models/SILAdvertiserSettings.swift @@ -11,6 +11,7 @@ import Foundation class SILAdvertiserSettings { private static let CompleteLocalNameSettingKey = "SILCompleteLocalNameSetting" private static let DisableRemoveServiceListWarningKey = "SILDisableRemoveServiceListWarningKey" + private static let NonSaveChangesExitWarningKey = "SILNonSaveChangesExitWarningKey" private let userDefaults = UserDefaults.standard @@ -23,4 +24,9 @@ class SILAdvertiserSettings { get { userDefaults.bool(forKey: Self.DisableRemoveServiceListWarningKey) } set { userDefaults.set(newValue, forKey: Self.DisableRemoveServiceListWarningKey) } } + + var nonSaveChangesExitWarning: Bool { + get { userDefaults.bool(forKey: Self.NonSaveChangesExitWarningKey) } + set { userDefaults.set(newValue, forKey: Self.NonSaveChangesExitWarningKey) } + } } diff --git a/SiliconLabsApp/Models/SILApp.h b/SiliconLabsApp/Models/SILApp.h index 68b6ae9e..c629d1ad 100644 --- a/SiliconLabsApp/Models/SILApp.h +++ b/SiliconLabsApp/Models/SILApp.h @@ -29,4 +29,6 @@ typedef NS_ENUM(NSInteger, SILAppType) { + (NSArray *)demoApps; + (NSArray *)developApps; ++ (SILApp *)rangeTestApp; + @end diff --git a/SiliconLabsApp/Models/SILApp.m b/SiliconLabsApp/Models/SILApp.m index 8fa5041e..00312d41 100644 --- a/SiliconLabsApp/Models/SILApp.m +++ b/SiliconLabsApp/Models/SILApp.m @@ -14,6 +14,8 @@ @implementation SILApp + (NSArray *)demoApps { return @[ [self healthThermometerApp], + [self connectedLightningApp], + [self rangeTestApp], ]; } @@ -27,7 +29,7 @@ + (NSArray *)developApps { + (SILApp *)connectedLightningApp { return [[SILApp alloc] initWithAppType:SILAppTypeConnectedLighting title:@"Connected Lighting" - description:@"Dynamic multiprotocol application for Wireless Gecko SoCs showcasing Bluetooth operating simultaneously with other wireless protocols" + description:@"Control a Dynamic Multiprotocol application of connected lights and switches." showcasedProfiles:@{} imageName:SILImageNameHomeConnectedLighting]; } @@ -67,7 +69,7 @@ + (SILApp *)homekitApp { + (SILApp *)rangeTestApp { return [[SILApp alloc] initWithAppType:SILAppTypeRangeTest title:@"Range Test" - description:@"Evaluate the link budget and communication range of the Wireless Gecko SoCs using various wireless radio configurations" + description:@"Evaluate the link budget and range of EFR32." showcasedProfiles:@{} imageName:SILImageNameHomeRangeTestDemo]; } @@ -75,7 +77,7 @@ + (SILApp *)rangeTestApp { + (SILApp *)advertiserApp { return [[SILApp alloc] initWithAppType:SILAppTypeAdvertiser title:@"Advertiser" - description:@"Utilize this device as a BLE peripheral" + description:@"Utilize this device as a Bluetooth Low Energy peripheral." showcasedProfiles:@{} imageName:SILImageNameHomeAdvertiser]; } diff --git a/SiliconLabsApp/Models/SILBeacon.m b/SiliconLabsApp/Models/SILBeacon.m index d85ae7a9..bf843cac 100644 --- a/SiliconLabsApp/Models/SILBeacon.m +++ b/SiliconLabsApp/Models/SILBeacon.m @@ -71,9 +71,15 @@ + (instancetype)beaconWithEddystone:(NSData *)eddystoneServiceData { beacon.name = @"Eddystone"; beacon.type = SILBeaconTypeEddystone; + beacon.eddystoneData = eddystoneServiceData; uint8_t *dataPointer = (uint8_t *)eddystoneServiceData.bytes; + if (dataPointer == nil) { + beacon.eddystoneBeaconType = SILEddystoneBeaconTypeUnspecified; + return beacon; + } + switch (dataPointer[0]) { case 0x00: beacon.eddystoneBeaconType = SILEddystoneBeaconTypeUID; @@ -97,9 +103,7 @@ + (instancetype)beaconWithEddystone:(NSData *)eddystoneServiceData { beacon.eddystoneBeaconType = SILEddystoneBeaconTypeUnspecified; break; } - - beacon.eddystoneData = eddystoneServiceData; - + return beacon; } diff --git a/SiliconLabsApp/Models/SILLogDataModel.h b/SiliconLabsApp/Models/SILLogDataModel.h index bec6435c..6d975deb 100644 --- a/SiliconLabsApp/Models/SILLogDataModel.h +++ b/SiliconLabsApp/Models/SILLogDataModel.h @@ -17,6 +17,8 @@ - (instancetype)initWithDesctiption:(NSString*)description; + (NSString*)prepareLogDescription:(NSString*)title andPeripheral:(CBPeripheral*)peripheral andError:(NSError*)error; + (NSString*)prepareLogDescription:(NSString *)title andCharacteristic:(CBCharacteristic*)characteristic andPeripheral:(CBPeripheral *)peripheral andError:(NSError *)error; ++ (NSString*)prepareLogDescriptionForWriteValueOfCharacteristic:(CBCharacteristic*)characteristic andPeripheral:(CBPeripheral *)peripheral andError:(NSError *)error andData:(NSData*)data; ++ (NSString*)prepareLogDescriptionForUpdateValueOfCharacteristic:(CBCharacteristic*)characteristic andPeripheral:(CBPeripheral *)peripheral andError:(NSError *)error; + (NSString*)prepareLogDescription:(NSString *)title andDescriptor:(CBDescriptor*)descriptor andPeripheral:(CBPeripheral *)peripheral andError:(NSError *)error; @end diff --git a/SiliconLabsApp/Models/SILLogDataModel.m b/SiliconLabsApp/Models/SILLogDataModel.m index 86e0e692..f3197770 100644 --- a/SiliconLabsApp/Models/SILLogDataModel.m +++ b/SiliconLabsApp/Models/SILLogDataModel.m @@ -8,6 +8,7 @@ #import #import "SILLogDataModel.h" +#import "SILCharacteristicFieldValueResolver.h" @interface SILLogDataModel () @@ -69,6 +70,30 @@ + (NSString*)prepareLogDescription:(NSString *)title andCharacteristic:(CBCharac return description; } ++ (NSString*)prepareLogDescriptionForUpdateValueOfCharacteristic:(CBCharacteristic*)characteristic andPeripheral:(CBPeripheral *)peripheral andError:(NSError *)error { + NSMutableString* description = [NSMutableString stringWithString:[self prepareLogDescription:@"didUpdateValueForCharacteristic: " andCharacteristic:characteristic andPeripheral:peripheral andError:error]]; + [description appendString:[self prepareStringOfValue:[characteristic value]]]; + return description; +} + ++ (NSString*)prepareLogDescriptionForWriteValueOfCharacteristic:(CBCharacteristic*)characteristic andPeripheral:(CBPeripheral *)peripheral andError:(NSError *)error andData:(NSData*)data { + NSMutableString* description = [[NSMutableString alloc] init]; + [description appendString:[self prepareLogDescription:@"writeToPeripheral: " andCharacteristic:characteristic andPeripheral:peripheral andError:error]]; + [description appendString:[self prepareStringOfValue: data]]; + return description; +} + ++ (NSString*)prepareStringOfValue:(NSData*)data { + if (data == nil || data.length == 0) { + return @"\nEmpty data"; + } + NSString* hexString = [[SILCharacteristicFieldValueResolver sharedResolver] hexStringForData:data decimalExponent:0]; + NSString* asciiString = [[[SILCharacteristicFieldValueResolver sharedResolver] asciiStringForData:data] stringByReplacingOccurrencesOfString:@"\0" withString:@""]; + NSString* decimalString = [[SILCharacteristicFieldValueResolver sharedResolver] decimalStringForData:data]; + NSString* res = [NSString stringWithFormat:@" data: 0x%@ (hex), %@ (ascii), %@ (dec).", hexString, asciiString, decimalString]; + return res; +} + + (NSString*)prepareLogDescription:(NSString *)title andDescriptor:(CBDescriptor*)descriptor andPeripheral:(CBPeripheral *)peripheral andError:(NSError *)error { NSMutableString* description = [NSMutableString stringWithString:title]; if (peripheral.name != nil) { diff --git a/SiliconLabsApp/Storyboards/DemoApps/SILAppTypeConnectedLighting.storyboard b/SiliconLabsApp/Storyboards/DemoApps/SILAppTypeConnectedLighting.storyboard index 8e76cec8..e2dfc11b 100644 --- a/SiliconLabsApp/Storyboards/DemoApps/SILAppTypeConnectedLighting.storyboard +++ b/SiliconLabsApp/Storyboards/DemoApps/SILAppTypeConnectedLighting.storyboard @@ -1,27 +1,327 @@ - + - + + + + + + Roboto-Medium + + + Roboto-Regular + + - + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SiliconLabsApp/Storyboards/DemoApps/SILAppTypeHealthThermometer.storyboard b/SiliconLabsApp/Storyboards/DemoApps/SILAppTypeHealthThermometer.storyboard index 5ef56049..dcbaca84 100644 --- a/SiliconLabsApp/Storyboards/DemoApps/SILAppTypeHealthThermometer.storyboard +++ b/SiliconLabsApp/Storyboards/DemoApps/SILAppTypeHealthThermometer.storyboard @@ -1,9 +1,9 @@ - - + + - + @@ -14,23 +14,23 @@ - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SiliconLabsApp/Storyboards/DevelopApps/SILAppAdvertiser.storyboard b/SiliconLabsApp/Storyboards/DevelopApps/SILAppAdvertiser.storyboard index 12105932..242db2ea 100644 --- a/SiliconLabsApp/Storyboards/DevelopApps/SILAppAdvertiser.storyboard +++ b/SiliconLabsApp/Storyboards/DevelopApps/SILAppAdvertiser.storyboard @@ -1,9 +1,9 @@ - + - + @@ -121,7 +121,7 @@ - + @@ -143,7 +144,7 @@ - + @@ -254,6 +255,7 @@ + @@ -271,7 +273,6 @@ - @@ -295,7 +296,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SiliconLabsApp/ViewControllers/Advertiser/Home/SILAdvertiserHomeWireframe.swift b/SiliconLabsApp/ViewControllers/Advertiser/Home/SILAdvertiserHomeWireframe.swift index 85eb4a10..b69cc485 100644 --- a/SiliconLabsApp/ViewControllers/Advertiser/Home/SILAdvertiserHomeWireframe.swift +++ b/SiliconLabsApp/ViewControllers/Advertiser/Home/SILAdvertiserHomeWireframe.swift @@ -49,6 +49,12 @@ final class SILAdvertiserHomeWireframe: SILBaseWireframe, WYPopoverControllerDel animated: true) } + func showBluetoothDisabledDialog() { + let bluetoothDisabledAlert = SILBluetoothDisabledAlert.advertiser + viewController.alertWithOKButton(title: bluetoothDisabledAlert.title, + message: bluetoothDisabledAlert.message) + } + func dismissPopover() { popoverController?.dismissPopover(animated: true) } diff --git a/SiliconLabsApp/ViewControllers/AppSelection/DeviceSelection/SILDeviceSelectionViewController.m b/SiliconLabsApp/ViewControllers/AppSelection/DeviceSelection/SILDeviceSelectionViewController.m index 44c5a98f..bc14393e 100644 --- a/SiliconLabsApp/ViewControllers/AppSelection/DeviceSelection/SILDeviceSelectionViewController.m +++ b/SiliconLabsApp/ViewControllers/AppSelection/DeviceSelection/SILDeviceSelectionViewController.m @@ -12,7 +12,6 @@ #import "SILDeviceSelectionCollectionViewCell.h" #import "SILDiscoveredPeripheral.h" #import "SILCentralManager.h" -#import "SILSegmentedControl.h" #import "UIImage+SILImages.h" #import "SILConstants.h" #import "SILRSSIMeasurementTable.h" @@ -173,8 +172,12 @@ - (void)registerForBluetoothControllerNotifications { selector:@selector(handleCentralManagerDidFailToConnectPeripheralNotification:) name:SILCentralManagerDidFailToConnectPeripheralNotification object:self.centralManager]; + if (self.viewModel.app.appType != SILAppTypeRangeTest) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleBluetoothDisabledNotification:) name:SILCentralManagerBluetoothDisabledNotification object:self.centralManager]; + + } } - } - (void)unregisterForBluetoothControllerNotifications { @@ -188,6 +191,11 @@ - (void)unregisterForBluetoothControllerNotifications { [[NSNotificationCenter defaultCenter] removeObserver:self name:SILCentralManagerDidFailToConnectPeripheralNotification object:self.centralManager]; + if (self.viewModel.app.appType != SILAppTypeRangeTest) { + [[NSNotificationCenter defaultCenter] removeObserver:self + name:SILCentralManagerBluetoothDisabledNotification + object:self.centralManager]; + } } } @@ -197,7 +205,7 @@ - (void)handleCentralManagerDidUpdateDiscoveredPeripheralsNotification:(NSNotifi - (void)handleCentralManagerDidConnectPeripheralNotification:(NSNotification *)notification { if (self.viewModel.connectingPeripheral) { - [SVProgressHUD showSuccessWithStatus:@"Connection Successful!"]; + [SVProgressHUD dismiss]; [self.delegate deviceSelectionViewController:self didSelectPeripheral:self.viewModel.connectingPeripheral.peripheral]; self.viewModel.connectingPeripheral = nil; } @@ -210,4 +218,25 @@ - (void)handleCentralManagerDidFailToConnectPeripheralNotification:(NSNotificati } } +- (void)handleBluetoothDisabledNotification:(NSNotification *)notification { + SILBluetoothDisabledAlertObjc* bluetoothDiabledAlert = nil; + + if (self.viewModel.app.appType == SILAppTypeHealthThermometer) { + bluetoothDiabledAlert = [[SILBluetoothDisabledAlertObjc alloc] initWithBluetoothDisabledAlert:SILBluetoothDisabledAlertHealthThermometer]; + } else if (self.viewModel.app.appType == SILAppTypeConnectedLighting) { + bluetoothDiabledAlert = [[SILBluetoothDisabledAlertObjc alloc] initWithBluetoothDisabledAlert:SILBluetoothDisabledAlertConnectedLighting]; + } + + if (bluetoothDiabledAlert == nil) { + return; + } + + [self alertWithOKButtonWithTitle:[bluetoothDiabledAlert getTitle] + message:[bluetoothDiabledAlert getMessage] completion:^(UIAlertAction * action) { + if ([self.delegate respondsToSelector:@selector(didDismissDeviceSelectionViewController)]) { + [self.delegate didDismissDeviceSelectionViewController]; + } + }]; +} + @end diff --git a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.h b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.h index a9c40c92..aa071fb6 100644 --- a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.h +++ b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.h @@ -15,8 +15,6 @@ @property (weak, nonatomic) IBOutlet UIImageView *iconImageView; @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @property (weak, nonatomic) IBOutlet UILabel *descriptionLabel; -@property (weak, nonatomic) IBOutlet UILabel *profileKeyLabel; -@property (weak, nonatomic) IBOutlet UILabel *profileValueLabel; - (void)setFieldsInCell:(SILApp*)appData; @end diff --git a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.m b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.m index 91d9d328..c24f9920 100644 --- a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.m +++ b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.m @@ -25,12 +25,7 @@ - (void)awakeFromNib { } - (void)setupCellAppearence { - [self setupTitleLabel]; - [self setupDescriptionLabel]; - [self setupProfileKeyLabel]; - [self setupProfileValueLabel]; [self setupIconImageView]; - [self setupImageView]; [self setupCellRoundedAppearance]; } @@ -41,43 +36,8 @@ -(void)layoutSubviews { [self addShadowWithOffset:SILCellShadowOffset radius:SILCellShadowRadius]; } -- (void)setupTitleLabel { - self.titleLabel.font = [UIFont robotoBoldWithSize:[UIFont getMiddleFontSize]]; - self.titleLabel.textColor = [UIColor sil_primaryTextColor]; - self.titleLabel.adjustsFontSizeToFitWidth = YES; - self.titleLabel.backgroundColor = [UIColor sil_cardBackgroundColor]; -} - -- (void)setupDescriptionLabel { - self.descriptionLabel.font = [UIFont robotoRegularWithSize:[UIFont getMiddleFontSize]]; - self.descriptionLabel.textColor = [UIColor sil_subtleTextColor]; - self.descriptionLabel.numberOfLines = 0; - self.descriptionLabel.adjustsFontSizeToFitWidth = YES; - self.descriptionLabel.backgroundColor = [UIColor sil_cardBackgroundColor]; -} - -- (void)setupProfileKeyLabel { - self.profileKeyLabel.font = [UIFont robotoRegularWithSize:[UIFont getSmallFontSize]]; - self.profileKeyLabel.textColor = [UIColor sil_subtleTextColor]; - self.profileKeyLabel.adjustsFontSizeToFitWidth = YES; - self.profileKeyLabel.backgroundColor = [UIColor sil_cardBackgroundColor]; -} - -- (void)setupProfileValueLabel { - self.profileValueLabel.font = [UIFont robotoRegularWithSize:[UIFont getSmallFontSize]]; - self.profileValueLabel.textColor = [UIColor sil_subtleTextColor]; - self.profileValueLabel.adjustsFontSizeToFitWidth = YES; - self.profileValueLabel.backgroundColor = [UIColor sil_cardBackgroundColor]; -} - - (void)setupIconImageView { self.iconImageView.layer.masksToBounds = YES; - self.iconImageView.backgroundColor = [UIColor sil_regularBlueColor]; - self.backgroundColor = [UIColor sil_cardBackgroundColor]; -} - -- (void)setupImageView { - self.imageView.backgroundColor = [UIColor sil_cardBackgroundColor]; } - (void)setupCellRoundedAppearance { @@ -91,15 +51,11 @@ - (void)prepareForReuse { _iconImageView = nil; _titleLabel = nil; _descriptionLabel = nil; - _profileKeyLabel = nil; - _profileValueLabel = nil; } - (void)setFieldsInCell:(SILApp*)appData { self.titleLabel.text = appData.title; self.descriptionLabel.text = appData.appDescription; - self.profileKeyLabel.text = EmptyText; - self.profileValueLabel.text = EmptyText; self.iconImageView.image = [UIImage imageNamed:appData.imageName]; } diff --git a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.xib b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.xib index 9963532b..6f9f52cc 100644 --- a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.xib +++ b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.xib @@ -1,102 +1,94 @@ - + - + + + + + Roboto-Bold + + + Roboto-Regular + + - + - + - + - - + + - + - + + + - + - - - + + + - - - - - - - + - - - - + + + + @@ -111,15 +103,24 @@ - + - - - + + + + + + + + + + + + diff --git a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionViewController.m b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionViewController.m index 1be27e51..624f6ba4 100644 --- a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionViewController.m +++ b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionViewController.m @@ -25,14 +25,12 @@ #import "SILConstants.h" #import "SILBluetoothBrowser+Constants.h" #import "UIView+SILShadow.h" -#if WIRELESS #import "SILConnectedLightingViewController.h" -#endif #if ENABLE_HOMEKIT #import "SILHomeKitDebugDeviceViewController.h" #endif -@interface SILAppSelectionViewController () +@interface SILAppSelectionViewController () @property (strong, nonatomic) IBOutlet UIView *allSpace; @property (strong, nonatomic) WYPopoverController *devicePopoverController; @@ -145,19 +143,6 @@ - (void)presentDeviceSelectionViewControllerWithApp:(SILApp *)app animated:(BOOL animated:YES]; } -- (void)presentRangeTestModeSelectionViewControllerWithApp:(SILApp *)app centralManager:(SILCentralManager*)manager peripheral:(CBPeripheral *)peripheral animated:(BOOL)animated { - SILRangeTestModeSelectionViewController *selectionViewController = [[SILRangeTestModeSelectionViewController alloc] init]; - - selectionViewController.app = app; - selectionViewController.delegate = self; - selectionViewController.peripheral = [[SILRangeTestPeripheral alloc] initWithPeripheral:peripheral andCentralManager:manager]; - - self.devicePopoverController = [WYPopoverController sil_presentCenterPopoverWithContentViewController:selectionViewController - presentingViewController:self - delegate:self - animated:YES]; -} - - (void)presentCalibrationViewController:(BOOL)animated { SILCalibrationViewController *calibrationViewController = [[SILCalibrationViewController alloc] init]; @@ -193,14 +178,9 @@ - (void)showHomeKitDebugWithApp:(SILApp *)app animated:(BOOL)animated { #endif } -- (void)showRangeTestWithApp:(SILApp *)app forPeripheral:(SILRangeTestPeripheral *)peripheral andBoardInfo:(SILRangeTestBoardInfo *)boardInfo withMode:(SILRangeTestMode)mode animated:(BOOL)animated { - UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"RangeTestStoryboard" bundle:nil]; +- (void)showRangeTestWithApp:(SILApp *)app animated:(BOOL)animated { + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"SILAppTypeRangeTest" bundle:nil]; SILRangeTestAppViewController *viewController = [storyboard instantiateInitialViewController]; - SILRangeTestAppViewModel *viewModel = [[SILRangeTestAppViewModel alloc] initWithMode:mode peripheral:peripheral andBoardInfo:boardInfo]; - - viewController.app = app; - viewController.viewModel = viewModel; - [self.navigationController pushViewController:viewController animated:animated]; } @@ -209,9 +189,11 @@ - (void)didSelectApp:(SILApp *)app { switch (app.appType) { case SILAppTypeConnectedLighting: case SILAppTypeHealthThermometer: - case SILAppTypeRangeTest: [self presentDeviceSelectionViewControllerWithApp:app animated:YES]; break; + case SILAppTypeRangeTest: + [self showRangeTestWithApp:app animated:YES]; + break; case SILAppTypeRetailBeacon: [self showRetailBeaconAppWithApp:app animated:YES]; break; @@ -259,7 +241,7 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollection CGFloat minimumLineSpacing = 16.0; CGFloat width = floor((self.appCollectionView.frame.size.width - self.appCollectionView.contentInset.left - self.appCollectionView.contentInset.right - self.appCollectionView.alignmentRectInsets.left - self.appCollectionView.alignmentRectInsets.right) / cellsInRow) - minimumLineSpacing; - CGFloat height = 162.0; + CGFloat height = 182.0; return CGSizeMake(width, height); } @@ -285,17 +267,10 @@ - (void)deviceSelectionViewController:(SILDeviceSelectionViewController *)viewCo if (viewController.viewModel.app.appType == SILAppTypeHealthThermometer) { [self runHealthThermometer:viewController forPeripheral:peripheral]; } else if (viewController.viewModel.app.appType == SILAppTypeConnectedLighting) { -#if WIRELESS - SILConnectedLightingViewController *appViewController = [[SILConnectedLightingViewController alloc] init]; + SILConnectedLightingViewController *appViewController = [[UIStoryboard storyboardWithName:@"SILAppTypeConnectedLighting" bundle:nil] instantiateInitialViewController]; appViewController.centralManager = viewController.centralManager; appViewController.connectedPeripheral = peripheral; [self.navigationController pushViewController:appViewController animated:YES]; -#endif - } else if (viewController.viewModel.app.appType == SILAppTypeRangeTest) { - [self presentRangeTestModeSelectionViewControllerWithApp:viewController.viewModel.app - centralManager:viewController.centralManager - peripheral:peripheral - animated:YES]; } }]; } @@ -317,20 +292,6 @@ - (void)runHealthThermometer:(SILDeviceSelectionViewController*)viewController f } } -#pragma mark - SILRangeTestModeSelectionViewControllerDelegate - --(void)didRangeTestModeSelectedForApp:(SILApp *)app peripheral:(SILRangeTestPeripheral *)peripheral andBoardInfo:(SILRangeTestBoardInfo *)boardInfo selectedMode:(enum SILRangeTestMode)mode { - [self.devicePopoverController dismissPopoverAnimated:YES completion:^{ - self.devicePopoverController = nil; - [self showRangeTestWithApp:app forPeripheral:peripheral andBoardInfo:boardInfo withMode:mode animated:YES]; - }]; -} - -- (void)didDismissRangeTestModeSelectionViewController { - [self.devicePopoverController dismissPopoverAnimated:YES completion:nil]; - self.devicePopoverController = nil; -} - - (void)presentAppSelectionInfoViewController:(BOOL)animated { SILAppSelectionInfoViewController *infoViewController = [[SILAppSelectionInfoViewController alloc] init]; infoViewController.delegate = self; diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Connections/SILBrowserConnectionsTableViewCell.m b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Connections/SILBrowserConnectionsTableViewCell.m index 5e9bb17f..2ec35a39 100644 --- a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Connections/SILBrowserConnectionsTableViewCell.m +++ b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Connections/SILBrowserConnectionsTableViewCell.m @@ -64,8 +64,7 @@ - (void)customizeUnselectedAppearance { } - (void)postNotificationToViewModel { - NSString* indexString = [NSString stringWithFormat:@"%luld", (unsigned long)_index]; - NSDictionary* userInfo = @{SILNotificationKeyIndex: indexString}; + NSDictionary* userInfo = @{SILNotificationKeyIndex: @(_index)}; [[NSNotificationCenter defaultCenter] postNotificationName:SILNotificationDisconnectPeripheral object:self userInfo:userInfo]; } diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Filter/Cells/SILSavedSearchesTableViewCell.m b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Filter/Cells/SILSavedSearchesTableViewCell.m index dcb3f9c0..ad6b31bd 100644 --- a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Filter/Cells/SILSavedSearchesTableViewCell.m +++ b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Filter/Cells/SILSavedSearchesTableViewCell.m @@ -53,8 +53,7 @@ - (IBAction)deleteButtonWasTapped:(id)sender { } - (void)postNotificationToViewModel { - NSString* indexString = [NSString stringWithFormat:@"%luld", (unsigned long)_index]; - NSDictionary* userInfo = @{SILNotificationKeyIndex: indexString}; + NSDictionary* userInfo = @{SILNotificationKeyIndex: @(_index)}; [[NSNotificationCenter defaultCenter] postNotificationName:SILNotificationDeleteSavedSearch object:self userInfo:userInfo]; } diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/FilterBar/SILFilterBarViewController.swift b/SiliconLabsApp/ViewControllers/BluetoothBrowser/FilterBar/SILFilterBarViewController.swift new file mode 100644 index 00000000..bf8db3d5 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/BluetoothBrowser/FilterBar/SILFilterBarViewController.swift @@ -0,0 +1,60 @@ +// +// SILFilterBarViewController.swift +// BlueGecko +// +// Created by Kamil Czajka on 10.12.2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import UIKit + +class SILFilterBarViewController: UIViewController { + @IBOutlet weak var filterParametersLabel: UILabel! + private var viewModel = SILFilterBarViewModel() + private var tokensBag = SILObservableTokenBag() + + override func viewDidLoad() { + super.viewDidLoad() + setupGestureRecognizer() + observeViewModel() + } + + private func setupGestureRecognizer() { + let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) + filterParametersLabel.addGestureRecognizer(gestureRecognizer) + } + + @objc private func handleTap() { + self.viewModel.handleTap() + } + + private func observeViewModel() { + tokensBag.add(token: self.viewModel.state.observe { [weak self] expanded in + self?.updateLabel(expanded: expanded) + }) + + tokensBag.add(token: self.viewModel.filterParamters.observe { [weak self] filterParameters in + self?.updateLabelText(text: filterParameters) + }) + } + + func updateCurrentFilter(filter: SILBrowserFilterViewModel?) { + self.viewModel.updateCurrentFilter(filter: filter) + } + + func restore() { + self.filterParametersLabel.text = self.viewModel.filterParamters.value + } + + func isEmpty() -> Bool { + return self.filterParametersLabel.text == "" + } + + private func updateLabelText(text: String) { + self.filterParametersLabel.text = text + } + + private func updateLabel(expanded: Bool) { + self.filterParametersLabel.numberOfLines = expanded ? 0 : 1 + } +} diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/KeyChains/SILBrowserMappingsSegmentedControl.swift b/SiliconLabsApp/ViewControllers/BluetoothBrowser/KeyChains/SILBrowserMappingsSegmentedControl.swift new file mode 100644 index 00000000..8b0b15a3 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/BluetoothBrowser/KeyChains/SILBrowserMappingsSegmentedControl.swift @@ -0,0 +1,22 @@ +// +// SILBrowserMappingsSegmentedControl.swift +// BlueGecko +// +// Created by Jan Wisniewski on 02/03/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import UIKit + +@IBDesignable +class SILBrowserMappingsSegmentedControl: SILSegmentedControl { + + enum SegmentType: Int { + case characteristics = 0 + case services = 1 + } + + var segmentType: SegmentType { + return SegmentType(rawValue: self.selectedSegmentIndex)! + } +} diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/KeyChains/SILKeychainViewController.swift b/SiliconLabsApp/ViewControllers/BluetoothBrowser/KeyChains/SILKeychainViewController.swift index 58b5c34b..8b216811 100644 --- a/SiliconLabsApp/ViewControllers/BluetoothBrowser/KeyChains/SILKeychainViewController.swift +++ b/SiliconLabsApp/ViewControllers/BluetoothBrowser/KeyChains/SILKeychainViewController.swift @@ -12,7 +12,7 @@ import RealmSwift class SILKeychainViewController: UIViewController { - @IBOutlet weak var segments: SILBrowserSegmentedControl! + @IBOutlet weak var segments: SILBrowserMappingsSegmentedControl! @IBOutlet weak var tableView: UITableView! @IBOutlet weak var infoImage: UIImageView! @@ -129,7 +129,7 @@ class SILKeychainViewController: UIViewController { self.navigationController?.popViewController(animated: true) } - @IBAction func segmentChanged(_ sender: SILBrowserSegmentedControl) { + @IBAction func segmentChanged(_ sender: SILBrowserMappingsSegmentedControl) { tableView.reloadData() } } diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/RefreshImageView/SILRefreshImageView.m b/SiliconLabsApp/ViewControllers/BluetoothBrowser/RefreshImageView/SILRefreshImageView.m index 86f8681b..3685c95b 100644 --- a/SiliconLabsApp/ViewControllers/BluetoothBrowser/RefreshImageView/SILRefreshImageView.m +++ b/SiliconLabsApp/ViewControllers/BluetoothBrowser/RefreshImageView/SILRefreshImageView.m @@ -85,6 +85,10 @@ - (void)moveImage:(UIPanGestureRecognizer *)recognizer { CGFloat duration = fabs(self.gestureLastOffsetValue / velocity.y); NSInteger verticalChangeValue = translation.y - _gestureBeganContentOffsetValue; CGFloat contentOffsetY = self.model.tableView.contentOffset.y; + + if (verticalChangeValue < 0) { + verticalChangeValue = 0; + } if (contentOffsetY == 0) { switch (recognizer.state) { @@ -95,6 +99,7 @@ - (void)moveImage:(UIPanGestureRecognizer *)recognizer { break; case UIGestureRecognizerStateChanged: + [self showAndManageRefreshingImage]; self.gestureLastOffsetValue = fabs(verticalChangeValue - self.gestureLastOffsetValue); if (verticalChangeValue <= RefreshTopConstraintMaxValue) { [self animateWithDuration: duration @@ -109,8 +114,8 @@ - (void)moveImage:(UIPanGestureRecognizer *)recognizer { } } - if (recognizer.state == UIGestureRecognizerStateBegan) { - [self.refreshImageView setHidden:NO]; + if (recognizer.state == UIGestureRecognizerStateBegan && [self shouldRefreshUI:velocity contentOffsetY:contentOffsetY]) { + [self showAndManageRefreshingImage]; self.gestureBeganContentOffsetValue = contentOffsetY; self.gestureLastOffsetValue = contentOffsetY; [self.layer removeAllAnimations]; @@ -118,6 +123,15 @@ - (void)moveImage:(UIPanGestureRecognizer *)recognizer { } } +- (BOOL)shouldRefreshUI:(CGPoint)velocity contentOffsetY:(CGFloat)contentOffsetY { + return velocity.y > 0 && contentOffsetY == 0; +} + +- (void)showAndManageRefreshingImage { + [self.model.tableView setScrollEnabled:NO]; + [self.refreshImageView setHidden:NO]; +} + - (void)animateRefreshingImageWithDuration:(NSTimeInterval)duration { if (self.model.topRefreshImageConstraint.constant >= RefreshTopConstraintActionValue) { [self animateWithDuration:duration diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/SILBluetoothBrowserViewController.m b/SiliconLabsApp/ViewControllers/BluetoothBrowser/SILBluetoothBrowserViewController.m index 58cf502f..7a748b5e 100644 --- a/SiliconLabsApp/ViewControllers/BluetoothBrowser/SILBluetoothBrowserViewController.m +++ b/SiliconLabsApp/ViewControllers/BluetoothBrowser/SILBluetoothBrowserViewController.m @@ -26,7 +26,6 @@ #import "NSString+SILBrowserNotifications.h" #import "SILBluetoothBrowser+Constants.h" #import "SILStoryboard+Constants.h" -#import "SILBluetoothBrowserExpandableViewManager.h" #import "BlueGecko.pch" #import "SILBluetoothBrowser+Constants.h" #import "SILRefreshImageView.h" @@ -36,7 +35,7 @@ #import "SILExitPopupViewController.h" #import "SILBrowserSettings.h" -@interface SILBluetoothBrowserViewController () +@interface SILBluetoothBrowserViewController () @property (weak, nonatomic) IBOutlet UIView *navigationBarView; @property (weak, nonatomic) IBOutlet UIView *aboveSpaceAreaView; @@ -47,6 +46,7 @@ @interface SILBluetoothBrowserViewController () * expandSections; @@ -76,7 +79,7 @@ @implementation SILBluetoothBrowserViewController NSString* const TitleForScanningButtonWhenIsNotScanning = @"Start Scanning"; long long const ms = 1000; NSString* const AppendingMS = @" ms"; -const float TABLE_FRESH_INTERVAL = 2.0f; +const float TABLE_FRESH_INTERVAL = 1.0f; - (void)viewDidLoad { [super viewDidLoad]; @@ -114,6 +117,7 @@ - (void)viewWillDisappear:(BOOL)animated { self.browserViewModel.observing = NO; [self setScannerStateWhenControllerIsDisappeared]; [self.browserViewModel clearIsConnectingDirectory]; + [self.browserExpandableViewManager removeExpandingControllerIfNeeded]; [[NSNotificationCenter defaultCenter] removeObserver:self]; } @@ -145,10 +149,11 @@ - (void)setupBrowserExpandableViewManager { [self.browserExpandableViewManager setReferenceForPresentationView:self.presentationView andDiscoveredDevicesView:self.discoveredDevicesView]; [self.browserExpandableViewManager setReferenceForExpandableControllerView:self.expandableControllerView andExpandableControllerHeight:self.expandableControllerHeight]; [self.browserExpandableViewManager setValueForCornerRadius:self.cornerRadius]; + [self.browserExpandableViewManager setupFilterBarWithFilterBarHeight:self.filterBarHeight filterBarViewController:self.filterBarViewController]; } - (void)setupButtonsTabBar { - [self.browserExpandableViewManager setupButtonsTabBarWithLog:self.logButton connections:self.connectionsButton filter:self.filterButton andFilterIsActive:[self.filterViewModel isFilterActive]]; + [self.browserExpandableViewManager setupButtonsTabBarWithLog:self.logButton connections:self.connectionsButton filter:self.filterButton andFilterIsActive:[self.filterViewModel isFilterActive] andSortButton:self.sortButton]; } - (void)setupScanningButton { @@ -172,6 +177,7 @@ - (void)installViewModelsForExpandableViews { self.connectionsViewModel = [SILBrowserConnectionsViewModel sharedInstance]; self.connectionsViewModel.centralManager = self.browserViewModel.centralManager; self.filterViewModel = [SILBrowserFilterViewModel sharedInstance]; + self.sortViewModel = [SILSortViewModel sharedInstance]; } - (void)setupBackgroundForScanning:(BOOL)scanning { @@ -222,7 +228,7 @@ - (void)displayToast:(NSNotification*)notification { - (void)setupRefreshImageView { self.refreshImageView.model = [[SILRefreshImageModel alloc] initWithConstraint:self.topRefreshImageConstraint - withEmptyView:self.presentationView + withEmptyView:self.discoveredDevicesView withTableView:self.browserTableView andWithReloadAction: ^{ [self refreshTableView]; @@ -260,6 +266,16 @@ - (void)scanningDidEnd { [self stopScanningForDevices]; } +- (void)bluetoothIsDisabled { + SILBluetoothDisabledAlertObjc* bluetoothDisabledAlert = [[SILBluetoothDisabledAlertObjc alloc] initWithBluetoothDisabledAlert:SILBluetoothDisabledAlertBrowser]; + [self alertWithOKButtonWithTitle:[bluetoothDisabledAlert getTitle] + message:[bluetoothDisabledAlert getMessage] + completion:^(UIAlertAction * action) { + [self stopScanningAndDisconnectAll]; + [self.navigationController popToRootViewControllerAnimated:YES]; + }]; +} + #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { @@ -448,6 +464,13 @@ - (IBAction)logButtonWasTapped:(id)sender { } } +- (IBAction)sortButtonWasTapped:(id)sender { + SILSortViewController* sortVC = [self.browserExpandableViewManager sortButtonWasTappedAction]; + if (sortVC.delegate == nil) { + sortVC.delegate = self; + } +} + #pragma mark - Scanning - (void)setScanningButtonAppearanceWithScanning:(BOOL)isScanning { @@ -469,12 +492,13 @@ - (void)setStartScanningButton { } - (IBAction)scanningButtonWasTapped:(id)sender { - if (self.isScanning) { + BOOL previousState = self.isScanning; + self.isScanning = !self.isScanning; + if (previousState) { [self stopScanningAction]; } else { [self startScanningAction]; } - self.isScanning = !self.isScanning; [self setScanningButtonAppearanceWithScanning:self.isScanning]; } @@ -532,8 +556,7 @@ - (void)connectViewButtonTappedInCell:(SILBrowserDeviceViewCell*)cell { } - (void)postNotificationToViewModel:(NSInteger)index { - NSString* indexString = [NSString stringWithFormat:@"%luld", (unsigned long)index]; - NSDictionary* userInfo = @{SILNotificationKeyIndex: indexString}; + NSDictionary* userInfo = @{SILNotificationKeyIndex: @(index)}; [[NSNotificationCenter defaultCenter] postNotificationName:SILNotificationDisconnectPeripheral object:self userInfo:userInfo]; } @@ -592,23 +615,29 @@ - (void)cellsForVisibleRows { - (void)startScanning { [self.browserViewModel startScanning]; + self.tableRefreshTimer = [NSTimer scheduledTimerWithTimeInterval:TABLE_FRESH_INTERVAL target:self selector:@selector(tableRefreshTimerFired) userInfo:nil repeats:YES]; - if (self.browserViewModel.isContentAvailable == NO) { - self.browserTableView.hidden = YES; - } - [self setupBackgroundForScanning:YES]; + [self displayNoDeviceViewIfNeeded]; } - (void)tableRefreshTimerFired { + [self refreshTable]; + [self displayNoDeviceViewIfNeeded]; +} + +- (void)displayNoDeviceViewIfNeeded { + [self setupBackgroundForScanning:self.isScanning]; if (self.browserViewModel.isContentAvailable) { self.browserTableView.hidden = NO; [self.noDevicesFoundView setHidden:YES]; - [self refreshTable]; + } else { + [self.browserTableView setHidden:YES]; + [self.noDevicesFoundView setHidden:NO]; } } @@ -622,6 +651,8 @@ - (void)searchButtonWasTapped:(SILBrowserFilterViewModel *)filterViewModel { [self.browserExpandableViewManager removeExpandingControllerIfNeeded]; [self manageAppearanceOfActiveFilterImage:filterViewModel]; [self filterBrowser:filterViewModel]; + [self.browserExpandableViewManager updateFilterBarWith:filterViewModel]; + [self displayNoDeviceViewIfNeeded]; } - (void)manageAppearanceOfActiveFilterImage:(SILBrowserFilterViewModel*)filterViewModel { @@ -697,9 +728,17 @@ - (IBAction)backToDevelopWasTapped:(id)sender { - (void)stopScanningAndDisconnectAll { [self stopScanningAction]; [self.connectionsViewModel disconnectAllPeripheral]; + [self.sortViewModel deselectSelectedOption]; [self.navigationController popViewControllerAnimated:NO]; } +#pragma mark = SILSortViewControllerDelegate + +- (void)sortOptionWasSelectedWithOption:(SILSortOption)option { + self.browserViewModel.sortOption = option; + [self.browserExpandableViewManager changeImagesOfSortButtonForOption:option]; +} + #pragma mark - WYPopoverControllerDelegate - (void)popoverControllerDidDismissPopover:(WYPopoverController *)popoverController { @@ -735,7 +774,8 @@ - (void)clearViewModelsForExpandableViews { - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (scrollView.contentOffset.y < 0) { scrollView.contentOffset = CGPointZero; - [scrollView setScrollEnabled:NO]; + } else { + [self.browserTableView setScrollEnabled:YES]; } } @@ -747,9 +787,18 @@ - (void)refreshTableView { [self.noDevicesFoundView setHidden:NO]; self.isScanning = YES; [self setScanningButtonAppearanceWithScanning:self.isScanning]; - [self setupBackgroundForScanning:YES]; [self startScanning]; } +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + [self getInstanceOfFilterBarViewController:segue]; +} + +- (void)getInstanceOfFilterBarViewController:(UIStoryboardSegue*)segue { + if ([segue.identifier isEqual:@"filterBarViewController"]) { + self.filterBarViewController = (SILFilterBarViewController*)segue.destinationViewController; + self.filterBarViewController.view.translatesAutoresizingMaskIntoConstraints = NO; + } +} @end diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortModeViewCell.swift b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortModeViewCell.swift new file mode 100644 index 00000000..ab46335a --- /dev/null +++ b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortModeViewCell.swift @@ -0,0 +1,27 @@ +// +// SILSortModeViewCell.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 10/11/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import UIKit + +class SILSortModeViewCell: UITableViewCell { + + @IBOutlet weak var sortModeLabel: UILabel! + @IBOutlet weak var checkImage: UIImageView! + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + if selected { + sortModeLabel.textColor = UIColor.sil_regularBlue() + checkImage.isHidden = false + } else { + sortModeLabel.textColor = UIColor.sil_primaryText() + checkImage.isHidden = true + } + } + +} diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortTypeViewCell.swift b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortTypeViewCell.swift new file mode 100644 index 00000000..943185a9 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortTypeViewCell.swift @@ -0,0 +1,15 @@ +// +// SILSortTypeViewCell.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 10/11/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import UIKit + +class SILSortTypeViewCell: UITableViewCell { + + @IBOutlet weak var typeLabel: UILabel! + +} diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortViewController.swift b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortViewController.swift new file mode 100644 index 00000000..6990dc13 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Sort/SILSortViewController.swift @@ -0,0 +1,72 @@ +// +// SILSortViewController.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 10/11/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import UIKit + +@objc +protocol SILSortViewControllerDelegate: class { + @objc(sortOptionWasSelectedWithOption:) + func sortOptionWasSelected(with option: SILSortOption) +} + +class SILSortViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { + + @IBOutlet weak var sortSettingTable: UITableView! + + var viewModel: SILSortViewModel! + @objc var delegate: SILSortViewControllerDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + viewModel = SILSortViewModel.sharedInstance() + sortSettingTable.dataSource = self + sortSettingTable.delegate = self + sortSettingTable.tableFooterView = UIView(frame: .zero) + } + + func numberOfSections(in tableView: UITableView) -> Int { + return viewModel.sections.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return viewModel.sections[section].modes.count + 1 + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return viewModel.cellHeight(forIndexPath: indexPath) + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let section = viewModel.sections[indexPath.section] + if indexPath.row == 0 { + let cell = tableView.dequeueReusableCell(withIdentifier: "SILSortTypeViewCell", for: indexPath) as! SILSortTypeViewCell + cell.typeLabel.text = section.type + cell.isUserInteractionEnabled = false + return cell + } else { + let cell = tableView.dequeueReusableCell(withIdentifier: "SILSortModeViewCell", for: indexPath) as! SILSortModeViewCell + cell.sortModeLabel.text = section.modes[indexPath.row - 1].modeName + cell.selectionStyle = .none + return cell + } + } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if indexPath.row != 0, viewModel.isSelected(forIndexPath: indexPath) { + tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none) + } + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.row != 0 { + viewModel.selectOption(forIndexPath: indexPath) + delegate?.sortOptionWasSelected(with: viewModel.selectedOption) + self.sortSettingTable.reloadData() + } + } +} diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.h b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.h deleted file mode 100644 index 153c836e..00000000 --- a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// SILBluetoothBrowserExpandableViewManager.h -// SiliconLabsApp -// -// Created by Kamil Czajka on 26/03/2020. -// Copyright © 2020 SiliconLabs. All rights reserved. -// - -#ifndef SILBluetoothBrowserExpandableViewManager_h -#define SILBluetoothBrowserExpandableViewManager_h - -#import "SILBrowserLogViewController.h" -#import "SILBrowserConnectionsViewController.h" -#import "SILBrowserFilterViewController.h" - -@interface SILBluetoothBrowserExpandableViewManager : NSObject - -- (instancetype)initWithOwnerViewController:(UIViewController*)viewController; -- (void)setValueForCornerRadius:(CGFloat)cornerRadius; -- (void)setReferenceForPresentationView:(UIView*)presentationView andDiscoveredDevicesView:(UIView*)discoveredDevicesView; -- (void)setReferenceForExpandableControllerView:(UIView*)expandableControllerView andExpandableControllerHeight:(NSLayoutConstraint*)expandableControllerHeight; -- (void)setupButtonsTabBarWithLog:(UIButton*)logButton connections:(UIButton*)connectionsButton; -- (void)setupButtonsTabBarWithLog:(UIButton*)logButton connections:(UIButton*)connectionsButton filter:(UIButton*)filterButton andFilterIsActive:(BOOL)isActive; -- (SILBrowserLogViewController*)logButtonWasTappedAction; -- (SILBrowserConnectionsViewController*)connectionsButtonWasTappedAction; -- (SILBrowserFilterViewController*)filterButtonWasTappedAction; -- (void)removeExpandingControllerIfNeeded; -- (void)updateConnectionsButtonTitle:(NSUInteger)connections; -- (void)updateFilterIsActiveFilter:(BOOL)isActiveFilter; - -@end - -#endif /* SILBluetoothBrowserExpandableViewManager_h */ diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.m b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.m deleted file mode 100644 index 1f50bb04..00000000 --- a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.m +++ /dev/null @@ -1,327 +0,0 @@ -// -// SILBluetoothBrowserExpandableViewManager.m -// BlueGecko -// -// Created by Kamil Czajka on 26/03/2020. -// Copyright © 2020 SiliconLabs. All rights reserved. -// - -#import -#import -#import "SILBluetoothBrowserExpandableViewManager.h" -#import "UIImage+SILImages.h" -#import "SILStoryboard+Constants.h" -#import "SILBrowserLogViewController.h" -#import "SILBrowserConnectionsViewController.h" -#import "SILBrowserFilterViewController.h" -#import "SILBluetoothBrowser+Constants.h" - -@interface SILBluetoothBrowserExpandableViewManager () - -@property (strong, nonatomic) UIButton* logButton; -@property (strong, nonatomic) UIButton* connectionsButton; -@property (strong, nonatomic) UIButton* filterButton; -@property (strong, nonatomic) NSLayoutConstraint* expandableControllerHeight; -@property (strong, nonatomic) UIView* expandableControllerView; -@property (strong, nonatomic) UIViewController* expandingViewController; -@property (strong, nonatomic) UIVisualEffectView* effectView; -@property (strong, nonatomic) UIView* presentationView; -@property (strong, nonatomic) UIView* discoveredDevicesView; -@property (strong, nonatomic) UIViewController* browserViewController; -@property (nonatomic, assign) CGFloat cornerRadius; - -@end - -@implementation SILBluetoothBrowserExpandableViewManager - -- (instancetype)initWithOwnerViewController:(UIViewController*)viewController { - self = [super init]; - if (self) { - _browserViewController = viewController; - _cornerRadius = 0; - } - return self; -} - -- (void)setupButtonsTabBarWithLog:(UIButton*)logButton connections:(UIButton*)connectionsButton { - self.logButton = logButton; - self.connectionsButton = connectionsButton; - [self setupLogButton]; - [self setupConnectionButton]; -} - -- (void)setupButtonsTabBarWithLog:(UIButton*)logButton connections:(UIButton*)connectionsButton filter:(UIButton*)filterButton andFilterIsActive:(BOOL)isActive { - self.logButton = logButton; - self.connectionsButton = connectionsButton; - self.filterButton = filterButton; - [self setupLogButton]; - [self setupConnectionButton]; - [self setupFilterButtonWhereIsFilterActive:isActive]; -} - -- (void)setReferenceForPresentationView:(UIView*)presentationView andDiscoveredDevicesView:(UIView*)discoveredDevicesView { - self.presentationView = presentationView; - self.discoveredDevicesView = discoveredDevicesView; -} - -- (void)setReferenceForExpandableControllerView:(UIView*)expandableControllerView andExpandableControllerHeight:(NSLayoutConstraint*)expandableControllerHeight { - self.expandableControllerView = expandableControllerView; - self.expandableControllerHeight = expandableControllerHeight; -} - -- (void)setValueForCornerRadius:(CGFloat)cornerRadius { - self.cornerRadius = cornerRadius; -} - -- (SILBrowserLogViewController*)logButtonWasTappedAction { - SILBrowserLogViewController* logVC; - - if (self.logButton.isSelected == NO) { - BOOL anyButtonSelected = [self isAnyButtonSelected]; - [self prepareSceneDependOnButtonSelection:anyButtonSelected]; - - UIStoryboard* storyboard = [UIStoryboard storyboardWithName:SILAppBluetoothBrowserHome bundle:nil]; - logVC = [storyboard instantiateViewControllerWithIdentifier:SILSceneLog]; - - [self insertIntoContainerExpandableController:logVC]; - [self animateExpandableViewControllerIfNeeded:anyButtonSelected]; - self.expandingViewController = logVC; - - [self.logButton setSelected:YES]; - } else { - [self prepareSceneForRemoveExpandingController]; - } - - return logVC; -} - -- (SILBrowserConnectionsViewController*)connectionsButtonWasTappedAction { - SILBrowserConnectionsViewController* connectionVC; - - if (self.connectionsButton.isSelected == NO) { - BOOL anyButtonSelected = [self isAnyButtonSelected]; - [self prepareSceneDependOnButtonSelection:anyButtonSelected]; - - UIStoryboard* storyboard = [UIStoryboard storyboardWithName:SILAppBluetoothBrowserHome bundle:nil]; - connectionVC = [storyboard instantiateViewControllerWithIdentifier:SILSceneConnections]; - - [self insertIntoContainerExpandableController:connectionVC]; - [self animateExpandableViewControllerIfNeeded:anyButtonSelected]; - self.expandingViewController = connectionVC; - - [self.connectionsButton setSelected:YES]; - } else { - [self prepareSceneForRemoveExpandingController]; - } - - return connectionVC; -} - -- (SILBrowserFilterViewController*)filterButtonWasTappedAction { - SILBrowserFilterViewController* filterVC; - - if (self.filterButton.isSelected == NO) { - BOOL anyButtonSelected = [self isAnyButtonSelected]; - [self prepareSceneDependOnButtonSelection:anyButtonSelected]; - - UIStoryboard* storyboard = [UIStoryboard storyboardWithName:SILAppBluetoothBrowserHome bundle:nil]; - filterVC = [storyboard instantiateViewControllerWithIdentifier:SILSceneFilter]; - - [self insertIntoContainerExpandableController:filterVC]; - [self animateExpandableViewControllerIfNeeded:anyButtonSelected]; - self.expandingViewController = filterVC; - - [self.filterButton setSelected:YES]; - } else { - [self prepareSceneForRemoveExpandingController]; - } - - return filterVC; -} - -- (void)removeExpandingControllerIfNeeded { - [self prepareSceneForRemoveExpandingController]; -} - -- (void)updateConnectionsButtonTitle:(NSUInteger)connections { - NSString* const AppendingConnections = @" Connections"; - NSString* connectionsText = [NSString stringWithFormat:@"%lu%@", (unsigned long)connections, AppendingConnections]; - [self.connectionsButton setTitle:connectionsText forState:UIControlStateNormal]; - [self.connectionsButton setTitle:connectionsText forState:UIControlStateSelected]; -} - -- (void)updateFilterIsActiveFilter:(BOOL)isActiveFiter { - if (isActiveFiter) { - [self setupImagesForActiveFilter]; - } else { - [self setupImagesForInactiveFilter]; - } -} - -#pragma mark - Private Functions - -- (void)setupLogButton { - UIEdgeInsets const ImageInsetsForLogButton = {0, 16, 0 ,8}; - UIEdgeInsets const TitleEdgeInsetsForLogButton = {0, 20., 0, 0}; - - [self.logButton setTintColor:[UIColor clearColor]]; - [self.logButton setImage:[[UIImage imageNamed:SILImageLogOff] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState: UIControlStateNormal]; - [self.logButton setImage:[[UIImage imageNamed:SILImageLogOn] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateSelected]; - self.logButton.imageView.contentMode = UIViewContentModeScaleAspectFit; - self.logButton.imageEdgeInsets = ImageInsetsForLogButton; - [self.logButton setTitleEdgeInsets:TitleEdgeInsetsForLogButton]; - self.logButton.titleLabel.adjustsFontSizeToFitWidth = YES; - self.logButton.titleLabel.font = [UIFont robotoMediumWithSize:[UIFont getMiddleFontSize]]; - [self.logButton setTitleColor:[UIColor sil_primaryTextColor] forState:UIControlStateNormal]; - [self.logButton setTitleColor:[UIColor sil_regularBlueColor] forState:UIControlStateSelected]; -} - -- (void)setupConnectionButton { - UIEdgeInsets const ImageInsetsForConnectionsButton = {0, 8, 0 ,8}; - UIEdgeInsets const TitleEdgeInsetsForConnectionsButton = {0, 8, 0, 0}; - - [self.connectionsButton setTintColor:[UIColor clearColor]]; - [self.connectionsButton setImage:[[UIImage imageNamed:SILImageConnectOff] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateNormal]; - [self.connectionsButton setImage:[[UIImage imageNamed:SILImageConnectOn] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateSelected]; - self.connectionsButton.imageView.contentMode = UIViewContentModeScaleAspectFit; - self.connectionsButton.imageEdgeInsets = ImageInsetsForConnectionsButton; - [self.connectionsButton setTitleEdgeInsets:TitleEdgeInsetsForConnectionsButton]; - self.connectionsButton.titleLabel.adjustsFontSizeToFitWidth = YES; - self.connectionsButton.titleLabel.font = [UIFont robotoMediumWithSize:[UIFont getMiddleFontSize]]; - [self.connectionsButton setTitleColor:[UIColor sil_primaryTextColor] forState:UIControlStateNormal]; - [self.connectionsButton setTitleColor:[UIColor sil_regularBlueColor] forState:UIControlStateSelected]; -} - -- (void)setupFilterButtonWhereIsFilterActive:(BOOL)isFilterActive { - UIEdgeInsets const ImageInsetsForFilterButton = {0, 8, 0 ,12}; - UIEdgeInsets const TitleEdgeInsetsForFilterButton = {0, 8, 0, 8}; - - [self.filterButton setTintColor:[UIColor clearColor]]; - [self updateFilterIsActiveFilter:isFilterActive]; - self.filterButton.imageView.contentMode = UIViewContentModeScaleAspectFit; - self.filterButton.imageEdgeInsets = ImageInsetsForFilterButton; - [self.filterButton setTitleEdgeInsets:TitleEdgeInsetsForFilterButton]; - self.filterButton.titleLabel.adjustsFontSizeToFitWidth = YES; - self.filterButton.titleLabel.font = [UIFont robotoMediumWithSize:[UIFont getMiddleFontSize]]; - [self.filterButton setTitleColor:[UIColor sil_primaryTextColor] forState:UIControlStateNormal]; - [self.filterButton setTitleColor:[UIColor sil_regularBlueColor] forState:UIControlStateSelected]; - -} - -- (void)setupImagesForInactiveFilter { - [self.filterButton setImage:[[UIImage imageNamed:SILImageFilterOff] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateNormal]; - [self.filterButton setImage:[[UIImage imageNamed:SILImageFilterOffSelected] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateSelected]; -} - -- (void)setupImagesForActiveFilter { - [self.filterButton setImage:[[UIImage imageNamed:SILImageFilterOn] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateNormal]; - [self.filterButton setImage:[[UIImage imageNamed:SILImageFilterOnSelected] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateSelected]; -} - -- (BOOL)isAnyButtonSelected { - return ! (self.logButton.isSelected == NO && self.connectionsButton.isSelected == NO && self.filterButton.isSelected == NO); -} - -- (void)deselectAllButtons { - [self.logButton setSelected:NO]; - [self.connectionsButton setSelected:NO]; - [self.filterButton setSelected:NO]; -} - -- (void)prepareSceneDependOnButtonSelection:(BOOL)anyButtonSelected { - if (anyButtonSelected) { - [self prepareSceneForChangeExpandableView]; - } else { - [self prepareSceneForExpandableView]; - } - - [self customizeExpandableViewAppearance]; -} - -- (void)prepareSceneForChangeExpandableView { - [self deselectAllButtons]; - [self removeExpandableViewController]; -} - -- (void)prepareSceneForExpandableView { - if (self.expandingViewController != nil) { - [self prepareSceneForRemoveExpandingController]; - } - - [self attachBlurEffectView]; -} - -- (void)customizeExpandableViewAppearance { - self.cornerRadius = 20.0; - self.expandableControllerHeight.constant = self.presentationView.frame.size.height * 0.9; -} - -- (void)customizeSceneWithoutExpandableViewContoller { - self.cornerRadius = 0.0; - self.expandableControllerHeight.constant = CollapsedViewHeight; -} - -- (void)removeExpandableViewController { - [self.browserViewController willMoveToParentViewController:nil]; - [self.expandingViewController.view removeFromSuperview]; - [self.expandingViewController removeFromParentViewController]; - self.expandingViewController = nil; -} - -- (void)insertIntoContainerExpandableController:(UIViewController*)viewController { - [self.browserViewController addChildViewController:viewController]; - [self.expandableControllerView addSubview:viewController.view]; - viewController.view.frame = self.expandableControllerView.frame; - viewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; - [viewController didMoveToParentViewController:self.browserViewController]; - - [self.browserViewController.view setNeedsUpdateConstraints]; -} - -- (void)animateExpandableViewControllerIfNeeded:(BOOL)anyButtonSelected { - if (!anyButtonSelected) { - [self animateExpandableViewController]; - } -} - -- (void)animateExpandableViewController { - [UIView animateWithDuration:AnimationExpandableControllerTime delay:AnimationExpandableControllerDelay options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionTransitionCurlDown animations:^{ - [self.browserViewController.view layoutIfNeeded]; - } completion:nil]; -} - -- (void)prepareSceneForRemoveExpandingController { - [self deselectAllButtons]; - [self removeExpandableViewController]; - [self customizeSceneWithoutExpandableViewContoller]; - [self animateExpandableViewController]; - [self removeBlurEffectView]; -} - -- (void)attachBlurEffectView { - UIBlurEffect* blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; - self.effectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; - self.effectView.frame = self.presentationView.frame; - [self.discoveredDevicesView addSubview:self.effectView]; -} - -- (void)removeBlurEffectView { - [self.effectView removeFromSuperview]; -} - -- (void)setCornerRadius:(CGFloat)cornerRadius { - if (_cornerRadius != cornerRadius) { - _cornerRadius = cornerRadius; - [self adjustView]; - } -} - -- (void)adjustView { - if (self.expandableControllerView != nil) { - self.expandableControllerView.layer.cornerRadius = self.cornerRadius; - self.expandableControllerView.layer.maskedCorners = kCALayerMaxXMaxYCorner | kCALayerMinXMaxYCorner; - self.expandableControllerView.clipsToBounds = YES; - } -} - -@end diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.swift b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.swift new file mode 100644 index 00000000..d3ba83b3 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/BluetoothBrowser/Utils/SILBluetoothBrowserExpandableViewManager.swift @@ -0,0 +1,359 @@ +// +// SILBluetoothBrowserExpandableViewManager.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 18/11/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +@objc +@objcMembers +class SILBluetoothBrowserExpandableViewManager: NSObject { + + private var logButton: UIButton? + private var connectionsButton: UIButton? + private var filterButton: UIButton? + private var sortButton: UIButton? + private var expandableControllerHeight: NSLayoutConstraint? + private var expandableControllerView: UIView? + private var expandingViewController: UIViewController? + private var filterBarHeight: NSLayoutConstraint? + private var filterBarViewController: SILFilterBarViewController? + + private var effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) + + private var presentationView: UIView? + private var discoveredDevicesView: UIView? + private var browserViewController: UIViewController? + private var cornerRadius: CGFloat = 0 { + didSet { + if cornerRadius != oldValue { + adjustView() + } + } + } + + private var usedButtons: [UIButton] = [] + + init(withOwnerViewController viewController: UIViewController) { + super.init() + self.browserViewController = viewController + } + + // MARK: Setup + + func setReferenceFor(presentationView: UIView, andDiscoveredDevicesView discoveredDevicesView: UIView) { + self.presentationView = presentationView + self.discoveredDevicesView = discoveredDevicesView + } + + + func setReferenceForExpandableControllerView(_ expandableControllerView: UIView, andExpandableControllerHeight expandableControllerHeight: NSLayoutConstraint) { + self.expandableControllerView = expandableControllerView + self.expandableControllerHeight = expandableControllerHeight + } + + func setValueFor(cornerRadius: CGFloat) { + self.cornerRadius = cornerRadius + } + + func setupButtonsTabBar(log logButton: UIButton, connections connectionsButton: UIButton) { + self.logButton = logButton; + self.connectionsButton = connectionsButton; + [logButton, connectionsButton].forEach { + usedButtons.append($0) + } + setupLogButton() + setupConnectionButton() + } + + func setupButtonsTabBar(log logButton: UIButton, connections connectionsButton: UIButton, filter filterButton: UIButton, andFilterIsActive isActive: Bool, andSortButton sortButton: UIButton) { + self.logButton = logButton; + self.connectionsButton = connectionsButton; + self.filterButton = filterButton; + self.sortButton = sortButton; + [logButton, connectionsButton, filterButton, sortButton].forEach { + usedButtons.append($0) + } + setupLogButton() + setupConnectionButton() + setupFilterButtonWhereIsFilterActive(isActive) + setupSortButton() + } + + private func setupLogButton() { + logButton?.setImage(UIImage(named: SILImageLogOff)!.withRenderingMode(.alwaysOriginal), for: .normal) + logButton?.setImage(UIImage(named: SILImageLogOn)!.withRenderingMode(.alwaysOriginal), for: .selected) + setupButton(logButton!) + } + + private func setupConnectionButton() { + connectionsButton?.setImage(UIImage(named: SILImageConnectOff)!.withRenderingMode(.alwaysOriginal), for: .normal) + connectionsButton?.setImage(UIImage(named: SILImageConnectOn)!.withRenderingMode(.alwaysOriginal), for: .selected) + setupButton(connectionsButton!) + } + + private func setupFilterButtonWhereIsFilterActive(_ isFilterActive: Bool) { + updateFilterIsActiveFilter(isFilterActive) + setupButton(filterButton!) + } + + private func setupSortButton() { + sortButton?.setImage(UIImage(named: SILImageSortOff)!.withRenderingMode(.alwaysOriginal), for: .normal) + sortButton?.setImage(UIImage(named: SILImageSortOn)!.withRenderingMode(.alwaysOriginal), for: .selected) + setupButton(sortButton!) + } + + private func setupButton(_ button: UIButton) { + let titleEdgeInsetsForButton = UIEdgeInsets(top: 0, left: 7, bottom: 0, right: 0) + button.tintColor = .clear + button.imageView?.contentMode = .scaleAspectFit + button.titleEdgeInsets = titleEdgeInsetsForButton + button.titleLabel?.font = UIFont.robotoMedium(size: UIFont.getMiddleFontSize()) + button.setTitleColor(UIColor.sil_primaryText(), for: .normal) + button.setTitleColor(UIColor.sil_regularBlue(), for: .selected) + } + + func setupFilterBar(filterBarHeight: NSLayoutConstraint?, filterBarViewController: SILFilterBarViewController?) { + self.filterBarHeight = filterBarHeight + self.filterBarViewController = filterBarViewController + } + + func updateFilterIsActiveFilter(_ isActiveFilter: Bool) { + if isActiveFilter { + setupImagesForActiveFilter() + } else { + setupImagesForInactiveFilter() + } + } + + func updateFilterBar(with filterViewModel: SILBrowserFilterViewModel?) { + if let filterViewModel = filterViewModel { + self.filterBarViewController?.updateCurrentFilter(filter: filterViewModel) + self.filterBarHeight?.isActive = !filterViewModel.isFilterActive() + } else { + hideFilterBar() + } + } + + private func hideFilterBar() { + self.filterBarHeight?.isActive = true + } + + private func setupImagesForInactiveFilter() { + filterButton?.setImage(UIImage(named: SILImageFilterOff)!.withRenderingMode(.alwaysOriginal), for: .normal) + filterButton?.setImage(UIImage(named: SILImageFilterOffSelected)!.withRenderingMode(.alwaysOriginal), for: .selected) + } + + private func setupImagesForActiveFilter() { + filterButton?.setImage(UIImage(named: SILImageFilterOn)!.withRenderingMode(.alwaysOriginal), for: .normal) + filterButton?.setImage(UIImage(named: SILImageFilterOnSelected)!.withRenderingMode(.alwaysOriginal), for: .selected) + } + + func changeImagesOfSortButtonFor(option: SILSortOption) { + switch option { + case .none: + sortButton?.setImage(UIImage(named: SILImageSortOff)!.withRenderingMode(.alwaysOriginal), for: .normal) + sortButton?.setImage(UIImage(named: SILImageSortOn)!.withRenderingMode(.alwaysOriginal), for: .selected) + case .ascendingRSSI: + sortButton?.setImage(UIImage(named: SILImageSortAscendingOff)!.withRenderingMode(.alwaysOriginal), for: .normal) + sortButton?.setImage(UIImage(named: SILImageSortAscendingOn)!.withRenderingMode(.alwaysOriginal), for: .selected) + case .descendingRSSI: + sortButton?.setImage(UIImage(named: SILImageSortDescendingOff)!.withRenderingMode(.alwaysOriginal), for: .normal) + sortButton?.setImage(UIImage(named: SILImageSortDescendingOn)!.withRenderingMode(.alwaysOriginal), for: .selected) + case .AZ: + sortButton?.setImage(UIImage(named: SILImageSortAZOff)!.withRenderingMode(.alwaysOriginal), for: .normal) + sortButton?.setImage(UIImage(named: SILImageSortAZOn)!.withRenderingMode(.alwaysOriginal), for: .selected) + case .ZA: + sortButton?.setImage(UIImage(named: SILImageSortZAOff)!.withRenderingMode(.alwaysOriginal), for: .normal) + sortButton?.setImage(UIImage(named: SILImageSortZAOn)!.withRenderingMode(.alwaysOriginal), for: .selected) + } + } + + // MARK: TappedActions + + func logButtonWasTappedAction() -> SILBrowserLogViewController? { + var logVC: SILBrowserLogViewController? = nil + + if !logButton!.isSelected { + let storyboard = UIStoryboard(name: SILAppBluetoothBrowserHome, bundle: nil) + logVC = storyboard.instantiateViewController(withIdentifier: SILSceneLog) as? SILBrowserLogViewController + handleButtonSelect(self.logButton!, andViewController: logVC!) + } else { + prepareSceneForRemoveExpandingController() + } + + return logVC; + } + + func connectionsButtonWasTappedAction() -> SILBrowserConnectionsViewController? { + var connectionVC: SILBrowserConnectionsViewController? = nil + + if !connectionsButton!.isSelected { + let storyboard = UIStoryboard(name: SILAppBluetoothBrowserHome, bundle: nil) + connectionVC = storyboard.instantiateViewController(withIdentifier: SILSceneConnections) as? SILBrowserConnectionsViewController + handleButtonSelect(self.connectionsButton!, andViewController: connectionVC!) + } else { + prepareSceneForRemoveExpandingController() + } + + return connectionVC; + } + + func filterButtonWasTappedAction() -> SILBrowserFilterViewController? { + var filterVC: SILBrowserFilterViewController? = nil + + if !filterButton!.isSelected { + let storyboard = UIStoryboard(name: SILAppBluetoothBrowserHome, bundle: nil) + filterVC = storyboard.instantiateViewController(withIdentifier: SILSceneFilter) as? SILBrowserFilterViewController + handleButtonSelect(self.filterButton!, andViewController: filterVC!) + } else { + prepareSceneForRemoveExpandingController() + } + + return filterVC; + } + + func sortButtonWasTappedAction() -> SILSortViewController? { + var sortVC: SILSortViewController? = nil + + if !sortButton!.isSelected { + let storyboard = UIStoryboard(name: SILAppBluetoothBrowserHome, bundle: nil) + sortVC = storyboard.instantiateViewController(withIdentifier: SILSceneSort) as? SILSortViewController + handleButtonSelect(self.sortButton!, andViewController: sortVC!) + } else { + prepareSceneForRemoveExpandingController() + } + + return sortVC; + } + + private func handleButtonSelect(_ button: UIButton, andViewController vc: UIViewController, wasSortButtonSelected: Bool = false) { + let wasAnyButtonSelected = isAnyButtonSelected() + let wasSortButtonSelected = sortButton?.isSelected ?? false + let willBeSortButtonSelected = button.isEqual(self.sortButton) + deselectAllButtons() + button.isSelected = true + prepareSceneDependOnButtonSelection(wasAnyButtonSelected) + + insertIntoContainerExpandableController(vc) + let isNeededAnimate = wasSortButtonSelected || !wasAnyButtonSelected || willBeSortButtonSelected; + animateExpandableViewController(isNeeded: isNeededAnimate) + self.expandingViewController = vc; + } + + // MARK: Public appearance methods + + func updateConnectionsButtonTitle(_ connections: UInt) { + let connectionText = "\(connections) Connections" + connectionsButton?.setTitle(connectionText, for: .normal) + connectionsButton?.setTitle(connectionText, for: .selected) + } + + func removeExpandingControllerIfNeeded() { + prepareSceneForRemoveExpandingController() + } + + // MARK: Private appearance methods + + private func isAnyButtonSelected() -> Bool { + return usedButtons.reduce(false) { $0 || $1.isSelected } + } + + private func deselectAllButtons() { + usedButtons.forEach{ $0.isSelected = false } + } + + private func prepareSceneDependOnButtonSelection(_ wasAnyButtonSelected: Bool) { + if wasAnyButtonSelected { + prepareSceneForChangeExpandableView() + } else { + prepareSceneForExpandableView() + } + customizeExpandableViewAppearance() + } + + private func insertIntoContainerExpandableController(_ viewController: UIViewController) { + browserViewController!.addChild(viewController) + expandableControllerView!.addSubview(viewController.view) + viewController.view.frame = self.expandableControllerView!.frame + viewController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth] + viewController.didMove(toParent: self.browserViewController!) + browserViewController!.view.setNeedsUpdateConstraints() + } + + private func prepareSceneForChangeExpandableView() { + removeExpandableViewController() + } + + private func prepareSceneForExpandableView() { + if let _ = self.expandingViewController { + prepareSceneForRemoveExpandingController() + } + hideFilterBar() + attachBlurEffectView() + } + + private func customizeExpandableViewAppearance() { + self.cornerRadius = 20.0; + if sortButton?.isSelected ?? false { + let sortVM = SILSortViewModel._sharedInstance + expandableControllerHeight!.constant = sortVM.getViewControllerHeight() + return + } + self.expandableControllerHeight!.constant = self.presentationView!.frame.size.height * 0.9; + } + + private func customizeSceneWithoutExpandableViewContoller() { + self.cornerRadius = 0.0; + self.expandableControllerHeight!.constant = CollapsedViewHeight; + } + + private func removeExpandableViewController() { + browserViewController?.willMove(toParent: nil) + expandingViewController?.view.removeFromSuperview() + expandingViewController?.removeFromParent() + expandingViewController = nil + } + + private func prepareSceneForRemoveExpandingController() { + deselectAllButtons() + removeExpandableViewController() + customizeSceneWithoutExpandableViewContoller() + animateExpandableViewController() + removeBlurEffectView() + restoreFilterBar() + } + + private func restoreFilterBar() { + if let filterBarViewController = self.filterBarViewController, + !filterBarViewController.isEmpty() { + filterBarViewController.restore() + self.filterBarHeight?.isActive = false + } + } + + private func attachBlurEffectView() { + self.effectView.frame = self.presentationView!.frame; + self.discoveredDevicesView?.addSubview(self.effectView) + } + + private func removeBlurEffectView() { + self.effectView.removeFromSuperview() + } + + private func adjustView() { + if let expandableView = self.expandableControllerView { + expandableView.layer.cornerRadius = self.cornerRadius + expandableView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] + expandableView.clipsToBounds = true + } + } + + private func animateExpandableViewController(isNeeded: Bool = true) { + if isNeeded { + UIView.animate(withDuration: TimeInterval(AnimationExpandableControllerTime), delay: TimeInterval(AnimationExpandableControllerDelay), options: [.curveLinear], animations: { self.browserViewController?.view.layoutIfNeeded() + }, completion: nil) + } + } +} diff --git a/SiliconLabsApp/ViewControllers/ConnectedLightingApp/SILConnectedLightingViewController.m b/SiliconLabsApp/ViewControllers/ConnectedLightingApp/SILConnectedLightingViewController.m index 28fac536..e9c1de2d 100644 --- a/SiliconLabsApp/ViewControllers/ConnectedLightingApp/SILConnectedLightingViewController.m +++ b/SiliconLabsApp/ViewControllers/ConnectedLightingApp/SILConnectedLightingViewController.m @@ -27,8 +27,8 @@ typedef NS_ENUM(int, SILSwitchSource) { #define IS_IOS10_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) -NSString * const SILLightEventOn = @"Last Event: Light ON"; -NSString * const SILLightEventOff = @"Last Event: Light OFF"; +NSString * const SILLightEventOn = @"Light On"; +NSString * const SILLightEventOff = @"Light Off"; @interface SILConnectedLightingViewController () { NSTimer *scheduleReadTimer; @@ -42,10 +42,14 @@ @interface SILConnectedLightingViewController () - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugCharacteristicTableViewCell.h b/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugCharacteristicTableViewCell.h index 71c021ad..878d0b47 100644 --- a/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugCharacteristicTableViewCell.h +++ b/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugCharacteristicTableViewCell.h @@ -28,6 +28,7 @@ @interface SILDebugCharacteristicTableViewCell : UITableViewCell @property (weak, nonatomic) id delegate; +@property (weak, nonatomic) id descriptorDelegate; @property (weak, nonatomic) SILCharacteristicTableModel *characteristicTableModel; @property (weak, nonatomic) IBOutlet UIButton *nameEditButton; diff --git a/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugCharacteristicTableViewCell.m b/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugCharacteristicTableViewCell.m index 8c065a74..c7f3e11f 100644 --- a/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugCharacteristicTableViewCell.m +++ b/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugCharacteristicTableViewCell.m @@ -18,19 +18,20 @@ #import "SILHomeKitCharacteristicTableModel.h" #endif -@interface SILDebugCharacteristicTableViewCell() +@interface SILDebugCharacteristicTableViewCell() @property (weak, nonatomic) IBOutlet UIView *topSeparatorView; @property (weak, nonatomic) IBOutlet UILabel *characteristicNameLabel; @property (weak, nonatomic) IBOutlet UILabel *characteristicUuidLabel; -@property (weak, nonatomic) IBOutlet UILabel *descriptorsTextLabel; @property (weak, nonatomic) IBOutlet UIView *descriptorsView; @property (weak, nonatomic) IBOutlet UIButton *readPropertyButton; @property (weak, nonatomic) IBOutlet UIButton *writePropertyButton; @property (weak, nonatomic) IBOutlet UIButton *notifyPropertyButton; @property (weak, nonatomic) IBOutlet UIButton *indicatePropertyButton; +@property (weak, nonatomic) IBOutlet UITableView *descriptorsTable; @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray * allPropertyViews; @property (strong, nonatomic) NSArray *allActiveProperties; +@property (strong, nonatomic) NSArray* descriptorModels; @property (strong, nonatomic) CBCharacteristic *characteristic; @@ -42,6 +43,14 @@ - (void)awakeFromNib { [super awakeFromNib]; self.selectionStyle = UITableViewCellSelectionStyleNone; [self addGestureRecognizerForCharacteristicNameLabel]; + [self setupDescriptorTable]; +} + +- (void)setupDescriptorTable { + self.descriptorModels = @[]; + self.descriptorsTable.dataSource = self; + self.descriptorsTable.delegate = self; + [self.descriptorsTable invalidateIntrinsicContentSize]; } - (void)configureWithCharacteristicModel:(SILCharacteristicTableModel *)characteristicModel { @@ -50,12 +59,15 @@ - (void)configureWithCharacteristicModel:(SILCharacteristicTableModel *)characte [self.nameEditButton setHidden:!characteristicModel.isMappable]; self.characteristicNameLabel.text = [characteristicModel name]; self.characteristicUuidLabel.text = [characteristicModel hexUuidString] ?: EmptyText; - if (characteristicModel.descriptorModels.count == 0) { + self.descriptorModels = characteristicModel.descriptorModels; + if (self.descriptorModels.count == 0) { [self.descriptorsView setHidden:YES]; } else { [self.descriptorsView setHidden:NO]; - self.descriptorsTextLabel.text = [self getDescriptorsText:characteristicModel.descriptorModels]; } + + [self.descriptorsTable reloadData]; + self.topSeparatorView.hidden = characteristicModel.hideTopSeparator; self.allActiveProperties = [SILDebugProperty getActivePropertiesFrom:characteristicModel.characteristic.properties]; [self configurePropertyViewsForProperties:self.allActiveProperties]; @@ -63,18 +75,6 @@ - (void)configureWithCharacteristicModel:(SILCharacteristicTableModel *)characte [self layoutIfNeeded]; } -- (NSString*)getDescriptorsText:(NSArray*)descriptorsModel { - NSMutableString* text = [[NSMutableString alloc] initWithString:EmptyText]; - for (SILDescriptorTableModel* descriptor in descriptorsModel) { - [text appendString:[[NSString alloc] initWithFormat:@"%@", descriptor.descriptor.UUID]]; - [text appendString:@" (UUID: "]; - [text appendString:[[NSString alloc] initWithFormat:@"%@", descriptor.hexUuidString]]; - [text appendString:@")\n"]; - } - - return [[NSString alloc] initWithString:text]; -} - - (void)addGestureRecognizerForCharacteristicNameLabel { UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(characteristicNameLabelWasTapped)]; [self.characteristicNameLabel setUserInteractionEnabled:YES]; @@ -147,14 +147,14 @@ - (void)readButtonAppearance { NSString *readImageString = SILImageNamePropertyReadDisabled; self.readPropertyButton.imageView.contentMode = UIViewContentModeScaleAspectFit; [self.readPropertyButton setImage:[UIImage imageNamed:readImageString] forState:UIControlStateNormal]; - [self.readPropertyButton setTitleColor:[UIColor sil_primaryTextColor] forState:UIControlStateNormal]; + [self.readPropertyButton setTitleColor:[UIColor sil_boulderColor] forState:UIControlStateNormal]; } - (void)writeButtonAppearance { NSString *writeImageString = SILImageNamePropertyWriteDisabled; self.writePropertyButton.imageView.contentMode = UIViewContentModeScaleAspectFit; [self.writePropertyButton setImage:[UIImage imageNamed:writeImageString] forState:UIControlStateNormal]; - [self.writePropertyButton setTitleColor:[UIColor sil_primaryTextColor] forState:UIControlStateNormal]; + [self.writePropertyButton setTitleColor:[UIColor sil_boulderColor] forState:UIControlStateNormal]; } @@ -165,7 +165,7 @@ - (void)indicateButtonAppearanceWithCondition:(BOOL)condition { if (condition) { [self.indicatePropertyButton setTitleColor:[UIColor sil_regularBlueColor] forState:UIControlStateNormal]; } else { - [self.indicatePropertyButton setTitleColor:[UIColor sil_primaryTextColor] forState:UIControlStateNormal]; + [self.indicatePropertyButton setTitleColor:[UIColor sil_boulderColor] forState:UIControlStateNormal]; } } @@ -176,7 +176,7 @@ - (void)notifyButtonAppearanceWithCondition:(BOOL)condition { if (condition) { [self.notifyPropertyButton setTitleColor:[UIColor sil_regularBlueColor] forState:UIControlStateNormal]; } else { - [self.notifyPropertyButton setTitleColor:[UIColor sil_primaryTextColor] forState:UIControlStateNormal]; + [self.notifyPropertyButton setTitleColor:[UIColor sil_boulderColor] forState:UIControlStateNormal]; } } @@ -216,4 +216,18 @@ - (IBAction)editName:(UIButton *)sender { } } +// MARK: UITableViewDataSource + +- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.descriptorModels.count; +} + +- (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { + SILDescriptorTableViewCell* descriptorCell = [tableView dequeueReusableCellWithIdentifier:@"SILDescriptorTableViewCell" forIndexPath:indexPath]; + SILDescriptorTableModel* descriptor = self.descriptorModels[indexPath.row]; + [descriptorCell configureCellWithDescriptor:descriptor]; + descriptorCell.delegate = self.descriptorDelegate; + return descriptorCell; +} + @end diff --git a/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugServicesViewController.m b/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugServicesViewController.m index dd27f9dd..dd688df4 100644 --- a/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugServicesViewController.m +++ b/SiliconLabsApp/ViewControllers/DebugApp/ServicesTable/SILDebugServicesViewController.m @@ -46,7 +46,6 @@ #import "UIImage+SILImages.h" #import "SILBrowserConnectionsViewModel.h" #import "NSString+SILBrowserNotifications.h" -#import "SILBluetoothBrowserExpandableViewManager.h" #import "SILBluetoothBrowser+Constants.h" #import "SILRefreshImageView.h" #import "SILRefreshImageModel.h" @@ -59,7 +58,7 @@ static float kTableRefreshInterval = 1; -@interface SILDebugServicesViewController () +@interface SILDebugServicesViewController () @property (weak, nonatomic) IBOutlet UILabel *deviceNameLabel; @property (weak, nonatomic) IBOutlet UILabel *rssiLabel; @@ -126,6 +125,7 @@ - (void)viewWillAppear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self dismissPopoverIfExist]; + [self.browserExpandableViewManager removeExpandingControllerIfNeeded]; [[NSNotificationCenter defaultCenter] removeObserver:self]; } @@ -247,6 +247,7 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { menuVC.delegate = self; [menuVC addMenuOptionWithTitle:@"OTA DFU" completion:^{ [self performOTAAction]; + [self.browserExpandableViewManager removeExpandingControllerIfNeeded]; }]; self.menuOptionHeight.constant = [menuVC getMenuOptionHeight]; } @@ -427,7 +428,15 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath } } +- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { + return [self heightForRowAtIndexPath:indexPath]; +} + - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return [self heightForRowAtIndexPath:indexPath]; +} + +- (CGFloat)heightForRowAtIndexPath:(NSIndexPath *)indexPath { if ([self.modelsToDisplay[indexPath.row] isEqual:kSpacerCellIdentifieer]) { return 24.0; } @@ -444,7 +453,15 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa if (descriptors == 0) { return 107.0; } else { - return 107.0 + (descriptors + 1) * 18.0; + CGFloat tableHeight = 0.0; + + for (SILDescriptorTableModel * model in modelCharacteristic.descriptorModels) { + CGSize size = CGSizeMake(self.tableView.bounds.size.width - 120, CGFLOAT_MAX); + CGRect rect = [[model getAttributedDescriptor] boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin context:nil]; + tableHeight += ceil(rect.size.height); + } + + return 130 + tableHeight; } } else { if ([model isKindOfClass:[SILEncodingPseudoFieldRowModel class]]) { @@ -483,6 +500,8 @@ - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)ce } cell.contentView.backgroundColor = UIColor.whiteColor; cell.clipsToBounds = NO; + [cell setNeedsLayout]; + [cell layoutIfNeeded]; } -(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { @@ -509,7 +528,8 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView { self.headerView.frame = rect; if (scrollView.contentOffset.y < 0) { scrollView.contentOffset = CGPointZero; - [scrollView setScrollEnabled:NO]; + } else { + [self.tableView setScrollEnabled:YES]; } } @@ -529,8 +549,9 @@ - (SILServiceCell *)serviceCellWithModel:(SILServiceTableModel *)serviceTableMod - (SILDebugCharacteristicTableViewCell *)characteristicCellWithModel:(SILCharacteristicTableModel *)characteristicTableModel forTable:(UITableView *)tableView { SILDebugCharacteristicTableViewCell *characteristicCell = (SILDebugCharacteristicTableViewCell *)[tableView dequeueReusableCellWithIdentifier:NSStringFromClass([SILDebugCharacteristicTableViewCell class])]; - [characteristicCell configureWithCharacteristicModel:characteristicTableModel]; characteristicCell.delegate = self; + characteristicCell.descriptorDelegate = self; + [characteristicCell configureWithCharacteristicModel:characteristicTableModel]; [characteristicCell.nameEditButton setHidden:!characteristicTableModel.isMappable]; return characteristicCell; } @@ -728,6 +749,13 @@ - (void)cell:(SILDebugCharacteristicTableViewCell *)cell didRequestIndicateForCh [self.peripheral setNotifyValue:value forCharacteristic:characteristic]; } +#pragma mark = SILDescriptorsTableViewCellDelegate + +- (void)cellDidRequestReadForDescriptor:(CBDescriptor *)descriptor { + [self.peripheral readValueForDescriptor:descriptor]; + [self refreshTable]; +} + #pragma mark - CBPeripheralDelegate - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { @@ -777,22 +805,27 @@ - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(C [CrashlyticsKit setObjectValue:peripheral.name forKey:@"peripheral"]; [self addOrUpdateModelForCharacteristic:characteristic forService:characteristic.service]; [self refreshTable]; - [self postRegisterLogNotification:[SILLogDataModel prepareLogDescription:@"didUpdateValueForCharacteristic: " andCharacteristic:characteristic andPeripheral:peripheral andError:error]]; + [self postRegisterLogNotification:[SILLogDataModel prepareLogDescriptionForUpdateValueOfCharacteristic:characteristic andPeripheral:peripheral andError:error]]; } - (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { + BOOL displayLog = NO; if (error != nil) { if ([self isATTError:error]) { [self showErrorDetailsPopoupWithError:error]; } + displayLog = YES; } else { for (CBDescriptor *descriptor in characteristic.descriptors) { - [self addOrUpdateModelForDescriptor:descriptor forCharacteristic:characteristic]; + BOOL wasAddedDescriptor = [self addOrUpdateModelForDescriptor:descriptor forCharacteristic:characteristic]; + displayLog = displayLog || wasAddedDescriptor; } [self markTableForUpdate]; } - - [self postRegisterLogNotification:[SILLogDataModel prepareLogDescription:@"didDiscoverDescriptorsForCharacteristic: " andCharacteristic:characteristic andPeripheral:peripheral andError:error]]; + + if (displayLog) { + [self postRegisterLogNotification:[SILLogDataModel prepareLogDescription:@"didDiscoverDescriptorsForCharacteristic: " andCharacteristic:characteristic andPeripheral:peripheral andError:error]]; + } } - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error { diff --git a/SiliconLabsApp/ViewControllers/Descriptors/SILDescriptorTableViewCell.swift b/SiliconLabsApp/ViewControllers/Descriptors/SILDescriptorTableViewCell.swift new file mode 100644 index 00000000..420b7447 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/Descriptors/SILDescriptorTableViewCell.swift @@ -0,0 +1,34 @@ +// +// SILDescriptorsTableViewCell.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 25/11/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import UIKit + +@objc +protocol SILDescriptorTableViewCellDelegate { + func cellDidRequestReadForDescriptor(_ descriptor: CBDescriptor?) +} + +@objc +@objcMembers +class SILDescriptorTableViewCell: UITableViewCell { + + @IBOutlet weak var descriptorLabel: UILabel! + var delegate: SILDescriptorTableViewCellDelegate? + private var descriptorModel: SILDescriptorTableModel! + + @objc func configureCellWithDescriptor(_ descriptorModel: SILDescriptorTableModel) { + self.descriptorModel = descriptorModel + descriptorLabel.attributedText = descriptorModel.getAttributedDescriptor() + } + + @IBAction func handleReadDescriptorTap(_ sender: Any) { + descriptorLabel.attributedText = NSAttributedString(string: "") + descriptorModel.shouldReadValue = true + delegate?.cellDidRequestReadForDescriptor(descriptorModel.descriptor) + } +} diff --git a/SiliconLabsApp/ViewControllers/HealthThermometerApp/SILHealthThermometerAppViewController.m b/SiliconLabsApp/ViewControllers/HealthThermometerApp/SILHealthThermometerAppViewController.m index dde5201f..a13f8a87 100644 --- a/SiliconLabsApp/ViewControllers/HealthThermometerApp/SILHealthThermometerAppViewController.m +++ b/SiliconLabsApp/ViewControllers/HealthThermometerApp/SILHealthThermometerAppViewController.m @@ -14,7 +14,7 @@ #import #import "SILCollectionViewRightAlignedFlowLayout.h" #import "UIColor+SILColors.h" -#import "SILSegmentedControl.h" +#import "SILThermometerSegmentedControl.h" #import "SILDeviceSelectionViewController.h" #import "WYPopoverController.h" #import "WYPopoverController+SILHelpers.h" @@ -43,7 +43,7 @@ @interface SILHealthThermometerAppViewController () () -> T? { diff --git a/SiliconLabsApp/ViewControllers/RangeTestApp/Peripheral/SILRangeTestPeripheral.swift b/SiliconLabsApp/ViewControllers/RangeTestApp/Peripheral/SILRangeTestPeripheral.swift index b49e980b..2b3dfdd6 100644 --- a/SiliconLabsApp/ViewControllers/RangeTestApp/Peripheral/SILRangeTestPeripheral.swift +++ b/SiliconLabsApp/ViewControllers/RangeTestApp/Peripheral/SILRangeTestPeripheral.swift @@ -9,9 +9,10 @@ import UIKit //MARK: - Peripheral delegate -protocol SILRangeTestPeripheralDelegate { +protocol SILRangeTestPeripheralDelegate: class { func didUpdate(connectionState: CBPeripheralState) func didUpdate(manufacturerData: SILRangeTestManufacturerData?) + func bluetoothIsDisabled() } // MARK: - SILRangeTestPeripheral implementation @@ -28,7 +29,7 @@ class SILRangeTestPeripheral: NSObject { private var characteristics: Characteristics = [:] private var alreadyDiscoveredCharacteristics: RawCharacteristics = [:] - var delegate: SILRangeTestPeripheralDelegate? = nil + weak var delegate: SILRangeTestPeripheralDelegate? = nil private(set) var manufacturerData: SILRangeTestManufacturerData? = nil var state: CBPeripheralState { get { @@ -73,7 +74,7 @@ class SILRangeTestPeripheral: NSObject { var characteristics: Characteristics = [:] for (characteristic, _) in SILRangeTestCharacteristic.deviceInformationServiceCharacteristics { - let rtChar = SILRangeTestCharacteristic(characteristic, forPeripheral: self) + let rtChar = SILRangeTestCharacteristic(characteristic) if characteristics[rtChar.serviceUuid] == nil { characteristics[rtChar.serviceUuid] = [:] @@ -83,7 +84,7 @@ class SILRangeTestPeripheral: NSObject { } for (characteristic, _) in SILRangeTestCharacteristic.rangeTestServiceCharacteristics { - let rtChar = SILRangeTestCharacteristic(characteristic, forPeripheral: self) + let rtChar = SILRangeTestCharacteristic(characteristic) if characteristics[rtChar.serviceUuid] == nil { characteristics[rtChar.serviceUuid] = [:] @@ -141,30 +142,21 @@ class SILRangeTestPeripheral: NSObject { // MARK: - Connecting extension SILRangeTestPeripheral { private func registerForConnectingNotifications() { - NotificationCenter.default.addObserver(forName: NSNotification.Name.SILCentralManagerDidConnectPeripheral, - object: nil, - queue: nil, - using: didConnectPeripheral) - NotificationCenter.default.addObserver(forName: NSNotification.Name.SILCentralManagerDidFailToConnectPeripheral, - object: nil, - queue: nil, - using: didFailToConnectPeripheral) - NotificationCenter.default.addObserver(forName: NSNotification.Name.SILCentralManagerDidDisconnectPeripheral, - object: nil, - queue: nil, - using: didDisconnectPeripheral) - } - - private func didConnectPeripheral(_ notification: Notification) { - delegate?.didUpdate(connectionState: state) + NotificationCenter.default.addObserver(self, selector: #selector(notifyConnectionStateUpdate(_:)), name: NSNotification.Name.SILCentralManagerDidConnectPeripheral, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(notifyConnectionStateUpdate(_:)), name: NSNotification.Name.SILCentralManagerDidFailToConnectPeripheral, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(notifyConnectionStateUpdate(_:)), name: NSNotification.Name.SILCentralManagerDidDisconnectPeripheral, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(bluetoothIsDisabled(_:)), name: NSNotification.Name.SILCentralManagerBluetoothDisabled, object: nil) } - private func didFailToConnectPeripheral(_ notification: Notification) { + @objc private func notifyConnectionStateUpdate(_ notification: Notification) { delegate?.didUpdate(connectionState: state) } - private func didDisconnectPeripheral(_ notification: Notification) { - delegate?.didUpdate(connectionState: state) + @objc private func bluetoothIsDisabled(_ notification: Notification) { + delegate?.bluetoothIsDisabled() } } diff --git a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppContainerViewController.swift b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppContainerViewController.swift new file mode 100644 index 00000000..720e9343 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppContainerViewController.swift @@ -0,0 +1,67 @@ +// +// SILRangeTestAppContainerViewController.swift +// SiliconLabsApp +// +// Created by Michał Lenart on 26/11/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import UIKit + +struct SILSetTabDeviceName { + let invoke: (String) -> Void +} + +class SILRangeTestAppContainerViewController: UIViewController, UITabBarControllerDelegate { + @IBOutlet weak var navigationBar: UIView! + @IBOutlet weak var tabSelection: UISegmentedControl! + + var tabController: UITabBarController! + + override func viewDidLoad() { + super.viewDidLoad() + + navigationBar.addShadow() + navigationBar.superview?.bringSubviewToFront(navigationBar) + observeForBluetoothDisabledNotification() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name.SILCentralManagerBluetoothDisabled, object: nil) + } + + func observeForBluetoothDisabledNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(bluetoothIsDisabled(_:)), name: NSNotification.Name.SILCentralManagerBluetoothDisabled, object: nil) + } + + @objc func bluetoothIsDisabled(_ notification: Notification) { + let bluetoothDisabledAlert = SILBluetoothDisabledAlert.rangeTest + self.alertWithOKButton(title: bluetoothDisabledAlert.title, + message: bluetoothDisabledAlert.message, + completion: { [weak self] _ in self?.navigationController?.popToRootViewController(animated: true) + }) + } + + @IBAction func backButtonTapped(_ sender: Any) { + self.navigationController?.popViewController(animated: true) + } + + @IBAction func didSelectTab(_ sender: Any) { + tabController?.selectedIndex = tabSelection.selectedSegmentIndex + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if (segue.identifier == "ShowTabBarController") { + tabController = (segue.destination as! UITabBarController) + + for i in tabController.viewControllers!.indices { + let context = SILSetTabDeviceName(invoke: { [weak self] (name: String) in + self?.tabSelection.setTitle(name, forSegmentAt: i) + }) + tabController.viewControllers?[i].sil_provideContext(type: SILSetTabDeviceName.self, value: context) + } + + } + } +} diff --git a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppViewController.swift b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppViewController.swift index 2b7d618c..96eadf92 100644 --- a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppViewController.swift +++ b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppViewController.swift @@ -14,16 +14,14 @@ import Charts fileprivate struct Constants { static let sliderGrey = UIColor(red:0.85, green:0.84, blue:0.84, alpha:1.00) static let sliderRed = UIColor(red:0.84, green:0.14, blue:0.19, alpha:1.00) + static let sliderBlue = UIColor.sil_regularBlue()! - static let buttonEnabledBackgroundColor = UIColor(hexString: "#D0021B")! + static let buttonEnabledStopBackgroundColor = UIColor(hexString: "#D0021B")! + static let buttonEnabledStartBackgroundColor = UIColor.sil_regularBlue()! static let buttonDisabledBackgroundColor = UIColor(hexString: "#CCCBCB")! static let demoName = "Range Test Demo" static let modePostfix = "Mode" - static let sliderColors = [ - Constants.sliderGrey, - Constants.sliderRed - ] static let textColor = UIColor(hexString: "#504E4E")! static let disabledBackgroundColor = UIColor(hexString: "#D5D5D5")! @@ -38,15 +36,16 @@ class SILRangeTestAppViewController: UIViewController { var app: SILApp! var viewModel: SILRangeTestAppViewModel! + @IBOutlet weak var contentView: UIView! @IBOutlet private weak var txViewContainer: UIView! @IBOutlet private weak var rxViewContainer: UIView! @IBOutlet private weak var chartView: LineChartView! @IBOutlet private weak var deviceNameLabel: UILabel! @IBOutlet private weak var modelNumberLabel: UILabel! - @IBOutlet private weak var executeButton: UIButton! + @IBOutlet private weak var executeButton: SILPrimaryButton! @IBOutlet private weak var txPowerRowView: UIView! - @IBOutlet private weak var packetRepeatSwitch: UISwitch! - @IBOutlet private weak var uartLogSwitch: UISwitch! + @IBOutlet weak var packetRepeatSwitch: SILSwitch! + @IBOutlet weak var uartLogSwitch: SILSwitch! @IBOutlet private weak var rxLabel: UILabel! @IBOutlet private weak var txLabel: UILabel! @@ -54,9 +53,7 @@ class SILRangeTestAppViewController: UIViewController { @IBOutlet private weak var maLabel: UILabel! @IBOutlet private weak var perLabel: UILabel! - @IBOutlet private var minimumSliderValueLabels: [UILabel]! - @IBOutlet private var maximumSliderValueLabels: [UILabel]! - @IBOutlet private var sliders: [GradientSlider]! + @IBOutlet private var sliders: [UISlider]! @IBOutlet private var valuesButtons: [UIButton]! @IBOutlet private var interactableViews: [UIControl]! @IBOutlet private var grayLabels: [UILabel]! @@ -69,6 +66,7 @@ class SILRangeTestAppViewController: UIViewController { viewModel.delegate = self + setTabDeviceName() prepareUI() prepareUIValues() prepareUIWithPeripheral() @@ -88,7 +86,11 @@ class SILRangeTestAppViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - self.navigationController?.popViewController(animated: false) + } + + private func setTabDeviceName() { + let setDeviceName = self.sil_useContext(type: SILSetTabDeviceName.self) + setDeviceName?.invoke(viewModel.peripheral.discoveredPeripheral()?.advertisedLocalName ?? "Unknown") } private func prepareUI() { @@ -103,8 +105,8 @@ class SILRangeTestAppViewController: UIViewController { rxViewContainer.isHidden = !isRxMode for slider in sliders { - slider.colors = Constants.sliderColors - slider.setThumbImage(enabledKnobImage, for: .normal) + slider.minimumTrackTintColor = Constants.sliderBlue + slider.thumbTintColor = Constants.sliderBlue } for valueButton in valuesButtons { @@ -121,6 +123,20 @@ class SILRangeTestAppViewController: UIViewController { if isRxMode { chartConfigure() } + + contentView.layer.cornerRadius = 16; + contentView.layer.shadowColor = UIColor.black.cgColor; + contentView.layer.shadowOpacity = 0.3; + contentView.layer.shadowOffset = CGSize.zero; + contentView.layer.shadowRadius = 2; + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews(); + + DispatchQueue.main.async { + self.contentView.layer.shadowPath = UIBezierPath(roundedRect: self.contentView.bounds, cornerRadius: 16.0).cgPath + } } private func prepareUIValues() { @@ -132,13 +148,9 @@ class SILRangeTestAppViewController: UIViewController { for setting in viewModel.getAllAvailableSettings() { let availableValues = viewModel.getAvailableValues(forSetting: setting) let slider = getSlider(forSetting: setting) - let minimumValueLabel = getSliderMinimumValueLabel(forSetting: setting) - let maximumValueLabel = getSliderMaximumValueLabel(forSetting: setting) slider?.minimumValue = 0 slider?.maximumValue = Float(availableValues.count - 1) - minimumValueLabel?.text = String(format: "%g", availableValues.first!) - maximumValueLabel?.text = String(format: "%g", availableValues.last!) updateUI(forSetting: setting) } @@ -182,25 +194,14 @@ class SILRangeTestAppViewController: UIViewController { let setting = SILRangeTestSetting(rawValue: 0x1 << sender.tag)! let availableValues = viewModel.getAvailableValues(forSetting: setting) let availableStringValues = viewModel.getAvailableStringValues(forSetting: setting) - let currentValue = sender.title(for: .normal) ?? "0" - let currentValueIdx = availableStringValues.index(of: currentValue) ?? 0 - let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil) - let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: nil, action: nil) - - let picker = ActionSheetStringPicker(title: viewModel.getTitle(forSetting: setting), - rows: availableStringValues, - initialSelection: currentValueIdx, - doneBlock: { (picker, idx, value) in - self.updateModel(setting: setting, withValue: availableValues[idx]) - }, - cancel: nil, - origin: sender) - doneButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: Constants.sliderRed], for: .normal) - cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: Constants.sliderRed], for: .normal) - picker?.setDoneButton(doneButton) - picker?.setCancelButton(cancelButton) - picker?.toolbarBackgroundColor = .white - picker?.show() + + let options: [ContextMenuOption] = availableStringValues.enumerated().map { (index, value) in + return ContextMenuOption(title: value) { + self.updateModel(setting: setting, withValue: availableValues[index]) + } + } + + SILContextMenu.present(owner: self, sourceView: sender, options: options) } @IBAction func sliderValueChanged(_ sender: UISlider) { @@ -219,7 +220,7 @@ class SILRangeTestAppViewController: UIViewController { updateModel(setting: setting, withValue: availableValues[valueIdx], shouldUpdatePeripheral: updatePeriperal) } - @IBAction func switchValueChanged(_ sender: UISwitch) { + @IBAction func switchValueChanged(_ sender: SILSwitch) { if sender == packetRepeatSwitch { viewModel.isPacketRepeatEnabled = packetRepeatSwitch.isOn } else if sender == uartLogSwitch { @@ -240,26 +241,17 @@ class SILRangeTestAppViewController: UIViewController { let stringValue = viewModel.getStringValue(forSetting: setting) let availableValues = viewModel.getAvailableValues(forSetting: setting) - guard let minValue = availableValues.first, - let maxValue = availableValues.last, - let valueIdx = availableValues.index(of: value) else { + guard let valueIdx = availableValues.index(of: value) else { return } let button = getButton(forSetting: setting) let slider = getSlider(forSetting: setting) - let minimumValueLabel = getSliderMinimumValueLabel(forSetting: setting) - let maximumValueLabel = getSliderMaximumValueLabel(forSetting: setting) - let minValueText = String(format: "%g", minValue) - let maxValueText = String(format: "%g", maxValue) let sliderMaxValue = Float(availableValues.count - 1) UIView.setAnimationsEnabled(false) - if minimumValueLabel?.text != minValueText { minimumValueLabel?.text = minValueText } - if maximumValueLabel?.text != maxValueText { maximumValueLabel?.text = maxValueText } - if slider?.maximumValue != sliderMaxValue { slider?.maximumValue = sliderMaxValue } slider?.setValue(Float(valueIdx), animated: false) slider?.setNeedsDisplay() @@ -273,17 +265,6 @@ class SILRangeTestAppViewController: UIViewController { // MARK: - Helper methods extension SILRangeTestAppViewController { - fileprivate var enabledKnobImage: UIImage? { get { - let size = CGSize(width: 24, height: 24) - UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) - let path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: size)) - Constants.sliderRed.setFill() - path.fill() - let image = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - return image - } } - private func stringify(_ mode: SILRangeTestMode) -> String { switch mode { case .RX: @@ -301,14 +282,6 @@ extension SILRangeTestAppViewController { return sliders.first { (0x1 << $0.tag) == setting.rawValue } } - private func getSliderMinimumValueLabel(forSetting setting: SILRangeTestSetting) -> UILabel? { - return minimumSliderValueLabels.first { (0x1 << $0.tag) == setting.rawValue } - } - - private func getSliderMaximumValueLabel(forSetting setting: SILRangeTestSetting) -> UILabel? { - return maximumSliderValueLabels.first { (0x1 << $0.tag) == setting.rawValue } - } - private func getTitleForExecuteButton() -> String { if viewModel.isTestStarted && viewModel.mode == .RX { return "Waiting for device..." @@ -320,6 +293,14 @@ extension SILRangeTestAppViewController { return "\(action) \(mode)" } + private func getBackgroundColorForExecuteButton() -> UIColor { + if viewModel.isTestStarted && viewModel.mode == .RX { + return Constants.buttonDisabledBackgroundColor + } + + return viewModel.isTestStarted ? Constants.buttonEnabledStopBackgroundColor : Constants.buttonEnabledStartBackgroundColor + } + private func readOnlySettings() -> [SILRangeTestSetting] { var result = [SILRangeTestSetting]() @@ -342,6 +323,10 @@ extension SILRangeTestAppViewController { if interactableView is UIButton { interactableView.backgroundColor = isInteractable ? UIColor.clear : Constants.disabledBackgroundColor } + + if interactableView is UISlider { + interactableView.setNeedsLayout() + } } if !blocked { @@ -351,7 +336,10 @@ extension SILRangeTestAppViewController { let isExecuteButtonEnabled = (viewModel.mode == .TX && viewModel.isTestStarted) || !blocked executeButton.isEnabled = isExecuteButtonEnabled - executeButton.backgroundColor = isExecuteButtonEnabled ? Constants.buttonEnabledBackgroundColor : Constants.buttonDisabledBackgroundColor + if isExecuteButtonEnabled { + executeButton.backgroundColor = getBackgroundColorForExecuteButton() + } + view.layoutIfNeeded() } private func resetUIValues() { @@ -390,13 +378,13 @@ extension SILRangeTestAppViewController : SILRangeTestAppViewModelDelegate { let isButtonEnabled = !isPacketRepeatEnabled && !viewModel.isTestStarted && viewModel.didReceivedAllPeripheralValues let button = getButton(forSetting: .packetCount) - packetRepeatSwitch.setOn(isPacketRepeatEnabled, animated: true) + packetRepeatSwitch.isOn = isPacketRepeatEnabled button?.isEnabled = isButtonEnabled button?.backgroundColor = isButtonEnabled ? UIColor.clear : Constants.disabledBackgroundColor } func updated(isUartLogEnabled: Bool) { - uartLogSwitch.setOn(isUartLogEnabled, animated: true) + uartLogSwitch.isOn = isUartLogEnabled } func updated(rssi: Int) { @@ -447,15 +435,27 @@ extension SILRangeTestAppViewController : SILRangeTestAppViewModelDelegate { updateLabel(per: per) } + + func bluetoothIsDisabled() { + let bluetoothDisabledAlert = SILBluetoothDisabledAlert.rangeTest + self.alertWithOKButton(title: bluetoothDisabledAlert.title, + message: bluetoothDisabledAlert.message, + completion: { [weak self] _ in self?.navigationController?.popToRootViewController(animated: true) + }) + } } // MARK: - Update label extension SILRangeTestAppViewController { private func updateLabel(rssi: Int?) { if let rssiValue = rssi { - rssiLabel.text = String(format: "RSSI: %d dBm", rssiValue) + if rssiValue > 0 { + rssiLabel.text = String(format: "+%d dBm", rssiValue) + } else { + rssiLabel.text = String(format: "%d dBm", rssiValue) + } } else { - rssiLabel.text = "RSSI: - dBm" + rssiLabel.text = "0 dBm" } } @@ -483,25 +483,25 @@ extension SILRangeTestAppViewController { if let rxValue = rx, let totalRxValue = totalRx { self.rxValue = rxValue self.totalRxValue = totalRxValue - rxLabel.text = String(format: "RX: %d/%d", rxValue, totalRxValue) + rxLabel.text = String(format: "%d/%d", rxValue, totalRxValue) } else { - rxLabel.text = "RX: -/-" + rxLabel.text = "0/0" } } private func updateLabel(ma: Float?) { if let maValue = ma { - maLabel.text = String(format: "MA: %.1f%%", maValue) + maLabel.text = String(format: "%.1f%%", maValue) } else { - maLabel.text = "MA: -%" + maLabel.text = "0%" } } private func updateLabel(per: Float?) { if let perValue = per { - perLabel.text = String(format: "PER: %.1f%%", perValue) + perLabel.text = String(format: "%.1f%%", perValue) } else { - perLabel.text = "PER: -%" + perLabel.text = "0%" } } } @@ -542,7 +542,7 @@ extension SILRangeTestAppViewController { dataSet.drawCirclesEnabled = false dataSet.lineWidth = 0 dataSet.drawFilledEnabled = true - dataSet.fillColor = Constants.sliderRed + dataSet.fillColor = Constants.sliderBlue dataSet.fillAlpha = 1 dataSet.drawHorizontalHighlightIndicatorEnabled = false dataSet.drawVerticalHighlightIndicatorEnabled = false @@ -639,7 +639,8 @@ extension SILRangeTestAppViewController { private class YAxisValueFormatter: IAxisValueFormatter { func stringForValue(_ value: Double, axis: AxisBase?) -> String { - return String(format: "%g dBm", value) + let sign = value > 0 ? "+" : "" + return String(format: "%@%g.0 dBm", sign, value) } } diff --git a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppViewModel.swift b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppViewModel.swift index 0f84f66b..73f1e67f 100644 --- a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppViewModel.swift +++ b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppViewModel.swift @@ -8,7 +8,7 @@ import UIKit -protocol SILRangeTestAppViewModelDelegate { +protocol SILRangeTestAppViewModelDelegate: class { func didReceiveAllPeripheralValues() func updated(setting: SILRangeTestSetting) @@ -24,12 +24,14 @@ protocol SILRangeTestAppViewModelDelegate { func updated(totalTx: Int) func updated(ma: Float) func updated(per: Float) + + func bluetoothIsDisabled() } @objcMembers class SILRangeTestAppViewModel : NSObject, SILRangeTestPeripheralDelegate { private let model: [SILRangeTestSetting: SILRangeTestSettingValue] = [ - .txPower: SILRangeTestSettingValue(title: "TX Power", values: [0], stringFormat: "%gdBm"), + .txPower: SILRangeTestSettingValue(title: "TX Power", values: [0]), .payloadLength: SILRangeTestSettingValue(title: "Payload Length", values: [0]), .maWindowSize: SILRangeTestSettingValue(title: "MA Window Size", values: [0]), .channelNumber: SILRangeTestSettingValue(title: "Channel Number", values: [0]), @@ -56,7 +58,7 @@ class SILRangeTestAppViewModel : NSObject, SILRangeTestPeripheralDelegate { let peripheral: SILRangeTestPeripheral let mode: SILRangeTestMode let boardInfo: SILRangeTestBoardInfo - var delegate: SILRangeTestAppViewModelDelegate? + weak var delegate: SILRangeTestAppViewModelDelegate? var maCalculator: SILRangeTestMovingAverageCalculator? var txValueUpdater: SILRangeTestTXValueUpdater? @@ -244,6 +246,10 @@ class SILRangeTestAppViewModel : NSObject, SILRangeTestPeripheralDelegate { } + func bluetoothIsDisabled() { + delegate?.bluetoothIsDisabled() + } + private func handleMissingManufacturerData() { isTestStarted = false peripheral.connect() @@ -288,24 +294,73 @@ extension SILRangeTestAppViewModel { receivedPeripheralValues = 0 - peripheral.radioMode(callback: onReceive(radioMode:)) - peripheral.phyConfigList(callback: onReceive(phyConfigList:)) - peripheral.phyConfig(callback: onReceive(phyConfig:)) - peripheral.txPower(callback: onReceive(txPower:minValue:maxValue:)) - peripheral.payloadLength(callback: onReceive(payloadLength:minValue:maxValue:)) - peripheral.maWindowSize(callback: onReceive(maWindowSize:minValue:maxValue:)) - peripheral.channel(callback: onReceive(channel:minValue:maxValue:)) - peripheral.packetsSent(callback: onReceive(packetsSent:)) - peripheral.packetCount(callback: onReceive(packetCount:minValue:maxValue:)) - peripheral.remoteId(callback: onReceive(remoteId:minValue:maxValue:)) - peripheral.selfId(callback: onReceive(selfId:minValue:maxValue:)) - peripheral.isUartLogEnabled(callback: onReceive(isUartLogEnabled:)) - peripheral.isRunning(callback: onReceive(isRunning:)) - - peripheral.packetsCnt(callback: onReceive(totalRx:)) - peripheral.packetsReceived(callback: onReceive(rx:)) - peripheral.per(callback: onReceive(per:)) - peripheral.ma(callback: onReceive(ma:)) + peripheral.radioMode { [weak self] in + self?.onReceive(radioMode: $0) + } + + peripheral.phyConfigList { [weak self] in + self?.onReceive(phyConfigList: $0) + } + + peripheral.phyConfig { [weak self] in + self?.onReceive(phyConfig: $0) + } + + peripheral.txPower { [weak self] in + self?.onReceive(txPower: $0, minValue: $1, maxValue: $2) + } + + peripheral.payloadLength { [weak self] in + self?.onReceive(payloadLength: $0, minValue: $1, maxValue: $2) + } + + peripheral.maWindowSize { [weak self] in + self?.onReceive(maWindowSize: $0, minValue: $1, maxValue: $2) + } + + peripheral.channel { [weak self] in + self?.onReceive(channel: $0, minValue: $1, maxValue: $2) + } + + peripheral.packetsSent { [weak self] in + self?.onReceive(packetsSent: $0) + } + + peripheral.packetCount { [weak self] in + self?.onReceive(packetCount: $0, minValue: $1, maxValue: $2) + } + + peripheral.remoteId { [weak self] in + self?.onReceive(remoteId: $0, minValue: $1, maxValue: $2) + } + + peripheral.selfId { [weak self] in + self?.onReceive(selfId: $0, minValue: $1, maxValue: $2) + } + + peripheral.isUartLogEnabled { [weak self] in + self?.onReceive(isUartLogEnabled: $0) + } + + peripheral.isRunning { [weak self] in + self?.onReceive(isRunning: $0) + } + + peripheral.packetsCnt { [weak self] in + self?.onReceive(totalRx: $0) + } + + peripheral.packetsReceived { [weak self] in + self?.onReceive(rx: $0) + } + + peripheral.per { [weak self] in + self?.onReceive(per: $0) + } + + peripheral.ma { [weak self] in + self?.onReceive(ma: $0) + } } private func onReceive(radioMode: Int?) { @@ -402,7 +457,8 @@ extension SILRangeTestAppViewModel { var availableValues: [Double]? = nil if let minVal = minValue, let maxVal = maxValue { - availableValues = Array(stride(from: minVal, through: maxVal, by: 1)) + let max = maxVal > 32 ? 32 : maxVal + availableValues = Array(stride(from: minVal, through: max, by: 1)) } self.set(value: remoteId, andAvailableValues: availableValues, forSetting: .remoteId) @@ -412,7 +468,8 @@ extension SILRangeTestAppViewModel { var availableValues: [Double]? = nil if let minVal = minValue, let maxVal = maxValue { - availableValues = Array(stride(from: minVal, through: maxVal, by: 1)) + let max = maxVal > 32 ? 32 : maxVal + availableValues = Array(stride(from: minVal, through: max, by: 1)) } self.set(value: selfId, andAvailableValues: availableValues, forSetting: .selfId) diff --git a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestModeSelectionViewController.swift b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestModeSelectionViewController.swift index 811a7f5c..3c5e883a 100644 --- a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestModeSelectionViewController.swift +++ b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestModeSelectionViewController.swift @@ -28,12 +28,11 @@ protocol SILRangeTestModeSelectionViewControllerDelegate { class SILRangeTestModeSelectionViewController: UIViewController { var app: SILApp? - var delegate: SILRangeTestModeSelectionViewControllerDelegate? + weak var delegate: SILRangeTestModeSelectionViewControllerDelegate? var peripheral: SILRangeTestPeripheral? @IBOutlet weak var loadingView: UIView! @IBOutlet weak var mainView: UIView! - @IBOutlet private var roundedImageViews: [UIImageView]! @IBOutlet private weak var deviceNameLabel: UILabel! @IBOutlet weak var modelNumberLabel: UILabel! @IBOutlet weak var txPowerLabel: UILabel! @@ -51,21 +50,21 @@ class SILRangeTestModeSelectionViewController: UIViewController { self.parse(txPower: nil) self.parse(deviceName: peripheral?.deviceName()) - peripheral?.modelNumber { - self.modelNumber = $0 - self.peripheralValueReceived() + peripheral?.modelNumber { [weak self] in + self?.modelNumber = $0 + self?.peripheralValueReceived() } - peripheral?.txPower { (value, min, max) in - self.txPower = value - self.peripheralValueReceived() + peripheral?.txPower { [weak self] (value, min, max) in + self?.txPower = value + self?.peripheralValueReceived() } - peripheral?.radioMode { - self.radioMode = $0 - self.peripheralValueReceived() + peripheral?.radioMode { [weak self] in + self?.radioMode = $0 + self?.peripheralValueReceived() } - peripheral?.isRunning { - self.isRunning = $0 - self.peripheralValueReceived() + peripheral?.isRunning { [weak self] in + self?.isRunning = $0 + self?.peripheralValueReceived() } } @@ -74,15 +73,7 @@ class SILRangeTestModeSelectionViewController: UIViewController { super.viewWillDisappear(animated) } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - for roundedImageView in roundedImageViews { - roundedImageView.layer.cornerRadius = roundedImageView.frame.width/2 - } - } - override var preferredContentSize: CGSize { get { if UI_USER_INTERFACE_IDIOM() == .pad { diff --git a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestSelectDeviceViewController.swift b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestSelectDeviceViewController.swift new file mode 100644 index 00000000..ea37cb23 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestSelectDeviceViewController.swift @@ -0,0 +1,100 @@ +// +// SILRangeTestSelectDeviceViewController.swift +// SiliconLabsApp +// +// Created by Michał Lenart on 26/11/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import UIKit + +class SILRangeTestSelectDeviceViewController: UIViewController, SILDeviceSelectionViewControllerDelegate, WYPopoverControllerDelegate, SILRangeTestModeSelectionViewControllerDelegate { + @IBOutlet weak var connectButton: SILPrimaryButton! + + private let app = SILApp.rangeTest()! + private let centralManager = SILCentralManagerBuilder.buildCentralManager(with: SILAppType.typeRangeTest)! + + private var popoverController: WYPopoverController? + + override func viewDidLoad() { + super.viewDidLoad() + connectButton.isEnabled = false + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + connect() + } + + @IBAction func didTapConnect(_ sender: Any) { + connect() + } + + private func connect() { + connectButton.isEnabled = false + self.popoverController?.dismissPopover(animated: false) + + let viewModel = SILDeviceSelectionViewModel(appType: app) + viewModel?.filter = { discoveredPeripheral in + return discoveredPeripheral?.isRangeTest ?? false + } + + let selectionViewController = SILDeviceSelectionViewController(deviceSelectionViewModel: viewModel)! + + selectionViewController.centralManager = centralManager + selectionViewController.delegate = self + + self.popoverController = WYPopoverController.sil_presentCenterPopover(withContentViewController: selectionViewController, presenting: self, delegate: self, animated: true) + } + + // MARK: SILDeviceSelectionViewControllerDelegate + + func deviceSelectionViewController(_ viewController: SILDeviceSelectionViewController!, didSelect peripheral: CBPeripheral!) { + self.popoverController?.dismissPopover(animated: true) { + let storyboard = UIStoryboard(name: "SILAppTypeRangeTest", bundle: nil) + let selectionViewController = storyboard.instantiateViewController(withIdentifier: "SILRangeTestModeSelectionViewController") as! SILRangeTestModeSelectionViewController + + selectionViewController.app = self.app; + selectionViewController.delegate = self + selectionViewController.peripheral = SILRangeTestPeripheral(withPeripheral: peripheral, andCentralManager: self.centralManager) + + self.popoverController = WYPopoverController.sil_presentCenterPopover(withContentViewController: selectionViewController, presenting: self, delegate: self, animated: true) + } + } + + func didDismissDeviceSelectionViewController() { + popoverController = nil + connectButton.isEnabled = true + } + + // MARK: SILRangeTestModeSelectionViewControllerDelegate + + func didRangeTestModeSelected(forApp app: SILApp?, + peripheral: SILRangeTestPeripheral?, + andBoardInfo boardInfo: SILRangeTestBoardInfo?, + selectedMode mode: SILRangeTestMode) { + self.popoverController?.dismissPopover(animated: true) { + let storyboard = UIStoryboard(name: "SILAppTypeRangeTest", bundle: nil) + let viewController = storyboard.instantiateViewController(withIdentifier: "SILRangeTestAppViewController") as! SILRangeTestAppViewController + let viewModel = SILRangeTestAppViewModel(withMode: mode, peripheral: peripheral!, andBoardInfo: boardInfo!) + + viewController.app = app; + viewController.viewModel = viewModel; + + self.navigationController?.pushViewController(viewController, animated: true) + self.connectButton.isEnabled = true + } + } + + func didDismissRangeTestModeSelectionViewController() { + popoverController = nil + connectButton.isEnabled = true + } + + // MARK: WYPopoverControllerDelegate + + func popoverControllerDidDismissPopover(_ popoverController: WYPopoverController!) { + self.popoverController = nil + } +} diff --git a/SiliconLabsApp/ViewModels/Advertiser/Details/SILAdvertiserDetailsViewModel.swift b/SiliconLabsApp/ViewModels/Advertiser/Details/SILAdvertiserDetailsViewModel.swift index c83a4e8d..c2ee01e0 100644 --- a/SiliconLabsApp/ViewModels/Advertiser/Details/SILAdvertiserDetailsViewModel.swift +++ b/SiliconLabsApp/ViewModels/Advertiser/Details/SILAdvertiserDetailsViewModel.swift @@ -28,6 +28,7 @@ class SILAdvertiserDetailsViewModel { private var currentState: SILTimeLimitRadioButtonState private var isExecutionTime: Bool private var executionTime: Double + private var executionTimeString: String init(wireframe: SILAdvertiserDetailsWireframe, repository: SILAdvertisingSetRepository, serviceRepository: SILAdvertisingServiceRepository, service: SILAdvertiserService, settings: SILAdvertiserSettings, advertiser: SILAdvertisingSetEntity) { self.wireframe = wireframe @@ -45,6 +46,7 @@ class SILAdvertiserDetailsViewModel { self.currentState = advertiser.isExecutionTime ? .withLimit : .noLimit self.isExecutionTime = advertiser.isExecutionTime self.executionTime = advertiser.executionTime + self.executionTimeString = String(Int(advertiser.executionTime * 1000)) self.advertisingData = SILObservable(initialValue: []) self.advertisingDataBytesAvailable = SILObservable(initialValue: 28) @@ -60,6 +62,10 @@ class SILAdvertiserDetailsViewModel { self.advertisingSetName = advertisingSetName ?? ""; } + func updateExecutionTimeString(_ timeString: String?) { + self.executionTimeString = timeString ?? "" + } + func updateExecutionTimeState(isExecutionTime: Bool) { if (isExecutionTime) { self.isExecutionTime = true @@ -220,12 +226,61 @@ class SILAdvertiserDetailsViewModel { }) } - func save(timeString: String?) { + func backToHome() { + let popupIsEnabled = !settings.nonSaveChangesExitWarning + if popupIsEnabled, advertiserWasChanged() { + wireframe.presentNonSaveChangesExitWarningPopup( + onYes: { disableWarning in + self.settings.nonSaveChangesExitWarning = disableWarning + self.save() + }, onNo: { + self.wireframe.popPage() + }) + } else { + wireframe.popPage() + } + } + + private func advertiserWasChanged() -> Bool { + if self.advertisingSetName != advertiser.name { + return true + } + + if self.isCompleteLocalName != advertiser.isCompleteLocalName { + return true + } + + let advertiserCompleteList16 = advertiser.completeList16 ?? [] + let currentCompleteList16 = self.completeList16 ?? [] + if !currentCompleteList16.elementsEqual(advertiserCompleteList16) { + return true + } + + let advertiserCompleteList128 = advertiser.completeList128 ?? [] + let currentCompleteList128 = self.completeList128 ?? [] + if !currentCompleteList128.elementsEqual(advertiserCompleteList128) { + return true + } + + let advertiserState: SILTimeLimitRadioButtonState = advertiser.isExecutionTime ? .withLimit : .noLimit + if self.currentState != advertiserState { + return true + } + + let advertiserTimeString = String(Int(advertiser.executionTime * 1000)) + if self.executionTimeString != advertiserTimeString { + return true + } + + return false + } + + func save() { let updated = SILAdvertisingSetEntity(value: advertiser) completeList16 = completeList16 == [] ? nil : completeList16 completeList128 = completeList128 == [] ? nil : completeList128 - if (!validateExecutionTime(fromString: timeString)) { + if (!validateExecutionTime(fromString: executionTimeString)) { wireframe.presentInvalidTimeToastAlert() return } diff --git a/SiliconLabsApp/ViewModels/Advertiser/ExitAdvertiserPopup/SILExitAdvertiserPopupViewModel.swift b/SiliconLabsApp/ViewModels/Advertiser/ExitAdvertiserPopup/SILExitAdvertiserPopupViewModel.swift new file mode 100644 index 00000000..c5927b76 --- /dev/null +++ b/SiliconLabsApp/ViewModels/Advertiser/ExitAdvertiserPopup/SILExitAdvertiserPopupViewModel.swift @@ -0,0 +1,35 @@ +// +// SILExitAdvertiserPopupViewModel.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 09/12/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +class SILExitAdvertiserPopupViewModel { + private let wireframe: SILAdvertiserDetailsWireframe + private let onYesCallback: (Bool) -> () + private let onNoCallback: () -> () + + private var disableWarning: Bool = false + + init(wireframe: SILAdvertiserDetailsWireframe, onYesCallback: @escaping (Bool) -> (), onNoCallback: @escaping () -> ()) { + self.wireframe = wireframe + self.onYesCallback = onYesCallback + self.onNoCallback = onNoCallback + } + + func onSwitchChange(disableWarning: Bool) { + self.disableWarning = disableWarning + } + + func onYes() { + wireframe.dismissPopover() + onYesCallback(disableWarning) + } + + func onNo() { + wireframe.dismissPopover() + onNoCallback() + } +} diff --git a/SiliconLabsApp/ViewModels/Advertiser/Home/SILAdvertiserHomeViewModel.swift b/SiliconLabsApp/ViewModels/Advertiser/Home/SILAdvertiserHomeViewModel.swift index baec846e..8c1a1c72 100644 --- a/SiliconLabsApp/ViewModels/Advertiser/Home/SILAdvertiserHomeViewModel.swift +++ b/SiliconLabsApp/ViewModels/Advertiser/Home/SILAdvertiserHomeViewModel.swift @@ -53,6 +53,12 @@ class SILAdvertiserHomeViewModel { self?.runningAdvertisers = runningAdvertisers }.putIn(bag: observableTokenBag) + service.blutoothEnabled.observe { [weak self] state in + if state == false { + self?.wireframe.showBluetoothDisabledDialog() + } + }.putIn(bag: observableTokenBag) + advertiserNotification.askForPermission() } diff --git a/SiliconLabsApp/ViewModels/DebugDeviceViewModel.swift b/SiliconLabsApp/ViewModels/DebugDeviceViewModel.swift index 7608fd56..0562c29c 100644 --- a/SiliconLabsApp/ViewModels/DebugDeviceViewModel.swift +++ b/SiliconLabsApp/ViewModels/DebugDeviceViewModel.swift @@ -17,7 +17,8 @@ protocol DebugDeviceViewModelDelegate: class { func didDisconnect(from peripheral: CBPeripheral?) @objc(didFailToConnectToPeripheral:) func didFailToConnect(to peripheral: CBPeripheral?) - + func bluetoothIsDisabled() + func scanningDidEnd() } @@ -38,43 +39,43 @@ final class DebugDeviceViewModel: NSObject { private(set) var isConnecting: [CBPeripheral : Bool] = [:] var didStopScanning = false var isContentAvailable: Bool { - return discoveredPeripherals.count > 0 || didStopScanning + return discoveredPeripheralsViewModels.count > 0 || didStopScanning } var observing = false var currentMinRSSI: NSNumber? = nil { didSet { - removeDiscoveredDevicesIfNeed() + removeAndSortDiscoveredDevicesIfNeed() } } var searchByDeviceName: String? = nil { didSet { - removeDiscoveredDevicesIfNeed() + removeAndSortDiscoveredDevicesIfNeed() } } var searchByAdvertisingData: String? = nil { didSet { - removeDiscoveredDevicesIfNeed() + removeAndSortDiscoveredDevicesIfNeed() } } var beaconTypes: [SILBrowserBeaconType]? = nil { didSet { - removeDiscoveredDevicesIfNeed() + removeAndSortDiscoveredDevicesIfNeed() } } var isFavourite: Bool = false { didSet { - removeDiscoveredDevicesIfNeed() + removeAndSortDiscoveredDevicesIfNeed() } } var isConnectable: Bool = false { didSet { - removeDiscoveredDevicesIfNeed() + removeAndSortDiscoveredDevicesIfNeed() } } @@ -83,6 +84,12 @@ final class DebugDeviceViewModel: NSObject { let peripheralName = peripheral.name ?? DefaultDeviceName return "Disconnected from \(peripheralName)" } + + var sortOption: SILSortOption = .none { + didSet { + removeAndSortDiscoveredDevicesIfNeed() + } + } // MARK: - Lifecycle @@ -169,7 +176,7 @@ final class DebugDeviceViewModel: NSObject { performActionsForStopScanningWasTapped(peripheralViewModels) } else { addNewDevicesIfNeed(peripheralViewModels) - removeDiscoveredDevicesIfNeed() + removeAndSortDiscoveredDevicesIfNeed() } } @@ -185,6 +192,7 @@ final class DebugDeviceViewModel: NSObject { NotificationCenter.default.addObserver(self, selector: #selector(didConnectPeripheral(notification:)), name: .SILCentralManagerDidConnectPeripheral, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(didDisconnectPeripheral(notification:)), name: .SILCentralManagerDidDisconnectPeripheral, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(didFailToConnectPeripheral(notification:)), name: .SILCentralManagerDidFailToConnectPeripheral, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(bluetoothIsDisabled(notification:)), name: .SILCentralManagerBluetoothDisabled, object: nil) } private func unregisterNotifications() { @@ -192,6 +200,7 @@ final class DebugDeviceViewModel: NSObject { NotificationCenter.default.removeObserver(self, name: .SILCentralManagerDidConnectPeripheral, object: nil) NotificationCenter.default.removeObserver(self, name: .SILCentralManagerDidDisconnectPeripheral, object: nil) NotificationCenter.default.removeObserver(self, name: .SILCentralManagerDidFailToConnectPeripheral, object: nil) + NotificationCenter.default.removeObserver(self, name: .SILCentralManagerBluetoothDisabled, object: nil) } // MARK: - Stop Scanning Was Tapped Case @@ -203,7 +212,7 @@ final class DebugDeviceViewModel: NSObject { } else { if isEqualToReplacementPeripheralsViewModel(peripheralViewModels) { replaceDevicesIfNeed(replacementDiscoveredPeripheralViewModels) - removeDiscoveredDevicesIfNeed() + removeAndSortDiscoveredDevicesIfNeed() replacementDiscoveredPeripheralViewModels = [] } else { fillReplacementDiscoveredPeripheralViewModel(peripheralViewModels) @@ -279,14 +288,10 @@ final class DebugDeviceViewModel: NSObject { } } - private func removeDiscoveredDevicesIfNeed() { - setupDeviceForFilter() - filterByCurrentMinRSSI() - filterBySearchDeviceName() - filterBySearchAdvertisingData() - filterByBeaconTypes() - filterByIsFavourite() - filterByIsConnectable() + private func removeAndSortDiscoveredDevicesIfNeed() { + setupDevicesForFilterAndSorting() + filterDevices() + sortDevices() if didStopScanning { postCellsForVisibleRows() didStopScanning = false @@ -297,7 +302,16 @@ final class DebugDeviceViewModel: NSObject { // MARK: - Filtering - private func setupDeviceForFilter() { + private func filterDevices() { + filterByCurrentMinRSSI() + filterBySearchDeviceName() + filterBySearchAdvertisingData() + filterByBeaconTypes() + filterByIsFavourite() + filterByIsConnectable() + } + + private func setupDevicesForFilterAndSorting() { discoveredPeripheralsViewModels = allDiscoveredPeripheralsViewModels } @@ -360,6 +374,64 @@ final class DebugDeviceViewModel: NSObject { connectablePredicate.evaluate(with: $0) } } } + + // MARK: - Sorting + + private func sortDevices() { + switch self.sortOption { + case .ascendingRSSI: + sortRSSI(ascending: true) + case .descendingRSSI: + sortRSSI(ascending: false) + case .AZ: + sortName(aToZ: true) + case .ZA: + sortName(aToZ: false) + case .none: + return + } + moveFavoritesUp() + } + + private func sortRSSI(ascending: Bool) { + discoveredPeripheralsViewModels = discoveredPeripheralsViewModels.sorted(by: { (first, second) in + let firstRSSI = first.discoveredPeripheralDisplayData.discoveredPeripheral.rssiValue()?.intValue ?? 0 + let secondRSSI = second.discoveredPeripheralDisplayData.discoveredPeripheral.rssiValue()?.intValue ?? 0 + if ascending { + return firstRSSI < secondRSSI + } else { + return firstRSSI > secondRSSI + } + }) + } + + private func sortName(aToZ: Bool) { + discoveredPeripheralsViewModels = discoveredPeripheralsViewModels.sorted(by: { (first, second) in + let firstName = first.discoveredPeripheralDisplayData.discoveredPeripheral.advertisedLocalName ?? DefaultDeviceName + let secondName = second.discoveredPeripheralDisplayData.discoveredPeripheral.advertisedLocalName ?? DefaultDeviceName + if aToZ { + return firstName < secondName + } else { + return firstName > secondName + } + }) + } + + func moveFavoritesUp() { + if SILFavoritePeripheral.areFavoritePeripherals() { + var peripheralsWithFavoritesAsFirst:[SILDiscoveredPeripheralDisplayDataViewModel] = [] + var index = 0 + for discoveredPeripheral in discoveredPeripheralsViewModels { + if (SILFavoritePeripheral.isFavorite(discoveredPeripheral)) { + peripheralsWithFavoritesAsFirst.insert(discoveredPeripheral, at: index) + index += 1 + } else { + peripheralsWithFavoritesAsFirst.append(discoveredPeripheral) + } + } + discoveredPeripheralsViewModels = peripheralsWithFavoritesAsFirst + } + } // MARK: - Post Notifications @@ -399,6 +471,10 @@ final class DebugDeviceViewModel: NSObject { delegate?.didFailToConnect(to: peripheral) } + @objc private func bluetoothIsDisabled(notification: Notification) { + delegate?.bluetoothIsDisabled() + } + // MARK: - Utils private func arrayContainDevice(_ device: SILDiscoveredPeripheralDisplayDataViewModel, in collection: [SILDiscoveredPeripheralDisplayDataViewModel]) -> Bool { diff --git a/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.h b/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.h index 54c16b61..965f8edd 100644 --- a/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.h +++ b/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.h @@ -16,6 +16,7 @@ @property (strong, nonatomic) NSArray* discoveredDevices; @property (strong, nonatomic) SILDiscoveredPeripheral *connectingPeripheral; @property (assign, nonatomic) BOOL hasDataChanged; +@property (nonatomic, copy) BOOL (^filter)(SILDiscoveredPeripheral*); - (instancetype)initWithAppType:(SILApp *)app; - (void)updateDiscoveredPeripheralsWithDiscoveredPeripherals:(NSArray*)discoveredPeripherals; diff --git a/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.m b/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.m index 7c68b374..1ae70885 100644 --- a/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.m +++ b/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.m @@ -17,6 +17,10 @@ @implementation SILDeviceSelectionViewModel - (instancetype)initWithAppType:(SILApp *)app { self = [super init]; self.app = app; + self.filter = ^BOOL(SILDiscoveredPeripheral* _) { + return YES; + }; + return self; } @@ -26,7 +30,10 @@ - (void)updateDiscoveredPeripheralsWithDiscoveredPeripherals:(NSArray*)removeNonConnectableDevices:(NSArray*)discoveredPeripherals { - NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"isConnectable == YES"]; + NSPredicate* filterPredicate = [NSPredicate predicateWithBlock:^BOOL(SILDiscoveredPeripheral* evaluatedObject, NSDictionary* bindings) { + return evaluatedObject.isConnectable && self.filter(evaluatedObject); + }]; + return [discoveredPeripherals filteredArrayUsingPredicate:filterPredicate]; } diff --git a/SiliconLabsApp/Views/SILBigButton.swift b/SiliconLabsApp/Views/SILBigButton.swift new file mode 100644 index 00000000..3b800f6c --- /dev/null +++ b/SiliconLabsApp/Views/SILBigButton.swift @@ -0,0 +1,28 @@ +// +// SILBigButton.swift +// SiliconLabsApp +// +// Created by Michał Lenart on 19/11/2020. +// Copyright © 2020 SiliconLabs. All rights reserved. +// + +import UIKit + +@IBDesignable +class SILBigButton: UIButton { + @IBInspectable var extendLeft: CGFloat = 0 + @IBInspectable var extendTop: CGFloat = 0 + @IBInspectable var extendRight: CGFloat = 0 + @IBInspectable var extendBottom: CGFloat = 0 + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + let newArea = CGRect( + x: self.bounds.origin.x - extendLeft, + y: self.bounds.origin.y - extendTop, + width: self.bounds.size.width + extendLeft + extendRight, + height: self.bounds.size.height + extendTop + extendBottom + ) + + return newArea.contains(point) + } +} diff --git a/SiliconLabsApp/ViewControllers/BluetoothBrowser/KeyChains/SILBrowserSegmentedControl.swift b/SiliconLabsApp/Views/SILSegmentedControl.swift similarity index 54% rename from SiliconLabsApp/ViewControllers/BluetoothBrowser/KeyChains/SILBrowserSegmentedControl.swift rename to SiliconLabsApp/Views/SILSegmentedControl.swift index ab694898..baddc866 100644 --- a/SiliconLabsApp/ViewControllers/BluetoothBrowser/KeyChains/SILBrowserSegmentedControl.swift +++ b/SiliconLabsApp/Views/SILSegmentedControl.swift @@ -1,29 +1,18 @@ // // SILSegmentedControl.swift -// BlueGecko +// SiliconLabsApp // -// Created by Jan Wisniewski on 02/03/2020. +// Created by Kamil Czajka on 15.12.2020. // Copyright © 2020 SiliconLabs. All rights reserved. // -import UIKit +import Foundation @IBDesignable -class SILBrowserSegmentedControl: UISegmentedControl { - - enum SegmentType: Int { - case characteristics = 0 - case services = 1 - } - - var segmentType: SegmentType { - return SegmentType(rawValue: self.selectedSegmentIndex)! - } - +class SILSegmentedControl: UISegmentedControl { override func layoutSubviews() { super.layoutSubviews() self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], for: .selected) self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], for: .normal) } - } diff --git a/SiliconLabsApp/Views/SILSegmentedControl.h b/SiliconLabsApp/Views/SILThermometerSegmentedControl.h similarity index 90% rename from SiliconLabsApp/Views/SILSegmentedControl.h rename to SiliconLabsApp/Views/SILThermometerSegmentedControl.h index 2d18ecf5..78045688 100644 --- a/SiliconLabsApp/Views/SILSegmentedControl.h +++ b/SiliconLabsApp/Views/SILThermometerSegmentedControl.h @@ -1,5 +1,5 @@ // -// SILSegmentedControl.h +// SILThermometerSegmentedControl.h // SiliconLabsApp // // Created by Colden Prime on 1/23/15. @@ -8,7 +8,7 @@ #import -@interface SILSegmentedControl : UIControl +@interface SILThermometerSegmentedControl : UIControl @property (nonatomic) NSUInteger selectedIndex; diff --git a/SiliconLabsApp/Views/SILSegmentedControl.m b/SiliconLabsApp/Views/SILThermometerSegmentedControl.m similarity index 92% rename from SiliconLabsApp/Views/SILSegmentedControl.m rename to SiliconLabsApp/Views/SILThermometerSegmentedControl.m index 99b25b94..4badb695 100644 --- a/SiliconLabsApp/Views/SILSegmentedControl.m +++ b/SiliconLabsApp/Views/SILThermometerSegmentedControl.m @@ -1,19 +1,19 @@ // -// SILSegmentedControl.m +// SILThermometerSegmentedControl.m // SiliconLabsApp // // Created by Colden Prime on 1/23/15. // Copyright (c) 2015 SiliconLabs. All rights reserved. // -#import "SILSegmentedControl.h" +#import "SILThermometerSegmentedControl.h" #import "UIColor+SILColors.h" -@interface SILSegmentedControl () +@interface SILThermometerSegmentedControl () @end -@implementation SILSegmentedControl +@implementation SILThermometerSegmentedControl - (void)setSelectedTextColor:(UIColor *)selectedTextColor { _selectedTextColor = selectedTextColor;