feat: everything up to bluetooth scanning
This commit is contained in:
131
lib/controller/bluetooth.dart
Normal file
131
lib/controller/bluetooth.dart
Normal file
@ -0,0 +1,131 @@
|
||||
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);
|
||||
}
|
||||
}
|
27
lib/controller/bluetooth.g.dart
Normal file
27
lib/controller/bluetooth.g.dart
Normal file
@ -0,0 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'bluetooth.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$bluetoothHash() => r'5e9c37c57e723b84dd08fd8763e7c445b3a4dbf3';
|
||||
|
||||
/// See also [bluetooth].
|
||||
@ProviderFor(bluetooth)
|
||||
final bluetoothProvider =
|
||||
AutoDisposeFutureProvider<BluetoothController>.internal(
|
||||
bluetooth,
|
||||
name: r'bluetoothProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$bluetoothHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef BluetoothRef = AutoDisposeFutureProviderRef<BluetoothController>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
Reference in New Issue
Block a user