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