Commit a3e1fa0

derdilla <derdilla06@gmail.com>
2023-05-15 13:36:21
FEAT: add useful info to statistics tag: v0.5.0
1 parent c03409d
Changed files (2)
lib/model/blood_pressure.dart
@@ -117,6 +117,19 @@ class BloodPressureModel extends ChangeNotifier {
   Future<int> get count async {
     return (await _database.rawQuery('SELECT COUNT(*) FROM bloodPressureModel'))[0]['COUNT(*)'] as int? ?? -1;
   }
+  Future<int> get measurementsPerDay async {
+    final c = await count;
+    if (c <= 1) {
+      return -1;
+    }
+    var firstDay = DateTime.fromMillisecondsSinceEpoch((await _database.rawQuery('SELECT timestamp FROM bloodPressureModel ORDER BY timestamp ASC LIMIT 1'))[0]['timestamp'] as int? ?? -1);
+    var lastDay = DateTime.fromMillisecondsSinceEpoch((await _database.rawQuery('SELECT timestamp FROM bloodPressureModel ORDER BY timestamp DESC LIMIT 1'))[0]['timestamp'] as int? ?? -1);
+
+    return c ~/ lastDay.difference(firstDay).inDays;
+
+  }
+
+
   Future<int> get avgDia async {
     var res = (await _database.rawQuery('SELECT AVG(diastolic) as dia FROM bloodPressureModel'))[0]['dia'];
     int? val;
@@ -148,9 +161,35 @@ class BloodPressureModel extends ChangeNotifier {
     return val ?? -1;
   }
 
+  Future<int> get maxDia async {
+    var res = (await _database.rawQuery('SELECT MAX(diastolic) as dia FROM bloodPressureModel'))[0]['dia'];
+    return (res as int?) ?? -1;
+  }
+  Future<int> get maxSys async {
+    var res = (await _database.rawQuery('SELECT MAX(systolic) as sys FROM bloodPressureModel'))[0]['sys'];
+    return (res as int?) ?? -1;
+  }
+  Future<int> get maxPul async {
+    var res = (await _database.rawQuery('SELECT MAX(pulse) as pul FROM bloodPressureModel'))[0]['pul'];
+    return (res as int?) ?? -1;
+  }
+
+  Future<int> get minDia async {
+    var res = (await _database.rawQuery('SELECT MIN(diastolic) as dia FROM bloodPressureModel'))[0]['dia'];
+    return (res as int?) ?? -1;
+  }
+  Future<int> get minSys async {
+    var res = (await _database.rawQuery('SELECT MIN(systolic) as sys FROM bloodPressureModel'))[0]['sys'];
+    return (res as int?) ?? -1;
+  }
+  Future<int> get minPul async {
+    var res = (await _database.rawQuery('SELECT MIN(pulse) as pul FROM bloodPressureModel'))[0]['pul'];
+    return (res as int?) ?? -1;
+  }
+
   /// outer list is type (0 -> diastolic, 1 -> systolic, 2 -> pulse)
   /// inner list index is hour of day ([0] -> 00:00-00:59; [1] -> ...)
-  Future<List<List<int>>> getAllAvgsRelativeToDaytime({bool interpolate = false}) async {
+  Future<List<List<int>>> get allAvgsRelativeToDaytime async {
     // setup vars
     List<List<int>> allDiaValuesRelativeToTime = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]];
     List<List<int>> allSysValuesRelativeToTime = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]];
@@ -164,34 +203,15 @@ class BloodPressureModel extends ChangeNotifier {
       allSysValuesRelativeToTime[ts.hour].add(entry['systolic'] as int);
       allPulValuesRelativeToTime[ts.hour].add(entry['pulse'] as int);
     }
