Skip to content

Commit

Permalink
Merge pull request #104 from NobodyForNothing/FEAT-range-limited-stat…
Browse files Browse the repository at this point in the history
…istics

Feat range limited statistics
  • Loading branch information
derdilla authored Jul 18, 2023
2 parents 1451762 + 583eb2d commit c725a8e
Show file tree
Hide file tree
Showing 13 changed files with 434 additions and 603 deletions.
6 changes: 3 additions & 3 deletions lib/components/display_interval_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ class IntervalPicker extends StatelessWidget {
),
Expanded(
flex: 40,
child: DropdownButton<int>(
child: DropdownButton<TimeStep>(
value: settings.graphStepSize,
isExpanded: true,
onChanged: (int? value) {
onChanged: (TimeStep? value) {
if (value != null) {
settings.changeStepSize(value);
}
},
items: TimeStep.options.map<DropdownMenuItem<int>>((v) {
items: TimeStep.options.map<DropdownMenuItem<TimeStep>>((v) {
return DropdownMenuItem(value: v, child: Text(TimeStep.getName(v, context)));
}).toList(),
),
Expand Down
4 changes: 0 additions & 4 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@
"@errNotEnoughDataToGraph": {},
"errNoData": "no data",
"@errNoData": {},
"errNoRangeForExport": "You need to specify a range in which data is exported.",
"@errNoRangeForExport": {},
"errPleaseSelect": "please select",
"@errPleaseSelect": {},
"errWrongImportFormat": "You can only import files in CSV and SQLite database format.",
Expand Down Expand Up @@ -179,8 +177,6 @@
"@exportAfterEveryInput": {},
"exportAfterEveryInputDesc": "Not recommended (file explosion)",
"@exportAfterEveryInputDesc": {},
"exportLimitDataRange": "Limit data range",
"@exportLimitDataRange": {},
"exportInterval": "Data range",
"@exportInterval": {},
"exportFormat": "Export format",
Expand Down
71 changes: 0 additions & 71 deletions lib/model/blood_pressure.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,81 +88,10 @@ class BloodPressureModel extends ChangeNotifier {
return UnmodifiableListView(_convert(await _database.query('bloodPressureModel', columns: ['*'])));
}

Future<int> get count async {
return (await _database.rawQuery('SELECT COUNT(*) FROM bloodPressureModel'))[0]['COUNT(*)'] as int? ?? -1;
}

Future<DateTime> get firstDay async {
return DateTime.fromMillisecondsSinceEpoch(
(await _database.rawQuery('SELECT timestamp FROM bloodPressureModel ORDER BY timestamp ASC LIMIT 1'))[0]
['timestamp'] as int? ??
-1);
}

Future<DateTime> get lastDay async {
return DateTime.fromMillisecondsSinceEpoch(
(await _database.rawQuery('SELECT timestamp FROM bloodPressureModel ORDER BY timestamp DESC LIMIT 1'))[0]
['timestamp'] as int? ??
-1);
}

Future<int> get avgDia async {
var res = _toInt((await _database.rawQuery('SELECT AVG(diastolic) as dia FROM bloodPressureModel'))[0]['dia']);
return res ?? -1;
}

Future<int> get avgSys async {
var res = _toInt((await _database.rawQuery('SELECT AVG(systolic) as sys FROM bloodPressureModel'))[0]['sys']);
return res ?? -1;
}

Future<int> get avgPul async {
var res = _toInt((await _database.rawQuery('SELECT AVG(pulse) as pul FROM bloodPressureModel'))[0]['pul']);
return res ?? -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;
}

void close() {
_database.close();
}

int? _toInt(Object? v) {
try {
return (v as int?);
} catch (e) {
return (v as double?)?.toInt();
}
}

List<BloodPressureRecord> _convert(List<Map<String, Object?>> dbResult) {
List<BloodPressureRecord> records = [];
for (var e in dbResult) {
Expand Down
66 changes: 55 additions & 11 deletions lib/model/blood_pressure_analyzer.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,61 @@
import 'dart:math';

import 'package:blood_pressure_app/model/blood_pressure.dart';
import 'package:collection/collection.dart';

class BloodPressureAnalyser {
final BloodPressureModel _model;
final List<BloodPressureRecord> _records;

BloodPressureAnalyser(this._records);

int get count => _records.length;

int get avgDia => _safeResult(() => _nonNullDia.average.toInt(), (r) => r.diastolic);

int get avgPul => _safeResult(() => _nonNullPul.average.toInt(), (r) => r.pulse);

int get avgSys => _safeResult(() => _nonNullSys.average.toInt(), (r) => r.systolic);

int get maxDia => _safeResult(() => _nonNullDia.reduce(max), (r) => r.diastolic);

int get maxPul => _safeResult(() => _nonNullPul.reduce(max), (r) => r.pulse);

int get maxSys => _safeResult(() => _nonNullSys.reduce(max), (r) => r.systolic);

BloodPressureAnalyser(this._model);
int get minDia => _safeResult(() => _nonNullDia.reduce(min), (r) => r.diastolic);

int get minPul => _safeResult(() => _nonNullPul.reduce(min), (r) => r.pulse);

int get minSys => _safeResult(() => _nonNullSys.reduce(min), (r) => r.systolic);

DateTime? get firstDay {
if (_records.isEmpty) return null;
_records.sort((a, b) => a.creationTime.compareTo(b.creationTime));
return _records.first.creationTime;
}

DateTime? get lastDay {
if (_records.isEmpty) return null;
_records.sort((a, b) => a.creationTime.compareTo(b.creationTime));
return _records.last.creationTime;
}

int _safeResult(int Function() f, int? Function(BloodPressureRecord) lengthOneResult) {
if (_records.isEmpty) return -1;
if (_records.length == 1) return lengthOneResult(_records.first) ?? -1;
return f();
}
Iterable<int> get _nonNullDia => _records.where((e) => e.diastolic!=null).map<int>((e) => e.diastolic!);
Iterable<int> get _nonNullSys => _records.where((e) => e.systolic!=null).map<int>((e) => e.systolic!);
Iterable<int> get _nonNullPul => _records.where((e) => e.pulse!=null).map<int>((e) => e.pulse!);

Future<int> get measurementsPerDay async {
final c = await _model.count;
int get measurementsPerDay {
final c = count;
if (c <= 1) return -1;

final firstDay = await _model.firstDay;
final lastDay = await _model.lastDay;
final firstDay = this.firstDay;
final lastDay = this.lastDay;
if (firstDay == null || lastDay == null) return -1;

if (firstDay.millisecondsSinceEpoch == -1 || lastDay.millisecondsSinceEpoch == -1) {
return -1;
Expand All @@ -25,7 +69,7 @@ class BloodPressureAnalyser {

/// 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>>> get allAvgsRelativeToDaytime async {
List<List<int>> get allAvgsRelativeToDaytime {
// setup vars
List<List<int>> allDiaValuesRelativeToTime = [];
List<List<int>> allSysValuesRelativeToTime = [];
Expand All @@ -37,7 +81,7 @@ class BloodPressureAnalyser {
}

// sort all data
final dbRes = await _model.all;
final dbRes = _records;
for (var e in dbRes) {
DateTime ts = DateTime.fromMillisecondsSinceEpoch(e.creationTime.millisecondsSinceEpoch);
if (e.diastolic != null) allDiaValuesRelativeToTime[ts.hour].add(e.diastolic!);
Expand All @@ -46,13 +90,13 @@ class BloodPressureAnalyser {
}
for (int i = 0; i < 24; i++) {
if (allDiaValuesRelativeToTime[i].isEmpty) {
allDiaValuesRelativeToTime[i].add(await _model.avgDia);
allDiaValuesRelativeToTime[i].add(avgDia);
}
if (allSysValuesRelativeToTime[i].isEmpty) {
allSysValuesRelativeToTime[i].add(await _model.avgSys);
allSysValuesRelativeToTime[i].add(avgSys);
}
if (allPulValuesRelativeToTime[i].isEmpty) {
allPulValuesRelativeToTime[i].add(await _model.avgPul);
allPulValuesRelativeToTime[i].add(avgPul);
}
}

Expand Down
14 changes: 2 additions & 12 deletions lib/model/export_import.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
Expand Down Expand Up @@ -224,17 +223,8 @@ class Exporter {
final messenger = ScaffoldMessenger.of(context);
final localizations = AppLocalizations.of(context);

final UnmodifiableListView<BloodPressureRecord> entries;
if (settings.exportLimitDataRange) {
var range = settings.exportDataRange;
if (range.start.millisecondsSinceEpoch == 0 || range.end.millisecondsSinceEpoch == 0) {
messenger.showSnackBar(SnackBar(content: Text(localizations!.errNoRangeForExport)));
return;
}
entries = await Provider.of<BloodPressureModel>(context, listen: false).getInTimeRange(settings.exportDataRange.start, settings.exportDataRange.end);
} else {
entries = await Provider.of<BloodPressureModel>(context, listen: false).all;
}
final entries = await Provider.of<BloodPressureModel>(context, listen: false)
.getInTimeRange(settings.displayDataStart, settings.displayDataEnd);
var fileContents = await ExportFileCreator(settings).createFile(entries);

String filename = 'blood_press_${DateTime.now().toIso8601String()}';
Expand Down
Loading

0 comments on commit c725a8e

Please sign in to comment.