Commit f9205db

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-06-27 15:16:34
fix MedicineManager updating
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent af23ee2
Changed files (5)
app
lib
screens
test
ui
components
health_data_store
app/lib/screens/subsettings/medicine_manager_screen.dart
@@ -1,4 +1,6 @@
+import 'package:blood_pressure_app/components/consistent_future_builder.dart';
 import 'package:blood_pressure_app/components/dialoges/add_medication_dialoge.dart';
+import 'package:blood_pressure_app/components/dialoges/confirm_deletion_dialoge.dart';
 import 'package:blood_pressure_app/model/storage/settings_store.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -9,25 +11,49 @@ import 'package:health_data_store/health_data_store.dart';
 ///
 /// This screen allows adding and removing medication but not modifying them in
 /// order to keep the code simple and maintainable.
-class MedicineManagerScreen extends StatefulWidget {
+class MedicineManagerScreen extends StatelessWidget {
   /// Create a screen to manage medications in settings.
   const MedicineManagerScreen({super.key});
 
-  @override
-  State<MedicineManagerScreen> createState() => _MedicineManagerScreenState();
-}
-
-class _MedicineManagerScreenState extends State<MedicineManagerScreen> {
-  List<Medicine> medicines = [];
+  Widget _buildMedicine(BuildContext context, Medicine med) => ListTile(
+    leading: med.color == Colors.transparent.value
+        || med.color == null
+        ? null
+        : Container(
+      width: 40.0,
+      height: 40.0,
+      decoration: BoxDecoration(
+        color: Color(med.color!),
+        shape: BoxShape.circle,
+      ),
+    ),
+    title: Text(med.designation),
+    subtitle: med.dosis == null ? null
+        : Text('${AppLocalizations.of(context)!.defaultDosis}: '
+        '${med.dosis!.mg} mg'),
+    trailing: IconButton(
+      icon: const Icon(Icons.delete),
+      onPressed: () async {
+        if (await showConfirmDeletionDialoge(context)) {
+          await RepositoryProvider.of<MedicineRepository>(context).remove(med);
+        }
+      },
+    ),
+  );
 
-  @override
-  void initState() {
-    super.initState();
-    RepositoryProvider.of<MedicineRepository>(context).getAll()
-        .then((value) => setState(() => medicines.addAll(value)));
-  }
+  Widget _buildAddMed(BuildContext context) => ListTile(
+    leading: const Icon(Icons.add),
+    title: Text(AppLocalizations.of(context)!.addMedication),
+    onTap: () async {
+      final medRepo = RepositoryProvider.of<MedicineRepository>(context);
+      final medicine = await showAddMedicineDialoge(context);
+      if (medicine != null) {
+        await medRepo.add(medicine);
+      }
+    },
+  );
 
-  @override
+ @override
   Widget build(BuildContext context) {
     final localizations = AppLocalizations.of(context)!;
     return Scaffold(
@@ -35,55 +61,20 @@ class _MedicineManagerScreenState extends State<MedicineManagerScreen> {
         forceMaterialTransparency: true,
       ),
       body: Center(
-        child: ListView.builder(
-          itemCount: medicines.length + 1,
-          itemBuilder: (context, i) {
-            if (i == medicines.length) { // last row
-              return ListTile(
-                leading: const Icon(Icons.add),
-                title: Text(localizations.addMedication),
-                onTap: () async {
-                  final medRepo = RepositoryProvider.of<MedicineRepository>(context);
-                  final medicine = await showAddMedicineDialoge(context);
-                  if (medicine != null) {
-                    setState(() {
-                      medicines.add(medicine);
-                      medRepo.add(medicine);
-                    });
-                  }
-                },
-              );
-            }
-            return ListTile(
-              leading: medicines[i].color == Colors.transparent.value
-                  || medicines[i].color == null
-                ? null
-                : Container(
-                  width: 40.0,
-                  height: 40.0,
-                  decoration: BoxDecoration(
-                    color: Color(medicines[i].color!),
-                    shape: BoxShape.circle,
-                  ),
-                ),
-              title: Text(medicines[i].designation),
-              // TODO: make localization function
-              subtitle: medicines[i].dosis == null ? null
-                  : Text('${localizations.defaultDosis}: '
-                         '${medicines[i].dosis!.mg} mg'),
-              trailing: IconButton(
-                icon: const Icon(Icons.delete),
-                onPressed: () async {
-                  await RepositoryProvider.of<MedicineRepository>(context)
-                    .remove(medicines[i]);
-                  setState(() async {
-                    medicines.removeAt(i);
-                    // FIXME: somehow no feedback
-                  });
-                },
-              ),
-            );
-          },
+        child: StreamBuilder(
+          stream: RepositoryProvider.of<MedicineRepository>(context).subscribe(),
+          builder: (context, _) => ConsistentFutureBuilder(
+            future: RepositoryProvider.of<MedicineRepository>(context).getAll(),
+            onData: (context, medicines) => ListView.builder(
+              itemCount: medicines.length + 1,
+              itemBuilder: (context, i) {
+                if (i == medicines.length) { // last row
+                  return _buildAddMed(context);
+                }
+                return _buildMedicine(context, medicines[i]);
+              },
+            ),
+          ),
         ),
       ),
     );
app/test/ui/components/util.dart
@@ -1,3 +1,5 @@
+import 'dart:async';
+
 import 'package:blood_pressure_app/model/storage/storage.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -132,14 +134,29 @@ class MockMedRepo implements MedicineRepository {
 
   final List<Medicine> _meds = [];
 
+  final _controller = StreamController.broadcast();
+
   @override
-  Future<void> add(Medicine medicine) async => _meds.add(medicine);
+  Future<void> add(Medicine medicine) async {
+    _meds.add(medicine);
+    _controller.add(null);
+  }
 
   @override
   Future<List<Medicine>> getAll() async=> _meds;
 
   @override
-  Future<void> remove(Medicine value) async => _meds.remove(value);
+  Future<void> remove(Medicine value) async {
+    _meds.remove(value);
+    _controller.add(null);
+  }
+
+  @override
+  @Deprecated('Medicines have no date. Use getAll directly')
+  Future<List<Medicine>> get(DateRange range) => getAll();
+
+  @override
+  Stream subscribe() => _controller.stream;
 }
 
 final List<Medicine> _meds = [];
health_data_store/lib/src/repositories/medicine_repository.dart
@@ -1,10 +1,12 @@
+import 'package:health_data_store/src/repositories/repository.dart';
 import 'package:health_data_store/src/types/medicine.dart';
 import 'package:health_data_store/src/types/medicine_intake.dart';
 
 /// Repository for medicines that are taken by the user.
-abstract class MedicineRepository {
+abstract class MedicineRepository extends Repository<Medicine> {
 
   /// Store a [Medicine] in the repository.
+  @override
   Future<void> add(Medicine medicine);
 
   /// Get a list of all stored Medicines that haven't been marked as removed.
@@ -15,6 +17,7 @@ abstract class MedicineRepository {
   /// Intakes will be deleted as soon as there is no [MedicineIntake]s
   /// referencing them. They need to be stored to allow intakes of them to be
   /// still displayed correctly.
+  @override
   Future<void> remove(Medicine value);
 
 }
health_data_store/lib/src/repositories/medicine_repository_impl.dart
@@ -1,6 +1,9 @@
+import 'dart:async';
+
 import 'package:health_data_store/src/database_manager.dart';
 import 'package:health_data_store/src/extensions/castable.dart';
 import 'package:health_data_store/src/repositories/medicine_repository.dart';
+import 'package:health_data_store/src/types/date_range.dart';
 import 'package:health_data_store/src/types/medicine.dart';
 import 'package:health_data_store/src/types/units/weight.dart';
 import 'package:sqflite_common/sqflite.dart';
@@ -13,10 +16,13 @@ class MedicineRepositoryImpl extends MedicineRepository {
   /// The [DatabaseManager] managed database.
   final Database _db;
 
+  final _controller = StreamController.broadcast();
+
   @override
   Future<void> add(Medicine medicine) => _db.transaction((txn) async {
     final idRes = await txn.query('Medicine', columns: ['MAX(medID)']);
     final id = (idRes.firstOrNull?['MAX(medID)']?.castOrNull<int>() ?? 0) + 1;
+    _controller.add(null);
     await txn.insert('Medicine', {
       'medID': id,
       'designation': medicine.designation,
@@ -45,25 +51,34 @@ class MedicineRepositoryImpl extends MedicineRepository {
   }
 
   @override
-  Future<void> remove(Medicine value) => _db.update('Medicine', {
-      'removed': 1,
-    },
-    where: 'designation = ? AND color '
-        + (value.color == null ? 'IS NULL' : '= ?')
-        + ' AND defaultDose '
-        + (value.dosis == null ? 'IS NULL' : '= ?'),
-    whereArgs: [
-      value.designation,
-      if (value.color != null)
-        value.color,
-      if (value.dosis != null)
-        value.dosis!.mg,
-    ],
-  );
+  Future<void> remove(Medicine value) async {
+    _controller.add(null);
+    await _db.update('Medicine', {
+        'removed': 1,
+      },
+      where: 'designation = ? AND color '
+          + (value.color == null ? 'IS NULL' : '= ?')
+          + ' AND defaultDose '
+          + (value.dosis == null ? 'IS NULL' : '= ?'),
+      whereArgs: [
+        value.designation,
+        if (value.color != null)
+          value.color,
+        if (value.dosis != null)
+          value.dosis!.mg,
+      ],
+    );
+  }
 
   Weight? _decode(Object? value) {
     if (value is! double) return null;
     return Weight.mg(value);
   }
 
+  @override
+  @Deprecated('Medicines have no date. Use getAll directly')
+  Future<List<Medicine>> get(DateRange _) => getAll();
+
+  @override
+  Stream subscribe() => _controller.stream;
 }
health_data_store/test/src/repositories/medicine_repository_test.dart
@@ -90,5 +90,20 @@ void main() {
     await repo.remove(med3);
     expect(await repo.getAll(), isEmpty);
   });
+  test('notifies on changes', () async {
+    final db = await mockDBManager();
+    addTearDown(db.close);
+    final repo = MedicineRepositoryImpl(db.db);
+    int calls = 0;
+    repo.subscribe().listen((_) => calls++);
+    final med1 = Medicine(designation: 'med1', color: 0xFF226A,);
+    await repo.add(med1);
+    expect(calls, 1);
+    await repo.add(mockMedicine(designation: 'med2', dosis: 43));
+    await repo.add(Medicine(designation: 'med3'));
+    expect(calls, 3);
+    await repo.remove(med1);
+    expect(calls, 4);
+  });
 
 }