Commit 47732f7

derdilla <derdilla06@gmail.com>
2023-05-05 19:25:55
revise BloodPressureModel structure
1 parent ef2aca9
Changed files (3)
lib/components/measurement_list.dart
@@ -1,3 +1,5 @@
+import 'dart:collection';
+
 import 'package:blood_pressure_app/model/blood_pressure.dart';
 import 'package:blood_pressure_app/model/settings.dart';
 import 'package:flutter/material.dart';
@@ -21,6 +23,7 @@ class MeasurementList extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
+
     return Container(
         child: Column(
           children: [
@@ -29,18 +32,34 @@ class MeasurementList extends StatelessWidget {
               flex: 100,
               child: Consumer<BloodPressureModel>(
                 builder: (context, model, child) {
-                  List<BloodPressureRecord> items = model.getLastX(30);
-                  if (items.isNotEmpty && items.first.diastolic > 0) {
-                    return ListView.builder(
-                        itemCount: items.length,
-                        shrinkWrap: true,
-                        itemBuilder: (context, index) {
-                          return buildListItem(items[index]);
+                  final items = model.getLastX(30);
+                  return FutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
+                    future: items,
+                    builder: (BuildContext context, AsyncSnapshot<UnmodifiableListView<BloodPressureRecord>> recordsSnapsot) {
+                      assert(recordsSnapsot.connectionState != ConnectionState.none);
+
+                      if (recordsSnapsot.connectionState == ConnectionState.waiting) {
+                        return const Text('loading...');
+                      } else {
+                        if (recordsSnapsot.hasError) {
+                          return Text('Error loading data:\n${recordsSnapsot.error}');
+                        } else {
+                          final data = recordsSnapsot.data ?? [];
+                          if (data.isNotEmpty && data.first.diastolic > 0) {
+                            return ListView.builder(
+                                itemCount: data.length,
+                                shrinkWrap: true,
+                                itemBuilder: (context, index) {
+                                  return buildListItem(data[index]);
+                                }
+                            );
+                          } else {
+                            return const Text('no data');
+                          }
                         }
-                    );
-                  } else {
-                    return const Text('no data');
-                  }
+                      }
+                    }
+                  );
                 }
               ),
             )
lib/model/blood_pressure.dart
@@ -9,13 +9,12 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart';
 import 'package:sqflite/sqflite.dart';
 import 'package:file_saver/file_saver.dart';
 import 'package:share_plus/share_plus.dart';
+import 'package:cross_file/cross_file.dart' show XFile;
 import 'dart:convert' show utf8;
 
 class BloodPressureModel extends ChangeNotifier {
   static const maxEntries = 2E64; // https://www.sqlite.org/limits.html Nr.13
   late final Database _database;
-  List<BloodPressureRecord> _allMeasurements = []; // TODO: remove cache
-  var _cacheCount = 100; // how many db entries are cached on default
 
   BloodPressureModel._create();
   Future<void> _asyncInit() async {
@@ -27,7 +26,6 @@ class BloodPressureModel extends ChangeNotifier {
       },
       version: 1,
     );
-    await _cacheLast();
   }
   // factory method, to allow for async contructor
   static Future<BloodPressureModel> create() async {
@@ -36,23 +34,6 @@ class BloodPressureModel extends ChangeNotifier {
     return component;
   }
 
-  Future<void> _cacheLast() async {
-    var dbEntries = await _database.query('bloodPressureModel',
-        orderBy: 'timestamp DESC', limit: _cacheCount); // descending
-    // syncronous part
-    _allMeasurements = [];
-    for (var e in dbEntries) {
-      _allMeasurements.add(BloodPressureRecord(
-        DateTime.fromMillisecondsSinceEpoch(e['timestamp']as int), 
-        e['systolic'] as int, 
-        e['diastolic'] as int, 
-        e['pulse'] as int, 
-        e['notes'] as String));
-    }
-    
-    notifyListeners();
-  }
-
   /// Adds a new measurement at the correct chronological position in the List.
   Future<void> add(BloodPressureRecord measurement) async {
     assert(_database.isOpen);
@@ -77,26 +58,25 @@ class BloodPressureModel extends ChangeNotifier {
       });
     }
 
-    _cacheLast(); // contains notifyListeners()
+    notifyListeners();
   }
 
   /// Returns the last x BloodPressureRecords from new to old.
   /// Caches new ones if necessary
-  UnmodifiableListView<BloodPressureRecord> getLastX(int count) {
+  Future<UnmodifiableListView<BloodPressureRecord>> getLastX(int count) async {
     List<BloodPressureRecord> lastMeasurements = [];
-    
-    // fetch more if needed
-    if (count > _cacheCount) {
-      _cacheCount = count;
-      _cacheLast();
-    } else if (count == _cacheCount) { // small optimization
-      lastMeasurements = _allMeasurements;
-    } else {
-      for (int i = 0; (i<count && i<_allMeasurements.length); i++) {
-        lastMeasurements.add(_allMeasurements[i]);
-      }
+
+    var dbEntries = await _database.query('bloodPressureModel',
+        orderBy: 'timestamp DESC', limit: count); // de
+    for (var e in dbEntries) {
+      lastMeasurements.add(BloodPressureRecord(
+          DateTime.fromMillisecondsSinceEpoch(e['timestamp']as int),
+          e['systolic'] as int,
+          e['diastolic'] as int,
+          e['pulse'] as int,
+          e['notes'].toString()));
+
     }
-    
     return UnmodifiableListView(lastMeasurements);
   }
 
@@ -120,8 +100,7 @@ class BloodPressureModel extends ChangeNotifier {
     return UnmodifiableListView(recordsInRange);
   }
 
-  Future<void> save(BuildContext context) async {
-    // TODO: passing context is not clean keep UI code out of model
+  Future<void> save(void Function(bool success, String? msg) callback) async {
     // create csv
     String csvData = 'timestampUnixMs, systolic, diastolic, pulse, notes\n';
     List<Map<String, Object?>> allEntries = await _database.query('bloodPressureModel',
@@ -131,8 +110,9 @@ class BloodPressureModel extends ChangeNotifier {
     }
 
     // save data
+    String filename = 'blood_press_${DateTime.now().toIso8601String()}';
     String path = await FileSaver.instance.saveFile(
-      name: 'blood_press_${DateTime.now().toIso8601String()}',
+      name: filename,
       bytes: Uint8List.fromList(utf8.encode(csvData)),
       ext: 'csv',
       mimeType: MimeType.csv
@@ -140,10 +120,11 @@ class BloodPressureModel extends ChangeNotifier {
 
     // notify user about location
     if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
-      ScaffoldMessenger.of(context).showSnackBar(
-          SnackBar(content: Text('Exported to: $path')));
+      callback(true, 'Exported to: $path');
     } else if (Platform.isAndroid || Platform.isIOS) {
-      Share.shareFiles([path], mimeTypes: [MimeType.csv.type]);
+      // TODO: compatability option
+      Share.shareXFiles([XFile(path, mimeType: MimeType.csv.type,)]);
+      callback(true, null);
     } else {
 
     }
@@ -184,13 +165,6 @@ class BloodPressureModel extends ChangeNotifier {
       return callback(false, 'no file opened');
     }
   }
-
-/* TODO:
-  - bool deleteFromTime (timestamp)
-  - bool changeAtTime (newRecord)
-   */
-
-
 }
 
 @immutable
lib/screens/settings.dart
@@ -20,7 +20,7 @@ class SettingsScreen extends StatelessWidget {
           builder: (context, settings, child) {
             return SettingsList(sections: [
               SettingsSection(
-                  title: const Text('theme'),
+                  title: const Text('layout'),
                   tiles: <SettingsTile>[
                     SettingsTile.switchTile(
                         initialValue: settings.followSystemDarkMode,
@@ -73,7 +73,15 @@ class SettingsScreen extends StatelessWidget {
                   SettingsTile(
                     title: const Text('export'),
                     leading: const Icon(Icons.save),
-                    onPressed: (context) =>  Provider.of<BloodPressureModel>(context, listen: false).save(context),
+                    onPressed: (context) =>  Provider.of<BloodPressureModel>(context, listen: false).save((success, msg) {
+                      if (success && msg != null) {
+                        ScaffoldMessenger.of(context).showSnackBar(
+                          SnackBar(content: Text(msg)));
+                      } else if (!success && msg != null) {
+                        ScaffoldMessenger.of(context).showSnackBar(
+                            SnackBar(content: Text('Error: $msg')));
+                      }
+                    }),
                   ),
                   SettingsTile(
                     title: const Text('import'),