Commit 91fbcf9
Changed files (2)
app
lib
components
test
ui
components
app/lib/components/bluetooth_input.dart
@@ -22,6 +22,9 @@ class BluetoothInput extends StatefulWidget {
const BluetoothInput({super.key,
required this.settings,
required this.onMeasurement,
+ this.bluetoothCubit,
+ this.deviceScanCubit,
+ this.bleReadCubit,
});
/// Settings to store known devices.
@@ -30,6 +33,15 @@ class BluetoothInput extends StatefulWidget {
/// Called when a measurement was received through bluetooth.
final void Function(BloodPressureRecord data) onMeasurement;
+ /// Function to customize [BluetoothCubit] creation.
+ final BluetoothCubit Function()? bluetoothCubit;
+
+ /// Function to customize [DeviceScanCubit] creation.
+ final DeviceScanCubit Function()? deviceScanCubit;
+
+ /// Function to customize [BleReadCubit] creation.
+ final BleReadCubit Function()? bleReadCubit;
+
@override
State<BluetoothInput> createState() => _BluetoothInputState();
}
@@ -38,11 +50,18 @@ class _BluetoothInputState extends State<BluetoothInput> {
/// Whether the user expanded bluetooth input
bool _isActive = false;
- final BluetoothCubit _bluetoothCubit = BluetoothCubit();
- StreamSubscription<BluetoothState>? _bluetoothSubscription;
+ late final BluetoothCubit _bluetoothCubit;
DeviceScanCubit? _deviceScanCubit;
BleReadCubit? _deviceReadCubit;
+ StreamSubscription<BluetoothState>? _bluetoothSubscription;
+
+ @override
+ void initState() {
+ super.initState();
+ _bluetoothCubit = widget.bluetoothCubit?.call() ?? BluetoothCubit();
+ }
+
@override
void dispose() {
unawaited(_bluetoothSubscription?.cancel());
@@ -53,17 +72,19 @@ class _BluetoothInputState extends State<BluetoothInput> {
}
void _returnToIdle() async {
- await _bluetoothSubscription?.cancel();
- _bluetoothSubscription = null;
- await _deviceScanCubit?.close();
- _deviceScanCubit = null;
- await _deviceReadCubit?.close();
- _deviceReadCubit = null;
+ // No need to show wait in the UI.
if (_isActive) {
setState(() {
_isActive = false;
});
}
+
+ await _deviceReadCubit?.close();
+ _deviceReadCubit = null;
+ await _deviceScanCubit?.close();
+ _deviceScanCubit = null;
+ await _bluetoothSubscription?.cancel();
+ _bluetoothSubscription = null;
}
Widget _buildActive(BuildContext context) {
@@ -75,7 +96,7 @@ class _BluetoothInputState extends State<BluetoothInput> {
_returnToIdle();
}
});
- _deviceScanCubit ??= DeviceScanCubit(
+ _deviceScanCubit ??= widget.deviceScanCubit?.call() ?? DeviceScanCubit(
service: serviceUUID,
settings: widget.settings,
);
@@ -99,7 +120,7 @@ class _BluetoothInputState extends State<BluetoothInput> {
// distinction
DeviceSelected() => BlocConsumer<BleReadCubit, BleReadState>(
bloc: (){
- _deviceReadCubit = BleReadCubit(state.device,
+ _deviceReadCubit = widget.bleReadCubit?.call() ?? BleReadCubit(state.device,
characteristicUUID: characteristicUUID,
serviceUUID: serviceUUID,
);
app/test/ui/components/bluetooth_input_test.dart
@@ -0,0 +1,110 @@
+import 'package:bloc_test/bloc_test.dart';
+import 'package:blood_pressure_app/bluetooth/ble_read_cubit.dart';
+import 'package:blood_pressure_app/bluetooth/bluetooth_cubit.dart';
+import 'package:blood_pressure_app/bluetooth/characteristics/ble_measurement_data.dart';
+import 'package:blood_pressure_app/bluetooth/device_scan_cubit.dart';
+import 'package:blood_pressure_app/components/bluetooth_input.dart';
+import 'package:blood_pressure_app/components/bluetooth_input/closed_bluetooth_input.dart';
+import 'package:blood_pressure_app/components/bluetooth_input/measurement_success.dart';
+import 'package:blood_pressure_app/model/blood_pressure/record.dart';
+import 'package:blood_pressure_app/model/storage/storage.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_blue_plus/flutter_blue_plus.dart' hide BluetoothState;
+import 'package:flutter_test/flutter_test.dart';
+
+import 'util.dart';
+
+class _MockBluetoothCubit extends MockCubit<BluetoothState>
+ implements BluetoothCubit {}
+class _MockDeviceScanCubit extends MockCubit<DeviceScanState>
+ implements DeviceScanCubit {}
+class _MockBleReadCubit extends MockCubit<BleReadState>
+ implements BleReadCubit {}
+
+void main() {
+ testWidgets('propagates successful read', (WidgetTester tester) async {
+ final bluetoothCubit = _MockBluetoothCubit();
+ whenListen(bluetoothCubit, Stream<BluetoothState>.fromIterable([BluetoothReady()]),
+ initialState: BluetoothReady());
+ final deviceScanCubit = _MockDeviceScanCubit();
+ final devScanOk = DeviceSelected(BluetoothDevice(remoteId: DeviceIdentifier('tstDev')));
+ whenListen(deviceScanCubit, Stream<DeviceScanState>.fromIterable([devScanOk]),
+ initialState: devScanOk);
+ final bleReadCubit = _MockBleReadCubit();
+ final bleReadOk = BleReadSuccess(BleMeasurementData(
+ systolic: 123,
+ diastolic: 45,
+ meanArterialPressure: 67,
+ isMMHG: true,
+ pulse: null,
+ userID: null,
+ status: null,
+ timestamp: null,
+ ));
+ whenListen(bleReadCubit, Stream<BleReadState>.fromIterable([bleReadOk]),
+ initialState: bleReadOk,
+ );
+
+ final List<BloodPressureRecord> reads = [];
+ await tester.pumpWidget(materialApp(BluetoothInput(
+ settings: Settings(),
+ onMeasurement: reads.add,
+ bluetoothCubit: () => bluetoothCubit,
+ deviceScanCubit: () => deviceScanCubit,
+ bleReadCubit: () => bleReadCubit,
+ )));
+
+ await tester.tap(find.byType(ClosedBluetoothInput));
+ await tester.pumpAndSettle();
+ expect(find.byType(ClosedBluetoothInput), findsNothing);
+
+ expect(reads, hasLength(1));
+ expect(reads, contains(isA<BloodPressureRecord>()
+ .having((p) => p.systolic, 'sys', 123)
+ .having((p) => p.diastolic, 'dia', 45)
+ .having((p) => p.pulse, 'pul', isNull),
+ ));
+ });
+
+ testWidgets('allows closing after successful read', (WidgetTester tester) async {
+ final bluetoothCubit = _MockBluetoothCubit();
+ whenListen(bluetoothCubit, Stream<BluetoothState>.fromIterable([BluetoothReady()]),
+ initialState: BluetoothReady());
+ final deviceScanCubit = _MockDeviceScanCubit();
+ final devScanOk = DeviceSelected(BluetoothDevice(remoteId: DeviceIdentifier('tstDev')));
+ whenListen(deviceScanCubit, Stream<DeviceScanState>.fromIterable([devScanOk]),
+ initialState: devScanOk);
+ final bleReadCubit = _MockBleReadCubit();
+ final bleReadOk = BleReadSuccess(BleMeasurementData(
+ systolic: 123,
+ diastolic: 45,
+ meanArterialPressure: 67,
+ isMMHG: true,
+ pulse: null,
+ userID: null,
+ status: null,
+ timestamp: null,
+ ));
+ whenListen(bleReadCubit, Stream<BleReadState>.fromIterable([bleReadOk]),
+ initialState: bleReadOk,
+ );
+
+ final List<BloodPressureRecord> reads = [];
+ await tester.pumpWidget(materialApp(BluetoothInput(
+ settings: Settings(),
+ onMeasurement: reads.add,
+ bluetoothCubit: () => bluetoothCubit,
+ deviceScanCubit: () => deviceScanCubit,
+ bleReadCubit: () => bleReadCubit,
+ )));
+
+ await tester.tap(find.byType(ClosedBluetoothInput));
+ await tester.pumpAndSettle();
+ expect(find.byType(ClosedBluetoothInput), findsNothing);
+ expect(find.byType(MeasurementSuccess), findsOneWidget);
+
+ await tester.tap(find.byIcon(Icons.close));
+ await tester.pumpAndSettle();
+ expect(find.byType(ClosedBluetoothInput), findsOneWidget);
+ });
+}