-    for(int i = 0; i < 24; i++) { // TODO: interpolate for every day instead without using allow of resources
-      if (allDiaValuesRelativeToTime[i].isEmpty) { // fixme next might be empty
-        allDiaValuesRelativeToTime[i].add(0);
+    for(int i = 0; i < 24; i++) {
+      if (allDiaValuesRelativeToTime[i].isEmpty) {
+        allDiaValuesRelativeToTime[i].add(await avgDia);
       }
       if (allSysValuesRelativeToTime[i].isEmpty) {
-        allSysValuesRelativeToTime[i].add(0);
+        allSysValuesRelativeToTime[i].add(await avgSys);
       }
       if (allPulValuesRelativeToTime[i].isEmpty) {
-        allPulValuesRelativeToTime[i].add(0);
-      }
-    }
-
-    if (interpolate) {
-      for(int i = 0; i < 24; i++) {
-        var prev = (i - 1 >= 0) ? (i - 1) : 23;
-        var next = (i + 1 <= 23) ? (i + 1) : 0;
-        allDiaValuesRelativeToTime[i].add([
-          allDiaValuesRelativeToTime[prev].average,
-          allDiaValuesRelativeToTime[next].average
-        ].average.toInt());
-        allSysValuesRelativeToTime[i].add([
-          allSysValuesRelativeToTime[prev].average,
-          allSysValuesRelativeToTime[next].average
-        ].average.toInt());
-        allPulValuesRelativeToTime[i].add([
-          allPulValuesRelativeToTime[prev].average,
-          allPulValuesRelativeToTime[next].average
-        ].average.toInt());
+        allPulValuesRelativeToTime[i].add(await avgPul);
       }
     }
 
lib/screens/statistics.dart
@@ -25,39 +25,48 @@ class StatisticsPage extends StatelessWidget {
                         caption: const Text('Measurement count'),
                         child: futureInt(model.count)
                     ),
-                    // Averages
-                    Row(
-                      children: [
-                        const Spacer(),
-                        Statistic(
-                          caption: Text('Diastolic avg.',
-                            style: TextStyle(color: settings.diaColor, fontWeight: FontWeight.w700),
-                          ),
-                          smallEdges: true,
-                          child: futureInt(model.avgDia),
-                        ),
-                        const Spacer(),
-                        Statistic(
-                          caption: Text('Systolic avg.',
-                            style: TextStyle(color: settings.sysColor, fontWeight: FontWeight.w700),),
-                          smallEdges: true,
-                          child: futureInt(model.avgSys),
-                        ),
-                        const Spacer(),
-                        Statistic(
-                          caption: Text('Pulse avg.',
-                            style: TextStyle(color: settings.pulColor, fontWeight: FontWeight.w700),),
-                          smallEdges: true,
-                          child: futureInt(model.avgPul),
-                        ),
-                        const Spacer(),
-                      ],
+                    // special measurements
+                    StatisticsRow(
+                      caption1: Text('Systolic avg.',
+                        style: TextStyle(color: settings.sysColor, fontWeight: FontWeight.w700),),
+                      child1: futureInt(model.avgSys),
+                      caption2: Text('Diastolic avg.', style: TextStyle(color: settings.diaColor, fontWeight: FontWeight.w700),),
+                      child2: futureInt(model.avgDia),
+                      caption3: Text('Pulse avg.',
+                        style: TextStyle(color: settings.pulColor, fontWeight: FontWeight.w700),),
+                      child3: futureInt(model.avgPul),
                     ),
+                    Statistic(
+                        caption: const Text('Measurements per Day'),
+                        child: futureInt(model.measurementsPerDay)
+                    ),
+                    StatisticsRow(
+                      caption2: Text('Diastolic min.',
+                        style: TextStyle(color: settings.diaColor, fontWeight: FontWeight.w700),),
+                      child2: futureInt(model.minDia),
+                      caption1: Text('Systolic min.',
+                        style: TextStyle(color: settings.sysColor, fontWeight: FontWeight.w700),),
+                      child1: futureInt(model.minSys),
+                      caption3: Text('Pulse min.',
+                        style: TextStyle(color: settings.pulColor, fontWeight: FontWeight.w700),),
+                      child3: futureInt(model.minPul),
+                    ),
+                    StatisticsRow(
+                      caption2: Text('Diastolic max.',
+                        style: TextStyle(color: settings.diaColor, fontWeight: FontWeight.w700),),
+                      child2: futureInt(model.maxDia),
+                      caption1: Text('Systolic max.',
+                        style: TextStyle(color: settings.sysColor, fontWeight: FontWeight.w700),),
+                      child1: futureInt(model.maxSys),
+                      caption3: Text('Pulse max.',
+                        style: TextStyle(color: settings.pulColor, fontWeight: FontWeight.w700),),
+                      child3: futureInt(model.maxPul),
+                    ),
+                    // Time-Resolved Metrics
                     Statistic(
                       caption: const Text('Time-Resolved Metrics'),
                       child: FutureBuilder<List<List<int>>>(
-                          future: model.getAllAvgsRelativeToDaytime(
-                              interpolate: true),
+                          future: model.allAvgsRelativeToDaytime,
                           builder: (BuildContext context, AsyncSnapshot<List<
                               List<int>>> snapshot) {
                             switch (snapshot.connectionState) {
@@ -131,6 +140,7 @@ class StatisticsPage extends StatelessWidget {
                           }
                       ),
                     ),
+                    // TODO: Weekdays / Weekends
                   ],
                 );
               }
@@ -140,29 +150,6 @@ class StatisticsPage extends StatelessWidget {
       ),
     );
   }
