main
  1import 'dart:async';
  2
  3import 'package:blood_pressure_app/config.dart';
  4import 'package:blood_pressure_app/features/bluetooth/bluetooth_input.dart' show BluetoothInput;
  5import 'package:blood_pressure_app/features/old_bluetooth/logic/ble_read_cubit.dart';
  6import 'package:blood_pressure_app/features/old_bluetooth/logic/bluetooth_cubit.dart';
  7import 'package:blood_pressure_app/features/old_bluetooth/logic/characteristics/ble_measurement_data.dart';
  8import 'package:blood_pressure_app/features/old_bluetooth/logic/device_scan_cubit.dart';
  9import 'package:blood_pressure_app/features/old_bluetooth/ui/closed_bluetooth_input.dart';
 10import 'package:blood_pressure_app/features/old_bluetooth/ui/device_selection.dart';
 11import 'package:blood_pressure_app/features/old_bluetooth/ui/input_card.dart';
 12import 'package:blood_pressure_app/features/old_bluetooth/ui/measurement_failure.dart';
 13import 'package:blood_pressure_app/features/old_bluetooth/ui/measurement_success.dart';
 14import 'package:blood_pressure_app/logging.dart';
 15import 'package:blood_pressure_app/model/storage/storage.dart';
 16import 'package:flutter/material.dart';
 17import 'package:flutter_bloc/flutter_bloc.dart';
 18import 'package:flutter_blue_plus/flutter_blue_plus.dart' show Guid;
 19import 'package:blood_pressure_app/l10n/app_localizations.dart';
 20import 'package:health_data_store/health_data_store.dart';
 21
 22/// Class for inputting measurement through bluetooth.
 23/// 
 24/// This widget is superseded by [BluetoothInput].
 25class OldBluetoothInput extends StatefulWidget {
 26  /// Create a measurement input through bluetooth.
 27  OldBluetoothInput({super.key,
 28    required this.onMeasurement,
 29  }) : assert(!isTestingEnvironment, "OldBluetoothInput isn't maintained in tests");
 30
 31  /// Called when a measurement was received through bluetooth.
 32  final void Function(BloodPressureRecord data) onMeasurement;
 33
 34  @override
 35  State<OldBluetoothInput> createState() => _OldBluetoothInputState();
 36}
 37
 38class _OldBluetoothInputState extends State<OldBluetoothInput> with TypeLogger {
 39  /// Whether the user expanded bluetooth input
 40  bool _isActive = false;
 41
 42  late final BluetoothCubit _bluetoothCubit;
 43  DeviceScanCubit? _deviceScanCubit;
 44  BleReadCubit? _deviceReadCubit;
 45
 46  StreamSubscription<BluetoothState>? _bluetoothSubscription;
 47
 48  /// Data received from reading bluetooth values.
 49  ///
 50  /// Its presence indicates that this input is done.
 51  BleMeasurementData? _finishedData;
 52
 53  @override
 54  void initState() {
 55    super.initState();
 56    _bluetoothCubit = BluetoothCubit();
 57  }
 58
 59  @override
 60  void dispose() {
 61    unawaited(_bluetoothSubscription?.cancel());
 62    unawaited(_bluetoothCubit.close());
 63    unawaited(_deviceScanCubit?.close());
 64    unawaited(_deviceReadCubit?.close());
 65    super.dispose();
 66  }
 67
 68  void _returnToIdle() async {
 69    // No need to show wait in the UI.
 70    if (_isActive) {
 71      setState(() {
 72        _isActive = false;
 73        _finishedData = null;
 74      });
 75    }
 76
 77    await _deviceReadCubit?.close();
 78    _deviceReadCubit = null;
 79    await _deviceScanCubit?.close();
 80    _deviceScanCubit = null;
 81    await _bluetoothSubscription?.cancel();
 82    _bluetoothSubscription = null;
 83  }
 84
 85  Widget _buildActive(BuildContext context) {
 86    final Guid serviceUUID = Guid('1810');
 87    final Guid characteristicUUID = Guid('2A35');
 88    _bluetoothSubscription = _bluetoothCubit.stream.listen((state) {
 89      if (state is! BluetoothReady) {
 90        logger.finest('_OldBluetoothInputState: _bluetoothSubscription state=$state, calling _returnToIdle');
 91        _returnToIdle();
 92      }
 93    });
 94    final settings = context.watch<Settings>();
 95    _deviceScanCubit ??= DeviceScanCubit(
 96      service: serviceUUID,
 97      settings: settings,
 98    );
 99    return BlocBuilder<DeviceScanCubit, DeviceScanState>(
100      bloc: _deviceScanCubit,
101      builder: (context, DeviceScanState state) {
102        logger.finest('OldBluetoothInput _OldBluetoothInputState _deviceScanCubit: $state');
103        const SizeChangedLayoutNotification().dispatch(context);
104        return switch(state) {
105          DeviceListLoading() => _buildMainCard(context,
106            title: Text(AppLocalizations.of(context)!.scanningForDevices),
107            child: const CircularProgressIndicator(),
108          ),
109          DeviceListAvailable() => DeviceSelection(
110            scanResults: state.devices,
111            onAccepted: (dev) => _deviceScanCubit!.acceptDevice(dev),
112          ),
113          SingleDeviceAvailable() => DeviceSelection(
114            scanResults: [ state.device ],
115            onAccepted: (dev) => _deviceScanCubit!.acceptDevice(dev),
116          ),
117            // distinction
118          DeviceSelected() => BlocConsumer<BleReadCubit, BleReadState>(
119            bloc: () {
120              _deviceReadCubit = BleReadCubit(
121                state.device,
122                characteristicUUID: characteristicUUID,
123                serviceUUID: serviceUUID,
124              );
125              return _deviceReadCubit;
126            }(),
127            listener: (BuildContext context, BleReadState state) {
128              if (state is BleReadSuccess) {
129                final BloodPressureRecord record = BloodPressureRecord(
130                  time: state.data.timestamp ?? DateTime.now(),
131                  sys: state.data.isMMHG
132                    ? Pressure.mmHg(state.data.systolic.toInt())
133                    : Pressure.kPa(state.data.systolic),
134                  dia: state.data.isMMHG
135                    ? Pressure.mmHg(state.data.diastolic.toInt())
136                    : Pressure.kPa(state.data.diastolic),
137                  pul: state.data.pulse?.toInt(),
138                );
139                widget.onMeasurement(record);
140                setState(() {
141                  _finishedData = state.data;
142                });
143              }
144            },
145            builder: (BuildContext context, BleReadState state) {
146              logger.finest('_OldBluetoothInputState BleReadCubit: $state');
147              const SizeChangedLayoutNotification().dispatch(context);
148              return switch (state) {
149                BleReadInProgress() => _buildMainCard(context,
150                  child: const CircularProgressIndicator(),
151                ),
152                BleReadFailure() => MeasurementFailure(
153                  onTap: _returnToIdle,
154                ),
155                BleReadSuccess() => MeasurementSuccess(
156                  onTap: _returnToIdle,
157                  data: state.data,
158                ),
159              };
160            },
161          ),
162        };
163      },
164    );
165  }
166
167  @override
168  Widget build(BuildContext context) {
169    const SizeChangedLayoutNotification().dispatch(context);
170    if (_finishedData != null) {
171      return MeasurementSuccess(
172        onTap: _returnToIdle,
173        data: _finishedData!,
174      );
175    }
176    if (_isActive) return _buildActive(context);
177    return ClosedBluetoothInput(
178      bluetoothCubit: _bluetoothCubit,
179      onStarted: () async {
180        setState(() =>_isActive = true);
181      },
182      inputInfo: () async {
183        if (context.mounted) {
184          await showDialog(
185            context: context,
186            builder: (BuildContext context) => AlertDialog(
187              title: Text(AppLocalizations.of(context)!.bluetoothInput),
188              content: Text(AppLocalizations.of(context)!.aboutBleInput),
189                actions: <Widget>[
190                  ElevatedButton(
191                    child: Text((AppLocalizations.of(context)!.btnConfirm)),
192                    onPressed: () => Navigator.of(context).pop(),
193                  ),
194                ],
195            ),
196          );
197        }
198      },
199    );
200  }
201
202  Widget _buildMainCard(BuildContext context, {
203    required Widget child,
204    Widget? title,
205  }) => InputCard(
206    onClosed: _returnToIdle,
207    title: title,
208    child: child,
209  );
210}