Add Bluetooth permission and adapter state handling for Android and iOS

This commit is contained in:
2026-09-21 18:14:31 -04:00
parent e8e7dc4a81
commit d8efcd7a36
6 changed files with 391 additions and 11 deletions
+31 -6
View File
@@ -8,6 +8,8 @@ import 'protocol_log.dart';
enum BleConnectionState { disconnected, connected }
enum BleAdapterState { unknown, unsupported, off, on, unauthorized }
/// Thin boundary between OpenScooter and the underlying BLE plugin.
///
/// OpenScooter v1 supports EXACTLY ONE active scooter connection at a time.
@@ -17,6 +19,13 @@ enum BleConnectionState { disconnected, connected }
/// Protocol code never imports the BLE plugin directly, so protocol tests run
/// with `flutter test` and a fake subclass, without hardware.
abstract class BleClient {
/// Current Bluetooth adapter state, emitting on change. Fakes default to on.
Stream<BleAdapterState> get adapterState => Stream.value(BleAdapterState.on);
/// Asks the OS to enable Bluetooth. Only Android supports this; elsewhere it
/// is a no-op and the UI must direct the user to system settings.
Future<void> turnOn() async {}
/// Streams the current set of visible devices. Scanning starts on listen
/// and stops when the subscription is cancelled.
Stream<List<ScooterDevice>> scan();
@@ -63,6 +72,27 @@ class FlutterBleClient extends BleClient {
@override
Stream<BleConnectionState> get connectionState => _connState.stream;
@override
Stream<BleAdapterState> get adapterState =>
fbp.FlutterBluePlus.adapterState.map((s) => switch (s) {
fbp.BluetoothAdapterState.on => BleAdapterState.on,
fbp.BluetoothAdapterState.off ||
fbp.BluetoothAdapterState.turningOff ||
fbp.BluetoothAdapterState.turningOn =>
BleAdapterState.off,
fbp.BluetoothAdapterState.unauthorized => BleAdapterState.unauthorized,
fbp.BluetoothAdapterState.unavailable => BleAdapterState.unsupported,
fbp.BluetoothAdapterState.unknown => BleAdapterState.unknown,
});
@override
Future<void> turnOn() async {
if (defaultTargetPlatform == TargetPlatform.android) {
_log('ADAPTER turnOn requested');
await fbp.FlutterBluePlus.turnOn();
}
}
@override
Stream<List<ScooterDevice>> scan() {
late StreamController<List<ScooterDevice>> controller;
@@ -70,12 +100,7 @@ class FlutterBleClient extends BleClient {
Future<void> start() async {
try {
var adapter = await fbp.FlutterBluePlus.adapterState.first;
if (adapter != fbp.BluetoothAdapterState.on &&
defaultTargetPlatform == TargetPlatform.android) {
await fbp.FlutterBluePlus.turnOn();
}
adapter = await fbp.FlutterBluePlus.adapterState
await fbp.FlutterBluePlus.adapterState
.where((s) => s == fbp.BluetoothAdapterState.on)
.first
.timeout(const Duration(seconds: 10));