-  
-  Widget futureInt(Future<int> value) {
-    return FutureBuilder<int>(
-        future: value,
-        builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
-          switch (snapshot.connectionState) {
-            case ConnectionState.none:
-              return const Text('not started');
-            case ConnectionState.waiting:
-              return const Text('loading...');
-            default:
-              if (snapshot.hasError) {
-                return Text('ERROR: ${snapshot.error}');
-              }
-              assert(snapshot.hasData);
-              if ((snapshot.data??-1) < 0) {
-                return const Text('invalid data');
-              }
-              return Text(snapshot.data?.toString() ?? 'error');
-          }
-        }
-    );
-  }
 
   List<RadarEntry> intListToRadarEntry(List<int> data) {
     var res = <RadarEntry>[];
@@ -184,7 +171,7 @@ class Statistic extends StatelessWidget {
   Widget build(BuildContext context) {
     double sides = 20;
     double top = 20;
-    double padding = 30;
+    double padding = 20;
     if (smallEdges) {
       sides = 0;
       padding = 10;
@@ -197,7 +184,7 @@ class Statistic extends StatelessWidget {
       ),
       decoration: BoxDecoration(
         border: Border.all(
-          width: 4,
+          width: 3,
           color: Theme.of(context).textTheme.bodyMedium?.color ?? Colors.white38
         ),
         borderRadius: const BorderRadius.all(Radius.circular(25)),
@@ -231,3 +218,65 @@ class Statistic extends StatelessWidget {
     );
   }
 }
+
+class StatisticsRow extends StatelessWidget {
+  final Widget caption1;
+  final Widget caption2;
+  final Widget caption3;
+  final Widget child1;
+  final Widget child2;
+  final Widget child3;
+
+  const StatisticsRow({super.key, required this.caption1, required this.caption2, required this.caption3, required this.child1, required this.child2, required this.child3});
+
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      children: [
+        const Spacer(),
+        Statistic(
+          smallEdges: true,
+          caption: caption1,
+          child: child1,
+        ),
+        const Spacer(),
+        Statistic(
+          smallEdges: true,
+          caption: caption2,
+          child: child2,
+        ),
+        const Spacer(),
+        Statistic(
+          smallEdges: true,
+          caption: caption3,
+          child: child3,
+        ),
+        const Spacer(),
+      ],
+    );
+  }
+}
+
+Widget futureInt(Future<int> value) {
+  return FutureBuilder<int>(
+      future: value,
+      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
+        switch (snapshot.connectionState) {
+          case ConnectionState.none:
+            return const Text('not started');
+          case ConnectionState.waiting:
+            return const Text('loading...');
+          default:
+            if (snapshot.hasError) {
+              return Text('ERROR: ${snapshot.error}');
+            }
+            assert(snapshot.hasData);
+            if ((snapshot.data ?? -1) < 0) {
+              return const Text('invalid data');
+            }
+            return Text(snapshot.data?.toString() ?? 'error');
+        }
+      }
+  );
+}
+