Commit 7a6fea6

derdilla <contact@derdilla.com>
2024-09-11 20:04:44
Implement simple weight input (#429)
* implement simple weight input * fix and test bodyweight dialoge * test changes to measurement dialoge
1 parent 56cfaca
app/lib/features/input/add_bodyweight_dialoge.dart
@@ -0,0 +1,42 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:health_data_store/health_data_store.dart';
+
+/// A simple dialoge to enter one weight value in kg.
+///
+/// Returns a [Weight] on submission.
+class AddBodyweightDialoge extends StatelessWidget {
+  /// Create a simple dialoge to enter one weight value in kg.
+  const AddBodyweightDialoge({super.key});
+
+  @override
+  Widget build(BuildContext context) => Dialog(
+    child: Padding(
+      padding: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 2.0),
+      child: TextFormField(
+        autofocus: true,
+        decoration: InputDecoration(
+          labelText: AppLocalizations.of(context)!.weight,
+          suffix: const Text('kg'),
+        ),
+        inputFormatters: [FilteringTextInputFormatter.allow(RegExp('[0-9,.]'))],
+        keyboardType: const TextInputType.numberWithOptions(decimal: true),
+        autovalidateMode: AutovalidateMode.onUnfocus,
+        validator: (value) {
+          if (value == null
+            || value.isEmpty
+            || double.tryParse(value) == null
+          ) {
+            return AppLocalizations.of(context)!.errNaN;
+          }
+          return null;
+        },
+        onFieldSubmitted: (text) {
+          final value = double.tryParse(text);
+          if (value != null) Navigator.of(context).pop(Weight.kg(value));
+        },
+      ),
+    ),
+  );
+}
app/lib/features/input/add_measurement_dialoge.dart
@@ -2,6 +2,7 @@ import 'dart:async';
 
 import 'package:blood_pressure_app/components/fullscreen_dialoge.dart';
 import 'package:blood_pressure_app/features/bluetooth/bluetooth_input.dart';
+import 'package:blood_pressure_app/features/input/add_bodyweight_dialoge.dart';
 import 'package:blood_pressure_app/features/input/forms/date_time_form.dart';
 import 'package:blood_pressure_app/features/settings/tiles/color_picker_list_tile.dart';
 import 'package:blood_pressure_app/model/blood_pressure/pressure_unit.dart';
@@ -403,6 +404,21 @@ class _AddEntryDialogeState extends State<AddEntryDialoge> {
                   ),
                 ),
               ),
+
+            if (settings.weightInput)
+              ListTile(
+                title: Text(localizations.enterWeight),
+                leading: const Icon(Icons.scale),
+                trailing: const Icon(Icons.arrow_forward_ios),
+                onTap: () async {
+                  final repo = context.read<BodyweightRepository>();
+                  final weight = await showDialog<Weight>(context: context, builder: (_) => const AddBodyweightDialoge());
+                  if (weight != null) {
+                    await repo.add(BodyweightRecord(time: time, weight: weight));
+                    if (context.mounted) Navigator.pop(context);
+                  }
+                },
+              ),
           ],
         ),
       ),
app/lib/features/measurement_list/weight_list.dart
@@ -29,7 +29,7 @@ class WeightList extends StatelessWidget {
         );
       },
     );
-  }
+  }// TODO: delete
 
   String _buildWeightText(Weight w) {
     String weightStr = w.kg.toStringAsFixed(2);
app/lib/l10n/app_en.arb
@@ -518,5 +518,9 @@
   "errCantCreateArchive": "Can''t create archive. Please report the bug if possible.",
   "@errCantCreateArchive": {},
   "activateWeightFeatures": "Activate weight related features",
-  "@weightInput": {}
+  "@weightInput": {},
+  "weight": "Weight",
+  "@weight": {},
+  "enterWeight": "Enter weight",
+  "@enterWeight": {}
 }
