Skip to content

Commit

Permalink
Only ask bluetooth permission after first scan trigger
Browse files Browse the repository at this point in the history
  • Loading branch information
bgoncal committed Jul 19, 2024
1 parent 535ef93 commit 853f09b
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Improv-iOS.podspec
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,22 @@
uuid = "60E63B13-8A48-4F9F-B6DA-D9E72F8516D3"
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "3521D5F5-FF22-4BD9-B821-78E9BF5499C1"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Improv-iOS/BluetoothManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "72"
endingLineNumber = "72"
landmarkName = "scan()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>
46 changes: 33 additions & 13 deletions Improv-iOS/BluetoothManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand All @@ -71,7 +87,7 @@ final class BluetoothManager: NSObject, BluetoothManagerProtocol {

func disconnectFromDevice(_ peripheral: CBPeripheral) {
bluetoothGatt = nil
centralManager.cancelPeripheralConnection(peripheral)
centralManager?.cancelPeripheralConnection(peripheral)
}

func identifyDevice() -> BluetoothManagerError? {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
}

Expand Down
39 changes: 26 additions & 13 deletions Improv-iOS/ImprovManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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()
}

}
8 changes: 0 additions & 8 deletions Improv-iOSTests/ImprovManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
12 changes: 12 additions & 0 deletions Improv-iOSTests/Mocks/MockBluetoothManagerDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -58,4 +60,14 @@ final class MockBluetoothManagerDelegate: BluetoothManagerDelegate {
didReceiveResultCalled = true
lastResult = result
}

func didUpdateIsScanning(_ isScanning: Bool) {
didUpdateIsScanningCalled = true

}

func didFailScanningBluetoothNotAvailable() {
didFailScanningBluetoothNotAvailableCalled = true
}

}

0 comments on commit 853f09b

Please sign in to comment.