main
1import 'dart:async';
2
3import 'package:blood_pressure_app/features/bluetooth/backend/bluetooth_backend.dart';
4import 'package:blood_pressure_app/features/bluetooth/logic/bluetooth_cubit.dart';
5import 'package:blood_pressure_app/logging.dart';
6import 'package:blood_pressure_app/model/storage/settings_store.dart';
7import 'package:collection/collection.dart';
8import 'package:flutter/foundation.dart';
9import 'package:flutter_bloc/flutter_bloc.dart';
10
11part 'device_scan_state.dart';
12
13/// A component to search for bluetooth devices.
14///
15/// For this to work the app must have access to the bluetooth adapter
16/// ([BluetoothCubit]).
17///
18/// A device counts as recognized, when the user connected with it at least
19/// once. Recognized devices connect automatically.
20class DeviceScanCubit extends Cubit<DeviceScanState> with TypeLogger {
21 /// Search for bluetooth devices that match the criteria or are known
22 /// ([Settings.knownBleDev]).
23 DeviceScanCubit({
24 required BluetoothManager manager,
25 required this.service,
26 required this.settings,
27 }): super(DeviceListLoading()) {
28 _manager = manager;
29 _startScanning();
30 }
31
32 /// Storage for known devices.
33 late final Settings settings;
34
35 /// Service required from bluetooth devices.
36 late final String service;
37
38 late final BluetoothManager _manager;
39
40 @override
41 Future<void> close() async {
42 final stopped = await _stopScanning();
43 if (stopped) {
44 await super.close();
45 }
46 }
47
48 Future<void> _startScanning() async {
49 try {
50 // no timeout, the user knows best how long scanning is needed
51 // Not all devices are found using this configuration (https://pub.dev/packages/flutter_blue_plus#scanning-does-not-find-my-device).
52 await _manager.discovery.start(service, _onScanResult);
53 } catch (e) {
54 _onScanError(e);
55 }
56 }
57
58 Future<bool> _stopScanning() async {
59 try {
60 await _manager.discovery.stop();
61 } catch (err) {
62 logger.severe('Failed to stop scanning', err);
63 return false;
64 }
65 return true;
66 }
67
68 void _onScanResult(List<BluetoothDevice> devices) {
69 logger.finer('_onScanResult devices: $devices');
70
71 // No need to check whether the devices really support the searched
72 // characteristic as users have to select their device anyways.
73 if(state is DeviceSelected) {
74 return;
75 }
76
77 final preferred = devices.firstWhereOrNull((dev) =>
78 settings.knownBleDev.contains(dev.name));
79
80 if (preferred != null) {
81 _stopScanning()
82 .then((_) => emit(DeviceSelected(preferred)));
83 } else if (devices.isEmpty) {
84 emit(DeviceListLoading());
85 } else if (devices.length == 1) {
86 emit(SingleDeviceAvailable(devices.first));
87 } else {
88 emit(DeviceListAvailable(devices));
89 }
90 }
91
92 void _onScanError(Object error) {
93 logger.severe('Error during device discovery', error);
94 }
95
96 /// Mark a new device as known and switch to selected device state asap.
97 Future<void> acceptDevice(BluetoothDevice device) async {
98 assert(state is! DeviceSelected);
99 try {
100 await _stopScanning();
101 } catch (e) {
102 _onScanError(e);
103 return;
104 }
105
106 assert(!_manager.discovery.isDiscovering);
107 emit(DeviceSelected(device));
108 final List<String> list = settings.knownBleDev.toList();
109 list.add(device.name);
110 settings.knownBleDev = list;
111 }
112}