\ No newline at end of file
app/test/features/input/add_bodyweight_dialoge_test.dart
@@ -0,0 +1,49 @@
+import 'package:blood_pressure_app/features/input/add_bodyweight_dialoge.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:health_data_store/health_data_store.dart';
+
+import '../../util.dart';
+
+void main() {
+  testWidgets('shows weight input weight', (tester) async {
+    await tester.pumpWidget(materialApp(const AddBodyweightDialoge()));
+    final localizations = await AppLocalizations.delegate.load(const Locale('en'));
+
+    expect(find.byType(TextFormField), findsOneWidget);
+    expect(find.text(localizations.weight), findsOneWidget);
+    expect(find.text('kg'), findsOneWidget);
+
+    await tester.enterText(find.byType(TextFormField), '123.45');
+  });
+  testWidgets('error on invalid input', (tester) async {
+    await tester.pumpWidget(materialApp(const AddBodyweightDialoge()));
+    final localizations = await AppLocalizations.delegate.load(const Locale('en'));
+
+    expect(find.text(localizations.errNaN), findsNothing);
+
+    await tester.enterText(find.byType(TextFormField), 'invalid input');
+    await tester.testTextInput.receiveAction(TextInputAction.done);
+    await tester.pumpAndSettle();
+
+    expect(find.text(localizations.errNaN), findsOneWidget);
+  });
+  testWidgets('creates weight from input', (tester) async {
+    Weight? res;
+    await tester.pumpWidget(materialApp(Builder(
+      builder: (context) => GestureDetector(
+        onTap: () async => res = await showDialog<Weight>(context: context, builder: (_) => const AddBodyweightDialoge()),
+        child: const Text('X'),
+      ),
+    )));
+    await tester.tap(find.text('X'));
+    await tester.pumpAndSettle();
+
+    await tester.enterText(find.byType(TextFormField), '123.45');
+    await tester.testTextInput.receiveAction(TextInputAction.done);
+    await tester.pumpAndSettle();
+
+    expect(res, Weight.kg(123.45));
+  });
+}
app/test/features/input/add_measurement_dialoge_test.dart
@@ -1,4 +1,5 @@
 import 'package:blood_pressure_app/features/bluetooth/bluetooth_input.dart';
+import 'package:blood_pressure_app/features/input/add_bodyweight_dialoge.dart';
 import 'package:blood_pressure_app/features/input/add_measurement_dialoge.dart';
 import 'package:blood_pressure_app/features/settings/tiles/color_picker_list_tile.dart';
 import 'package:blood_pressure_app/model/storage/settings_store.dart';
@@ -634,5 +635,29 @@ void main() {
       expect(find.descendant(of: focusedTextFormField, matching: find.text('Pulse')), findsNothing);
       expect(find.descendant(of: focusedTextFormField, matching: find.text('Note (optional)')), findsWidgets);
     });
+    testWidgets('opens weight input if necessary', (tester) async {
+      final repo = MockBodyweightRepository();
+      await tester.pumpWidget(appBase(Builder(
+        builder: (context) => TextButton(onPressed: () => showAddEntryDialoge(context, MockMedRepo([])), child: const Text('X'))
+      ), settings: Settings(weightInput: true), weightRepo: repo));
+      final localizations = await AppLocalizations.delegate.load(const Locale('en'));
+      await tester.tap(find.text('X'));
+      await tester.pumpAndSettle();
+
+      expect(find.text(localizations.enterWeight), findsOneWidget);
+      expect(find.byIcon(Icons.scale), findsOneWidget);
+      await tester.tap(find.text(localizations.enterWeight));
+      await tester.pumpAndSettle();
+      
+      expect(repo.data, isEmpty);
+      await tester.enterText(find.descendant(
+        of: find.byType(AddBodyweightDialoge),
+        matching: find.byType(TextFormField)
+      ), '123.45');
+      await tester.testTextInput.receiveAction(TextInputAction.done);
+      await tester.pumpAndSettle();
+      expect(repo.data, hasLength(1));
+      expect(repo.data[0].weight, Weight.kg(123.45));
+    });
   });
 }