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}