Commit a3e1fa0
Changed files (2)
lib
model
screens
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');
+ }
+ }
+ );
+}
+