From 853f09b14a8a565be60ddd47cd573cf605147ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantalea=CC=83o?= Date: Fri, 19 Jul 2024 14:39:32 +0200 Subject: [PATCH] Only ask bluetooth permission after first scan trigger --- Improv-iOS.podspec | 2 +- .../xcdebugger/Breakpoints_v2.xcbkptlist | 18 ++++++++ Improv-iOS/BluetoothManager.swift | 46 +++++++++++++------ Improv-iOS/ImprovManager.swift | 39 ++++++++++------ Improv-iOSTests/ImprovManagerTests.swift | 8 ---- .../Mocks/MockBluetoothManagerDelegate.swift | 12 +++++ 6 files changed, 90 insertions(+), 35 deletions(-) diff --git a/Improv-iOS.podspec b/Improv-iOS.podspec index 08957db..694c11a 100644 --- a/Improv-iOS.podspec +++ b/Improv-iOS.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "Improv-iOS" - spec.version = "0.0.5" + spec.version = "0.0.6" spec.summary = "Easily detect and connect Improv devices to WiFi networks in iOS" spec.description = "This library abstracts the bluetooth scanning for Improv devices and allow you to connect them to WiFi networks" spec.author = "Improv" diff --git a/Improv-iOS.xcodeproj/xcuserdata/brunopantaleao.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Improv-iOS.xcodeproj/xcuserdata/brunopantaleao.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 2fd5a36..898015e 100644 --- a/Improv-iOS.xcodeproj/xcuserdata/brunopantaleao.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Improv-iOS.xcodeproj/xcuserdata/brunopantaleao.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,4 +3,22 @@ uuid = "60E63B13-8A48-4F9F-B6DA-D9E72F8516D3" type = "1" version = "2.0"> + + + + + + diff --git a/Improv-iOS/BluetoothManager.swift b/Improv-iOS/BluetoothManager.swift index 1acc6fb..7bd7c47 100644 --- a/Improv-iOS/BluetoothManager.swift +++ b/Improv-iOS/BluetoothManager.swift @@ -17,6 +17,8 @@ protocol BluetoothManagerDelegate: AnyObject { func didConnect(peripheral: CBPeripheral) func didDisconnect(peripheral: CBPeripheral) func didReceiveResult(_ result: [String]?) + func didUpdateIsScanning(_ isScanning: Bool) + func didFailScanningBluetoothNotAvailable() } protocol BluetoothManagerProtocol { @@ -38,31 +40,45 @@ public enum BluetoothManagerError: Error { final class BluetoothManager: NSObject, BluetoothManagerProtocol { - private var centralManager: CBCentralManager + private var centralManager: CBCentralManager? weak var delegate: BluetoothManagerDelegate? var state: CBManagerState { - centralManager.state + centralManager?.state ?? .unknown + } + + var isScanning: Bool { + centralManager?.isScanning ?? false } private var bluetoothGatt: CBPeripheral? private var operationQueue = [BleOperationType]() private var pendingOperation: BleOperationType? - override init() { - self.centralManager = CBCentralManager() - super.init() - - centralManager.delegate = self - } + private var scanAsSoonAsPoweredOn = false + private var scanObservation: NSKeyValueObservation? func scan() { - let scanOptions: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: false] - centralManager.scanForPeripherals(withServices: [BluetoothUUIDs.serviceProvision], options: scanOptions) + if centralManager == nil { + scanAsSoonAsPoweredOn = true + centralManager = CBCentralManager(delegate: self, queue: .main) + } else if centralManager?.state == .poweredOn { + scanObservation = centralManager?.observe(\.isScanning, options: [.new]) { [weak self] (manager, change) in + if let isScanning = change.newValue { + self?.delegate?.didUpdateIsScanning(isScanning) + } + } + + let scanOptions: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: false] + centralManager?.scanForPeripherals(withServices: [BluetoothUUIDs.serviceProvision], options: scanOptions) + } else { + Logger.main.info("User has not allowed bluetooth permission or bluetooth unavailable. Bluetooth state: \(String(describing: self.centralManager?.state.rawValue))") + delegate?.didFailScanningBluetoothNotAvailable() + } } func stopScan() { - centralManager.stopScan() + centralManager?.stopScan() } func connectToDevice(_ peripheral: CBPeripheral) { @@ -71,7 +87,7 @@ final class BluetoothManager: NSObject, BluetoothManagerProtocol { func disconnectFromDevice(_ peripheral: CBPeripheral) { bluetoothGatt = nil - centralManager.cancelPeripheralConnection(peripheral) + centralManager?.cancelPeripheralConnection(peripheral) } func identifyDevice() -> BluetoothManagerError? { @@ -138,7 +154,7 @@ final class BluetoothManager: NSObject, BluetoothManagerProtocol { switch operation { case let connect as Connect: - centralManager.connect(connect.device, options: nil) + centralManager?.connect(connect.device, options: nil) case let write as CharacteristicWrite: bluetoothGatt?.writeValue(write.data, for: write.char, type: .withResponse) case let read as CharacteristicRead: @@ -180,6 +196,10 @@ final class BluetoothManager: NSObject, BluetoothManagerProtocol { extension BluetoothManager: CBCentralManagerDelegate { public func centralManagerDidUpdateState(_ central: CBCentralManager) { + if central.state == .poweredOn, scanAsSoonAsPoweredOn { + scanAsSoonAsPoweredOn = false + scan() + } delegate?.didUpdateBluetoohState(central.state) } diff --git a/Improv-iOS/ImprovManager.swift b/Improv-iOS/ImprovManager.swift index 186e919..bceca19 100644 --- a/Improv-iOS/ImprovManager.swift +++ b/Improv-iOS/ImprovManager.swift @@ -24,20 +24,28 @@ public protocol ImprovManagerProtocol: ObservableObject { public protocol ImprovManagerDelegate: AnyObject { func didUpdateBluetoohState(_ state: CBManagerState) - func didUpdateFoundDevices(devices: [String : CBPeripheral]) - func didConnect(peripheral: CBPeripheral) - func didDisconnect(peripheral: CBPeripheral) - func didUpdateDeviceState(_ state: DeviceState?) - func didUpdateErrorState(_ state: ErrorState?) - func didReceiveResult(_ result: [String]?) - func didReset() + func didUpdateIsScanning(_ isScanning: Bool) + func didFailScanningBluetoothNotAvailable() +} + +public extension ImprovManagerDelegate { + func didUpdateBluetoohState(_ state: CBManagerState) {} + func didUpdateFoundDevices(devices: [String : CBPeripheral]) {} + func didConnect(peripheral: CBPeripheral) {} + func didDisconnect(peripheral: CBPeripheral) {} + func didUpdateDeviceState(_ state: DeviceState?) {} + func didUpdateErrorState(_ state: ErrorState?) {} + func didReceiveResult(_ result: [String]?) {} + func didReset() {} + func didUpdateIsScanning(_ isScanning: Bool) {} + func didFailScanningBluetoothNotAvailable() {} } public final class ImprovManager: NSObject, ImprovManagerProtocol { @@ -76,16 +84,11 @@ public final class ImprovManager: NSObject, ImprovManagerProtocol { } public func scan() { - bluetoothState = bluetoothManager.state - if bluetoothState == .poweredOn { - bluetoothManager.scan() - scanInProgress = true - } + bluetoothManager.scan() } public func stopScan() { bluetoothManager.stopScan() - scanInProgress = false foundDevices = [:] delegate?.didUpdateFoundDevices(devices: foundDevices) } @@ -155,4 +158,14 @@ extension ImprovManager: BluetoothManagerDelegate { lastResult = result delegate?.didReceiveResult(result) } + + func didUpdateIsScanning(_ isScanning: Bool) { + scanInProgress = isScanning + delegate?.didUpdateIsScanning(isScanning) + } + + func didFailScanningBluetoothNotAvailable() { + delegate?.didFailScanningBluetoothNotAvailable() + } + } diff --git a/Improv-iOSTests/ImprovManagerTests.swift b/Improv-iOSTests/ImprovManagerTests.swift index 82c622f..94b48ef 100644 --- a/Improv-iOSTests/ImprovManagerTests.swift +++ b/Improv-iOSTests/ImprovManagerTests.swift @@ -24,19 +24,11 @@ class ImprovManagerTests: XCTestCase { ) } - func test_scan_whileBluetoothOff_doesNothing() { - mockBluetoothManager.state = .poweredOff - sut.scan() - - XCTAssertFalse(mockBluetoothManager.scanCalled) - } - func test_scan_whileBluetoothOn_callsScan() { mockBluetoothManager.state = .poweredOn sut.scan() XCTAssertTrue(mockBluetoothManager.scanCalled) - XCTAssertTrue(sut.scanInProgress) } func test_stopScan_callsStopScan() { diff --git a/Improv-iOSTests/Mocks/MockBluetoothManagerDelegate.swift b/Improv-iOSTests/Mocks/MockBluetoothManagerDelegate.swift index 7e7b627..9ccfbb0 100644 --- a/Improv-iOSTests/Mocks/MockBluetoothManagerDelegate.swift +++ b/Improv-iOSTests/Mocks/MockBluetoothManagerDelegate.swift @@ -17,6 +17,8 @@ final class MockBluetoothManagerDelegate: BluetoothManagerDelegate { var didConnectCalled = false var didDisconnectCalled = false var didReceiveResultCalled = false + var didUpdateIsScanningCalled = false + var didFailScanningBluetoothNotAvailableCalled = false var lastBluetoothState: CBManagerState? var lastDeviceState: DeviceState? @@ -58,4 +60,14 @@ final class MockBluetoothManagerDelegate: BluetoothManagerDelegate { didReceiveResultCalled = true lastResult = result } + + func didUpdateIsScanning(_ isScanning: Bool) { + didUpdateIsScanningCalled = true + + } + + func didFailScanningBluetoothNotAvailable() { + didFailScanningBluetoothNotAvailableCalled = true + } + }