Commit a4a9959

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-06-16 14:53:32
reduce introduced tech dept
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent f46c909
Changed files (9)
app/lib/bluetooth/characteristics/ble_measurement_data.dart
@@ -88,7 +88,7 @@ class BleMeasurementData {
       systolic: systolic,
       diastolic: diastolic,
       meanArterialPressure: meanArterialPressure,
-      isMMHG: isMMHG, // TODO: use
+      isMMHG: isMMHG,
       pulse: pulse,
       userID: userId,
       status: status,
app/lib/bluetooth/characteristics/decoding_util.dart
@@ -11,7 +11,8 @@ bool isBitIntByteSet(int byte, int offset) =>
 double? readSFloat(List<int> data, int offset) {
   if (data.length < offset + 2) return null;
   // TODO: special values (NaN, Infinity)
-  final mantissa = data[offset] + ((data[offset + 1] & 0x0F) << 8); // TODO: https://github.com/NordicSemiconductor/Kotlin-BLE-Library/blob/6b565e59de21dfa53ef80ff8351ac4a4550e8d58/core/src/main/java/no/nordicsemi/android/kotlin/ble/core/data/util/DataByteArray.kt#L392
+  // If this ever stops working: https://github.com/NordicSemiconductor/Kotlin-BLE-Library/blob/6b565e59de21dfa53ef80ff8351ac4a4550e8d58/core/src/main/java/no/nordicsemi/android/kotlin/ble/core/data/util/DataByteArray.kt#L392
+  final mantissa = data[offset] + ((data[offset + 1] & 0x0F) << 8);
   final exponent = data[offset + 1] >> 4;
   return (mantissa * (pow(10, exponent))).toDouble();
 }
app/lib/bluetooth/ble_read_cubit.dart
@@ -9,7 +9,7 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart';
 
 part 'ble_read_state.dart';
 
-/// Logic for reading a characteristic from a device.
+/// Logic for reading a characteristic from a device through a "indication".
 ///
 /// May only be used on devices that are fully connected.
 ///
@@ -29,11 +29,13 @@ part 'ble_read_state.dart';
 /// 5. Emit decoded object
 class BleReadCubit extends Cubit<BleReadState> {
   /// Start reading a characteristic from a device.
-  BleReadCubit(this._device)
+  BleReadCubit(this._device, {
+    required this.serviceUUID,
+    required this.characteristicUUID,
+  })
     : super(BleReadInProgress()){
     _subscription = _device.connectionState
       .listen(_onConnectionStateChanged);
-    _device.cancelWhenDisconnected(_device.mtu.listen((int mtu) => Log.trace('BleReadCubit mtu: $mtu'))); // TODO: remove
     // timeout
     _timeoutTimer = Timer(const Duration(minutes: 2), () {
       if (state is BleReadInProgress) {
@@ -44,9 +46,12 @@ class BleReadCubit extends Cubit<BleReadState> {
       }
     });
   }
-  
-  static const String _kServiceID = '1810';
-  static const String _kCharacteristicID = '2A35';
+
+  /// UUID of the service to read.
+  final Guid serviceUUID;
+
+  /// UUID of the characteristic to read.
+  final Guid characteristicUUID;
 
   /// Bluetooth device to connect to.
   ///
@@ -138,7 +143,7 @@ class BleReadCubit extends Cubit<BleReadState> {
     // [Guid.str] trims standard parts from the uuid. 0x1810 is the blood
     // pressure uuid. https://developer.nordicsemi.com/nRF51_SDK/nRF51_SDK_v4.x.x/doc/html/group___u_u_i_d___s_e_r_v_i_c_e_s.html
     final BluetoothService? service = allServices
-      .firstWhereOrNull((BluetoothService s) => s.uuid.str == _kServiceID);
+      .firstWhereOrNull((BluetoothService s) => s.uuid == serviceUUID);
     if (service == null) {
       Log.err('unsupported service', [_device, allServices]);
       emit(BleReadFailure());
@@ -149,7 +154,7 @@ class BleReadCubit extends Cubit<BleReadState> {
     final List<BluetoothCharacteristic> allCharacteristics = service.characteristics;
     Log.trace('BleReadCubit allCharacteristics: $allCharacteristics');
     final BluetoothCharacteristic? characteristic = allCharacteristics
-      .firstWhereOrNull((c) => c.uuid == Guid(_kCharacteristicID),);
+      .firstWhereOrNull((c) => c.uuid == characteristicUUID,);
     if (characteristic == null) {
       Log.err('no characteristic', [_device, allServices, allCharacteristics]);
       emit(BleReadFailure());
app/lib/components/bluetooth_input/device_selection.dart
@@ -30,7 +30,7 @@ class DeviceSelection extends StatelessWidget {
   Widget build(BuildContext context) {
     assert(scanResults.isNotEmpty);
     return InputCard(
-      //title: Text('Available devices:'), TODO
+      title: Text(AppLocalizations.of(context)!.availableDevices),
       child: ListView(
         shrinkWrap: true,
         children: [
app/lib/components/bluetooth_input/measurement_success.dart
@@ -36,42 +36,42 @@ class MeasurementSuccess extends StatelessWidget {
                 style: Theme.of(context).textTheme.titleMedium,),
               const SizedBox(height: 8,),
               ListTile(
-                title: Text('Mean arterial pressure'), // TODO: localizations and testing
-                subtitle: Text(data.meanArterialPressure.toString()),
+                title: Text(AppLocalizations.of(context)!.meanArterialPressure),
+                subtitle: Text(data.meanArterialPressure.round().toString()),
               ),
               if (data.userID != null)
                 ListTile(
-                  title: Text('User ID'),
+                  title: Text(AppLocalizations.of(context)!.userID),
                   subtitle: Text(data.userID!.toString()),
                 ),
               if (data.status?.bodyMovementDetected ?? false)
                 ListTile(
-                  title: Text('Body movement detected'),
+                  title: Text(AppLocalizations.of(context)!.bodyMovementDetected),
                   leading: Icon(Icons.directions_walk),
                 ),
               if (data.status?.cuffTooLose ?? false)
                 ListTile(
-                  title: Text('Cuff too loose'),
+                  title: Text(AppLocalizations.of(context)!.cuffTooLoose),
                   leading: Icon(Icons.space_bar),
                 ),
               if (data.status?.improperMeasurementPosition ?? false)
                 ListTile(
-                  title: Text('Improper measurement position'),
+                  title: Text(AppLocalizations.of(context)!.improperMeasurementPosition),
                   leading: Icon(Icons.emoji_people),
                 ),
               if (data.status?.irregularPulseDetected ?? false)
                 ListTile(
-                  title: Text('Irregular pulse detected'),
+                  title: Text(AppLocalizations.of(context)!.irregularPulseDetected),
                   leading: Icon(Icons.heart_broken),
                 ),
               if (data.status?.pulseRateExceedsUpperLimit ?? false)
                 ListTile(
-                  title: Text('Pulse rate exceeds upper limit'),
+                  title: Text(AppLocalizations.of(context)!.pulseRateExceedsUpperLimit),
                   leading: Icon(Icons.monitor_heart),
                 ),
               if (data.status?.pulseRateIsLessThenLowerLimit ?? false)
                 ListTile(
-                  title: Text('Pulse rate is less than lower limit'),
+                  title: Text(AppLocalizations.of(context)!.pulseRateLessThanLowerLimit),
                   leading: Icon(Icons.monitor_heart),
                 ),
               const SizedBox(height: 8,),
app/lib/components/bluetooth_input.dart
@@ -67,6 +67,8 @@ class _BluetoothInputState extends State<BluetoothInput> {
   }
 
   Widget _buildActive(BuildContext context) {
+    final Guid serviceUUID = Guid('1810');
+    final Guid characteristicUUID = Guid('1810');
     _bluetoothSubscription = _bluetoothCubit.stream.listen((state) {
       if (state is! BluetoothReady) {
         Log.trace('_BluetoothInputState: _bluetoothSubscription state=$state, calling _returnToIdle');
@@ -74,7 +76,7 @@ class _BluetoothInputState extends State<BluetoothInput> {
       }
     });
     _deviceScanCubit ??= DeviceScanCubit(
-      service: Guid('1810'), // TODO one source of truth (w read cubit)
+      service: serviceUUID,
       settings: widget.settings,
     );
     return BlocBuilder<DeviceScanCubit, DeviceScanState>(
@@ -97,7 +99,10 @@ class _BluetoothInputState extends State<BluetoothInput> {
             // distinction
           DeviceSelected() => BlocConsumer<BleReadCubit, BleReadState>(
             bloc: (){
-              _deviceReadCubit = BleReadCubit(state.device);
+              _deviceReadCubit = BleReadCubit(state.device,
+                characteristicUUID: characteristicUUID,
+                serviceUUID: serviceUUID,
+              );
               return _deviceReadCubit;
             }(),
             listener: (BuildContext context, BleReadState state) {
@@ -117,7 +122,6 @@ class _BluetoothInputState extends State<BluetoothInput> {
               return switch (state) {
                 BleReadInProgress() => _buildMainCard(context,
                   child: const CircularProgressIndicator(),
-                  // TODO: onTap to retry
                 ),
                 BleReadFailure() => MeasurementFailure(
                   onTap: _returnToIdle,
app/lib/l10n/app_en.arb
@@ -530,5 +530,23 @@
   "scanningForDevices": "Scanning for devices",
   "@scanningForDevices": {},
   "tapToClose": "Tap to close.",
-  "@tapToClose": {}
+  "@tapToClose": {},
+  "meanArterialPressure": "Mean arterial pressure",
+  "@meanArterialPressure": {},
+  "userID": "User ID",
+  "@userID": {},
+  "bodyMovementDetected": "Body movement detected",
+  "@bodyMovementDetected": {},
+  "cuffTooLoose": "Cuff too loose",
+  "@cuffTooLoose": {},
+  "improperMeasurementPosition": "Improper measurement position",
+  "@improperMeasurementPosition": {},
+  "irregularPulseDetected": "Irregular pulse detected",
+  "@irregularPulseDetected": {},
+  "pulseRateExceedsUpperLimit": "Pulse rate exceeds upper limit",
+  "@pulseRateExceedsUpperLimit": {},
+  "pulseRateLessThanLowerLimit": "Pulse rate is less than lower limit",
+  "@pulseRateLessThanLowerLimit": {},
+  "availableDevices": "Available devices",
+  "@availableDevices": {}
 }
app/lib/main.dart
@@ -11,7 +11,6 @@ import 'package:blood_pressure_app/model/storage/settings_store.dart';
 import 'package:blood_pressure_app/model/storage/update_legacy_settings.dart';
 import 'package:blood_pressure_app/screens/home_screen.dart';
 import 'package:blood_pressure_app/screens/loading_screen.dart';
-import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:fluttertoast/fluttertoast.dart';
@@ -23,20 +22,8 @@ import 'package:sqflite/sqflite.dart';
 late final ConfigDB _database;
 late final BloodPressureModel _bloodPressureModel;
 
-// TODO: remove
-final debugLog = <String>[];
 
 void main() async {
-  // TODO: remove
-  FlutterError.onError = (details) {
-    FlutterError.presentError(details);
-    debugLog.add('FLUTTER: {{$details}}');
-  };
-  PlatformDispatcher.instance.onError = (error, stack) {
-    debugLog.add('PLATFORM: {{$error||$stack}}');
-    return true;
-  };
-
   runApp(ConsistentFutureBuilder(
       future: _loadApp(),
       onWaiting: const LoadingScreen(),
app/test/ui/components/bluetooth_input/measurement_success_test.dart
@@ -18,7 +18,7 @@ void main() {
         systolic: 123,
         diastolic: 456,
         pulse: 67,
-        meanArterialPressure: 123,
+        meanArterialPressure: 123456,
         isMMHG: true,
         userID: 3,
         status: BleMeasurementStatus(
@@ -33,13 +33,23 @@ void main() {
         timestamp: DateTime.now(),
       ),
     )));
-    // TODO
 
     expect(find.byIcon(Icons.done), findsOneWidget);
     expect(find.byIcon(Icons.close), findsOneWidget);
     final localizations = await AppLocalizations.delegate.load(const Locale('en'));
     expect(find.text(localizations.measurementSuccess), findsOneWidget);
 
+    expect(find.text(localizations.meanArterialPressure), findsOneWidget);
+    expect(find.text('123456'), findsOneWidget);
+
+    expect(find.text(localizations.userID), findsOneWidget);
+    expect(find.text(localizations.bodyMovementDetected), findsOneWidget);
+    expect(find.text(localizations.cuffTooLoose), findsOneWidget);
+    expect(find.text(localizations.improperMeasurementPosition), findsOneWidget);
+    expect(find.text(localizations.irregularPulseDetected), findsOneWidget);
+    expect(find.text(localizations.pulseRateExceedsUpperLimit), findsOneWidget);
+    expect(find.text(localizations.pulseRateLessThanLowerLimit), findsOneWidget);
+
     expect(tapCount, 0);
     await tester.tap(find.text(localizations.measurementSuccess));
     await tester.pump();
@@ -48,5 +58,36 @@ void main() {
     await tester.pump();
     expect(tapCount, 2);
   });
-  // TODO: works when not everything shown
+  testWidgets('hides elements correctly', (WidgetTester tester) async {
+    int tapCount = 0;
+    await tester.pumpWidget(materialApp(MeasurementSuccess(
+      onTap: () => tapCount++,
+      data: BleMeasurementData(
+        systolic: 123,
+        diastolic: 456,
+        pulse: null,
+        meanArterialPressure: 123456,
+        isMMHG: true,
+        userID: null,
+        status: null,
+        timestamp: null,
+      ),
+    )));
+
+    expect(find.byIcon(Icons.done), findsOneWidget);
+    expect(find.byIcon(Icons.close), findsOneWidget);
+    final localizations = await AppLocalizations.delegate.load(const Locale('en'));
+    expect(find.text(localizations.measurementSuccess), findsOneWidget);
+
+    expect(find.text(localizations.meanArterialPressure), findsOneWidget);
+    expect(find.text('123456'), findsOneWidget);
+
+    expect(find.text(localizations.userID), findsNothing);
+    expect(find.text(localizations.bodyMovementDetected), findsNothing);
+    expect(find.text(localizations.cuffTooLoose), findsNothing);
+    expect(find.text(localizations.improperMeasurementPosition), findsNothing);
+    expect(find.text(localizations.irregularPulseDetected), findsNothing);
+    expect(find.text(localizations.pulseRateExceedsUpperLimit), findsNothing);
+    expect(find.text(localizations.pulseRateLessThanLowerLimit), findsNothing);
+  });
 }