132 lines
3.8 KiB
Dart
132 lines
3.8 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:anyhow/anyhow.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:logging/logging.dart';
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
|
|
part 'bluetooth.g.dart';
|
|
|
|
final log = Logger('BluetoothController');
|
|
|
|
@riverpod
|
|
Future<BluetoothController> bluetooth(Ref ref) async {
|
|
final controller = BluetoothController();
|
|
log.info(await controller.init());
|
|
return controller;
|
|
}
|
|
|
|
class BluetoothController {
|
|
StreamSubscription<BluetoothAdapterState>? _btStateSubscription;
|
|
StreamSubscription<List<ScanResult>>? _scanResultsSubscription;
|
|
List<ScanResult> _latestScanResults = [];
|
|
|
|
Future<Result<void>> init() async {
|
|
if (await FlutterBluePlus.isSupported == false) {
|
|
log.severe("Bluetooth is not supported on this device!");
|
|
return bail("Bluetooth is not supported on this device!");
|
|
}
|
|
|
|
_btStateSubscription =
|
|
FlutterBluePlus.adapterState.listen((BluetoothAdapterState state) {
|
|
if (state == BluetoothAdapterState.on) {
|
|
log.info("Bluetooth is on!");
|
|
// usually start scanning, connecting, etc
|
|
} else {
|
|
log.info("Bluetooth is off!");
|
|
// show an error to the user, etc
|
|
}
|
|
});
|
|
|
|
if (!kIsWeb && Platform.isAndroid) {
|
|
await FlutterBluePlus.turnOn();
|
|
}
|
|
|
|
return Ok(null);
|
|
}
|
|
|
|
/// Start scanning for Bluetooth devices
|
|
///
|
|
/// [withServices] - Optional list of service UUIDs to filter devices by
|
|
/// [withNames] - Optional list of device names to filter by
|
|
/// [timeout] - Optional duration after which scanning will automatically stop
|
|
Future<Result<void>> startScan({
|
|
List<Guid>? withServices,
|
|
List<String>? withNames,
|
|
Duration? timeout,
|
|
}) async {
|
|
try {
|
|
// Wait for Bluetooth to be enabled
|
|
await FlutterBluePlus.adapterState
|
|
.where((val) => val == BluetoothAdapterState.on)
|
|
.first;
|
|
|
|
// Set up scan results listener
|
|
_scanResultsSubscription = FlutterBluePlus.onScanResults.listen(
|
|
(results) {
|
|
if (results.isNotEmpty) {
|
|
_latestScanResults = results;
|
|
ScanResult latestResult = results.last;
|
|
log.info(
|
|
'${latestResult.device.remoteId}: "${latestResult.advertisementData.advName}" found!');
|
|
}
|
|
},
|
|
onError: (e) {
|
|
log.severe('Scan error: $e');
|
|
},
|
|
);
|
|
|
|
// Clean up subscription when scanning completes
|
|
FlutterBluePlus.cancelWhenScanComplete(_scanResultsSubscription!);
|
|
|
|
// Start scanning with optional parameters
|
|
await FlutterBluePlus.startScan(
|
|
withServices: withServices ?? [],
|
|
withNames: withNames ?? [],
|
|
timeout: timeout,
|
|
);
|
|
|
|
return Ok(null);
|
|
} catch (e) {
|
|
return bail('Failed to start Bluetooth scan: $e');
|
|
}
|
|
}
|
|
|
|
/// Stop an ongoing Bluetooth scan
|
|
Future<Result<void>> stopScan() async {
|
|
try {
|
|
await FlutterBluePlus.stopScan();
|
|
return Ok(null);
|
|
} catch (e) {
|
|
return bail('Failed to stop Bluetooth scan: $e');
|
|
}
|
|
}
|
|
|
|
/// Get the latest scan results
|
|
List<ScanResult> get scanResults => _latestScanResults;
|
|
|
|
/// Wait for the current scan to complete
|
|
Future<Result<void>> waitForScanToComplete() async {
|
|
try {
|
|
await FlutterBluePlus.isScanning.where((val) => val == false).first;
|
|
return Ok(null);
|
|
} catch (e) {
|
|
return bail('Error waiting for scan to complete: $e');
|
|
}
|
|
}
|
|
|
|
/// Check if currently scanning
|
|
Future<bool> get isScanning async {
|
|
return await FlutterBluePlus.isScanning.first;
|
|
}
|
|
|
|
Future<Result<void>> dispose() async {
|
|
await _scanResultsSubscription?.cancel();
|
|
await _btStateSubscription?.cancel();
|
|
return Ok(null);
|
|
}
|
|
}
|