Files
universal_ble/example/lib/data/scan_controller.dart
T
Foti Dim 8c75e3965f Integrate SharedPreferencesAsync for improved preferences handling
- Added a new `prefs_async.dart` file to encapsulate the usage of `SharedPreferencesAsync`.
- Updated `ScanController`, `StorageService`, and `ScannerScreen` to utilize the new async preferences methods, enhancing performance and code clarity.
- Modified `devtools_options.yaml` to enable the `shared_preferences` extension.
- Refactored the `FlashOnUpdateWidget` to improve the outline animation and added a customizable border radius.
- Adjusted the favorite services loading mechanism in `PeripheralDetailPage` to be asynchronous, ensuring better state management.
2026-02-09 22:24:41 +01:00

137 lines
3.8 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:universal_ble/universal_ble.dart';
import 'package:universal_ble_example/data/company_identifier_service.dart';
import 'package:universal_ble_example/data/prefs_async.dart';
/// Shared controller for BLE scan state so the scan button can be shown
/// on both the scanner screen and the peripheral detail screen.
class ScanController extends ChangeNotifier {
bool _isScanning = false;
bool get isScanning => _isScanning;
/// Syncs [isScanning] with the actual native state (e.g. after app restart).
Future<void> syncState() async {
final value = await UniversalBle.isScanning();
if (_isScanning != value) {
_isScanning = value;
notifyListeners();
}
}
Future<void> startScan() async {
if (_isScanning) return;
try {
final filter = await _loadFilterFromPrefs();
final platformConfig = await _loadPlatformConfigFromPrefs();
await UniversalBle.startScan(
scanFilter: filter,
platformConfig: platformConfig,
);
_isScanning = true;
notifyListeners();
} catch (e) {
_isScanning = false;
notifyListeners();
rethrow;
}
}
Future<void> stopScan() async {
if (!_isScanning) return;
try {
await UniversalBle.stopScan();
_isScanning = false;
notifyListeners();
} catch (e) {
rethrow;
}
}
Future<ScanFilter?> _loadFilterFromPrefs() async {
final services =
(await prefsAsync.getString('scan_filter_services')) ?? '';
final namePrefix =
(await prefsAsync.getString('scan_filter_name_prefix')) ?? '';
final manufacturerData =
(await prefsAsync.getString('scan_filter_manufacturer_data')) ?? '';
if (services.isEmpty &&
namePrefix.isEmpty &&
manufacturerData.isEmpty) {
return null;
}
await CompanyIdentifierService.instance.load();
final serviceUUids = <String>[];
if (services.isNotEmpty) {
for (final s in services.split(',').map((e) => e.trim())) {
if (s.isEmpty) continue;
try {
serviceUUids.add(BleUuidParser.string(s));
} on FormatException catch (_) {
continue;
}
}
}
final namePrefixes = namePrefix.isEmpty
? <String>[]
: namePrefix.split(',').map((e) => e.trim()).toList();
final manufacturerDataFilters = <ManufacturerDataFilter>[];
if (manufacturerData.isNotEmpty) {
final companyService = CompanyIdentifierService.instance;
for (final s in manufacturerData
.split(',')
.map((e) => e.trim())
.where((s) => s.isNotEmpty)) {
final id = companyService.parseCompanyIdentifier(s);
if (id != null) {
manufacturerDataFilters.add(ManufacturerDataFilter(
companyIdentifier: id,
));
}
}
}
if (serviceUUids.isEmpty &&
namePrefixes.isEmpty &&
manufacturerDataFilters.isEmpty) {
return null;
}
return ScanFilter(
withServices: serviceUUids,
withNamePrefix: namePrefixes,
withManufacturerData: manufacturerDataFilters,
);
}
Future<PlatformConfig> _loadPlatformConfigFromPrefs() async {
WebOptions? web;
if (kIsWeb) {
final webServices =
(await prefsAsync.getString('scan_filter_web_services')) ?? '';
if (webServices.isNotEmpty) {
final list = webServices
.split(',')
.where((s) => s.trim().isNotEmpty)
.map((s) {
try {
return BleUuidParser.string(s.trim());
} catch (_) {
return s.trim();
}
}).toList();
web = WebOptions(optionalServices: list);
}
}
return PlatformConfig(
android: AndroidOptions(scanMode: AndroidScanMode.lowLatency),
web: web,
);
}
}