diff --git a/lib/components/display_interval_picker.dart b/lib/components/display_interval_picker.dart index c265d925..fc373666 100644 --- a/lib/components/display_interval_picker.dart +++ b/lib/components/display_interval_picker.dart @@ -59,15 +59,15 @@ class IntervalPicker extends StatelessWidget { ), Expanded( flex: 40, - child: DropdownButton( + child: DropdownButton( value: settings.graphStepSize, isExpanded: true, - onChanged: (int? value) { + onChanged: (TimeStep? value) { if (value != null) { settings.changeStepSize(value); } }, - items: TimeStep.options.map>((v) { + items: TimeStep.options.map>((v) { return DropdownMenuItem(value: v, child: Text(TimeStep.getName(v, context))); }).toList(), ), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0d164aa3..9dab8001 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -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.", @@ -179,8 +177,6 @@ "@exportAfterEveryInput": {}, "exportAfterEveryInputDesc": "Not recommended (file explosion)", "@exportAfterEveryInputDesc": {}, - "exportLimitDataRange": "Limit data range", - "@exportLimitDataRange": {}, "exportInterval": "Data range", "@exportInterval": {}, "exportFormat": "Export format", diff --git a/lib/model/blood_pressure.dart b/lib/model/blood_pressure.dart index 2be84acc..6d26e5ef 100644 --- a/lib/model/blood_pressure.dart +++ b/lib/model/blood_pressure.dart @@ -88,81 +88,10 @@ class BloodPressureModel extends ChangeNotifier { return UnmodifiableListView(_convert(await _database.query('bloodPressureModel', columns: ['*']))); } - Future get count async { - return (await _database.rawQuery('SELECT COUNT(*) FROM bloodPressureModel'))[0]['COUNT(*)'] as int? ?? -1; - } - - Future 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 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 get avgDia async { - var res = _toInt((await _database.rawQuery('SELECT AVG(diastolic) as dia FROM bloodPressureModel'))[0]['dia']); - return res ?? -1; - } - - Future get avgSys async { - var res = _toInt((await _database.rawQuery('SELECT AVG(systolic) as sys FROM bloodPressureModel'))[0]['sys']); - return res ?? -1; - } - - Future get avgPul async { - var res = _toInt((await _database.rawQuery('SELECT AVG(pulse) as pul FROM bloodPressureModel'))[0]['pul']); - return res ?? -1; - } - - Future get maxDia async { - var res = (await _database.rawQuery('SELECT MAX(diastolic) as dia FROM bloodPressureModel'))[0]['dia']; - return (res as int?) ?? -1; - } - - Future get maxSys async { - var res = (await _database.rawQuery('SELECT MAX(systolic) as sys FROM bloodPressureModel'))[0]['sys']; - return (res as int?) ?? -1; - } - - Future get maxPul async { - var res = (await _database.rawQuery('SELECT MAX(pulse) as pul FROM bloodPressureModel'))[0]['pul']; - return (res as int?) ?? -1; - } - - Future get minDia async { - var res = (await _database.rawQuery('SELECT MIN(diastolic) as dia FROM bloodPressureModel'))[0]['dia']; - return (res as int?) ?? -1; - } - - Future get minSys async { - var res = (await _database.rawQuery('SELECT MIN(systolic) as sys FROM bloodPressureModel'))[0]['sys']; - return (res as int?) ?? -1; - } - - Future 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 _convert(List> dbResult) { List records = []; for (var e in dbResult) { diff --git a/lib/model/blood_pressure_analyzer.dart b/lib/model/blood_pressure_analyzer.dart index 1325b146..04e05c91 100644 --- a/lib/model/blood_pressure_analyzer.dart +++ b/lib/model/blood_pressure_analyzer.dart @@ -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 _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 get _nonNullDia => _records.where((e) => e.diastolic!=null).map((e) => e.diastolic!); + Iterable get _nonNullSys => _records.where((e) => e.systolic!=null).map((e) => e.systolic!); + Iterable get _nonNullPul => _records.where((e) => e.pulse!=null).map((e) => e.pulse!); - Future 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; @@ -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>> get allAvgsRelativeToDaytime async { + List> get allAvgsRelativeToDaytime { // setup vars List> allDiaValuesRelativeToTime = []; List> allSysValuesRelativeToTime = []; @@ -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!); @@ -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); } } diff --git a/lib/model/export_import.dart b/lib/model/export_import.dart index 68a26fff..58fd8f51 100644 --- a/lib/model/export_import.dart +++ b/lib/model/export_import.dart @@ -1,5 +1,4 @@ -import 'dart:collection'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; @@ -224,17 +223,8 @@ class Exporter { final messenger = ScaffoldMessenger.of(context); final localizations = AppLocalizations.of(context); - final UnmodifiableListView 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(context, listen: false).getInTimeRange(settings.exportDataRange.start, settings.exportDataRange.end); - } else { - entries = await Provider.of(context, listen: false).all; - } + final entries = await Provider.of(context, listen: false) + .getInTimeRange(settings.displayDataStart, settings.displayDataEnd); var fileContents = await ExportFileCreator(settings).createFile(entries); String filename = 'blood_press_${DateTime.now().toIso8601String()}'; diff --git a/lib/model/ram_only_implementations.dart b/lib/model/ram_only_implementations.dart index a514df02..e39c7cb0 100644 --- a/lib/model/ram_only_implementations.dart +++ b/lib/model/ram_only_implementations.dart @@ -1,5 +1,4 @@ import 'dart:collection'; -import 'dart:math'; import 'package:blood_pressure_app/model/blood_pressure.dart'; import 'package:blood_pressure_app/model/export_import.dart'; @@ -35,52 +34,6 @@ class RamBloodPressureModel extends ChangeNotifier implements BloodPressureModel @override Future> get all async => UnmodifiableListView(_records); - @override - Future get count async => _records.length; - - @override - Future get avgDia async => _nonNullDia.reduce((a, b) => a + b) ~/ _nonNullDia.length; - - @override - Future get avgPul async => _nonNullPul.reduce((a, b) => a + b) ~/ _nonNullPul.length; - - @override - Future get avgSys async => _nonNullSys.reduce((a, b) => a + b) ~/ _nonNullSys.length; - - @override - Future get maxDia async => _nonNullDia.reduce(max); - - @override - Future get maxPul async => _nonNullPul.reduce(max); - - @override - Future get maxSys async => _nonNullSys.reduce(max); - - @override - Future get minDia async => _nonNullDia.reduce(min); - - @override - Future get minPul async => _nonNullPul.reduce(min); - - @override - Future get minSys async => _nonNullSys.reduce(min); - - @override - Future get firstDay async { - _records.sort((a, b) => a.creationTime.compareTo(b.creationTime)); - return _records.first.creationTime; - } - - @override - Future get lastDay async { - _records.sort((a, b) => a.creationTime.compareTo(b.creationTime)); - return _records.last.creationTime; - } - - Iterable get _nonNullDia => _records.where((e) => e.diastolic!=null).map((e) => e.diastolic!); - Iterable get _nonNullSys => _records.where((e) => e.systolic!=null).map((e) => e.systolic!); - Iterable get _nonNullPul => _records.where((e) => e.pulse!=null).map((e) => e.pulse!); - @override void close() {} } @@ -98,7 +51,7 @@ class RamSettings extends ChangeNotifier implements Settings { DateTime? _displayDataStart; bool _followSystemDarkMode = true; double _graphLineThickness = 3; - int _graphStepSize = TimeStep.day; + TimeStep _graphStepSize = TimeStep.day; double _iconSize = 30; MaterialColor _pulColor = Colors.pink; MaterialColor _sysColor = Colors.pink; @@ -112,9 +65,7 @@ class RamSettings extends ChangeNotifier implements Settings { List _exportAddableItems = ['isoUTCTime']; bool _exportCsvHeadline = true; bool _exportCustomEntries = false; - DateTimeRange _exportDataRange = DateTimeRange(start: DateTime.fromMillisecondsSinceEpoch(0), end: DateTime.fromMillisecondsSinceEpoch(0)); List _exportItems = ['timestampUnixMs', 'systolic', 'diastolic', 'pulse', 'notes']; - bool _exportLimitDataRange = false; MimeType _exportMimeType = MimeType.csv; String _defaultExportDir = ''; bool _exportAfterEveryEntry = false; @@ -253,10 +204,10 @@ class RamSettings extends ChangeNotifier implements Settings { } @override - int get graphStepSize => _graphStepSize; + TimeStep get graphStepSize => _graphStepSize; @override - set graphStepSize(int value) { + set graphStepSize(TimeStep value) { _graphStepSize = value; notifyListeners(); } @@ -367,15 +318,6 @@ class RamSettings extends ChangeNotifier implements Settings { notifyListeners(); } - @override - DateTimeRange get exportDataRange => _exportDataRange; - - @override - set exportDataRange(DateTimeRange value) { - _exportDataRange = value; - notifyListeners(); - } - @override List get exportItems => _exportItems; @@ -385,15 +327,6 @@ class RamSettings extends ChangeNotifier implements Settings { notifyListeners(); } - @override - bool get exportLimitDataRange => _exportLimitDataRange; - - @override - set exportLimitDataRange(bool value) { - _exportLimitDataRange = value; - notifyListeners(); - } - @override MimeType get exportMimeType => _exportMimeType; @@ -431,7 +364,7 @@ class RamSettings extends ChangeNotifier implements Settings { } @override - void changeStepSize(int value) { + void changeStepSize(TimeStep value) { graphStepSize = value; final newInterval = getMostRecentDisplayIntervall(); displayDataStart = newInterval[0]; @@ -448,6 +381,7 @@ class RamSettings extends ChangeNotifier implements Settings { displayDataEnd = oldEnd.copyWith(day: oldEnd.day + directionalStep); break; case TimeStep.week: + case TimeStep.last7Days: displayDataStart = oldStart.copyWith(day: oldStart.day + directionalStep * 7); displayDataEnd = oldEnd.copyWith(day: oldEnd.day + directionalStep * 7); break; @@ -463,6 +397,9 @@ class RamSettings extends ChangeNotifier implements Settings { displayDataStart = DateTime.fromMillisecondsSinceEpoch(0); displayDataEnd = DateTime.now(); break; + case TimeStep.last30Days: + displayDataStart = oldStart.copyWith(day: oldStart.day + directionalStep * 30); + displayDataEnd = oldEnd.copyWith(day: oldEnd.day + directionalStep * 30); } } @@ -485,6 +422,12 @@ class RamSettings extends ChangeNotifier implements Settings { case TimeStep.lifetime: final start = DateTime.fromMillisecondsSinceEpoch(0); return [start, now]; + case TimeStep.last7Days: + final start = now.copyWith(day: now.day-7); + return [start, now]; + case TimeStep.last30Days: + final start = now.copyWith(day: now.day-30); + return [start, now]; default: assert(false); final start = DateTime.fromMillisecondsSinceEpoch(0); diff --git a/lib/model/settings_store.dart b/lib/model/settings_store.dart index 1e204a24..06f6f158 100644 --- a/lib/model/settings_store.dart +++ b/lib/model/settings_store.dart @@ -33,6 +33,15 @@ class Settings extends ChangeNotifier { if (keys.contains('overrideWarnValues')) { toAwait.add(_prefs.remove('overrideWarnValues')); } + if (keys.contains('exportLimitDataRange')) { + toAwait.add(_prefs.remove('exportLimitDataRange')); + } + if (keys.contains('exportDataRangeStartEpochMs')) { + toAwait.add(_prefs.remove('exportDataRangeStartEpochMs')); + } + if (keys.contains('exportDataRangeEndEpochMs')) { + toAwait.add(_prefs.remove('exportDataRangeEndEpochMs')); + } for (var e in toAwait) { await e; @@ -40,16 +49,58 @@ class Settings extends ChangeNotifier { return; } - int get graphStepSize { - return _prefs.getInt('graphStepSize') ?? TimeStep.day; + TimeStep get graphStepSize { + int stepInt = _prefs.getInt('graphStepSize') ?? 0; + switch (stepInt) { + case 0: + return TimeStep.day; + case 1: + return TimeStep.month; + case 2: + return TimeStep.year; + case 3: + return TimeStep.lifetime; + case 4: + return TimeStep.week; + case 5: + return TimeStep.last7Days; + case 6: + return TimeStep.last30Days; + } + assert(false); + return TimeStep.day; } - set graphStepSize(int newStepSize) { - _prefs.setInt('graphStepSize', newStepSize); + set graphStepSize(TimeStep newStepSize) { + switch (newStepSize) { + case TimeStep.day: + _prefs.setInt('graphStepSize', 0); + break; + case TimeStep.month: + _prefs.setInt('graphStepSize', 1); + break; + case TimeStep.year: + _prefs.setInt('graphStepSize', 2); + break; + case TimeStep.lifetime: + _prefs.setInt('graphStepSize', 3); + break; + case TimeStep.week: + _prefs.setInt('graphStepSize', 4); + break; + case TimeStep.last7Days: + _prefs.setInt('graphStepSize', 5); + break; + case TimeStep.last30Days: + _prefs.setInt('graphStepSize', 6); + break; + default: + assert(false); + } notifyListeners(); } - void changeStepSize(int value) { + void changeStepSize(TimeStep value) { graphStepSize = value; final newInterval = getMostRecentDisplayIntervall(); displayDataStart = newInterval[0]; @@ -372,27 +423,6 @@ class Settings extends ChangeNotifier { notifyListeners(); } - bool get exportLimitDataRange { - return _prefs.getBool('exportLimitDataRange') ?? false; - } - - set exportLimitDataRange(bool value) { - _prefs.setBool('exportLimitDataRange', value); - notifyListeners(); - } - - DateTimeRange get exportDataRange { - final start = DateTime.fromMillisecondsSinceEpoch(_prefs.getInt('exportDataRangeStartEpochMs') ?? 0); - final end = DateTime.fromMillisecondsSinceEpoch(_prefs.getInt('exportDataRangeEndEpochMs') ?? 0); - return DateTimeRange(start: start, end: end); - } - - set exportDataRange(DateTimeRange value) { - _prefs.setInt('exportDataRangeStartEpochMs', value.start.millisecondsSinceEpoch); - _prefs.setInt('exportDataRangeEndEpochMs', value.end.millisecondsSinceEpoch); - notifyListeners(); - } - bool get exportCustomEntries { return _prefs.getBool('exportCustomEntries') ?? false; } @@ -446,38 +476,34 @@ class Settings extends ChangeNotifier { } } -class TimeStep { // TODO: replace with enum - static const options = [0, 4, 1, 2, 3, 5, 6]; - - static const day = 0; - static const month = 1; - static const year = 2; - static const lifetime = 3; - static const week = 4; - static const last7Days = 5; - static const last30Days = 6; +enum TimeStep { + day, + month, + year, + lifetime, + week, + last7Days, + last30Days; - TimeStep._create(); + static const options = [TimeStep.day, TimeStep.week, TimeStep.month, TimeStep.year, TimeStep.lifetime, TimeStep.last7Days, TimeStep.last30Days]; - static String getName(int opt, BuildContext context) { + static String getName(TimeStep opt, BuildContext context) { switch (opt) { - case day: + case TimeStep.day: return AppLocalizations.of(context)!.day; - case month: + case TimeStep.month: return AppLocalizations.of(context)!.month; - case year: + case TimeStep.year: return AppLocalizations.of(context)!.year; - case lifetime: + case TimeStep.lifetime: return AppLocalizations.of(context)!.lifetime; - case week: + case TimeStep.week: return AppLocalizations.of(context)!.week; - case last7Days: + case TimeStep.last7Days: return AppLocalizations.of(context)!.last7Days; - case last30Days: + case TimeStep.last30Days: return AppLocalizations.of(context)!.last30Days; } - assert(false); - return '-'; } } diff --git a/lib/screens/statistics.dart b/lib/screens/statistics.dart index aeeb156e..b7d2c03d 100644 --- a/lib/screens/statistics.dart +++ b/lib/screens/statistics.dart @@ -1,4 +1,7 @@ +import 'dart:collection'; + import 'package:blood_pressure_app/components/consistent_future_builder.dart'; +import 'package:blood_pressure_app/components/display_interval_picker.dart'; import 'package:blood_pressure_app/model/blood_pressure.dart'; import 'package:blood_pressure_app/model/blood_pressure_analyzer.dart'; import 'package:blood_pressure_app/model/settings_store.dart'; @@ -7,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; +// TODO: rewrite to be smaller is possible class StatisticsPage extends StatelessWidget { const StatisticsPage({super.key}); @@ -20,121 +24,131 @@ class StatisticsPage extends StatelessWidget { body: SingleChildScrollView(child: Consumer( builder: (context, model, child) { return Consumer(builder: (context, settings, child) { - return Column( - children: [ - Statistic( - key: const Key('measurementCount'), - caption: Text(AppLocalizations.of(context)!.measurementCount), child: futureInt(model.count)), - // special measurements - StatisticsRow( - caption1: Text( - AppLocalizations.of(context)!.avgOf(AppLocalizations.of(context)!.sysLong), - style: TextStyle(color: settings.sysColor, fontWeight: FontWeight.w700), - ), - child1: futureInt(model.avgSys), - caption2: Text( - AppLocalizations.of(context)!.avgOf(AppLocalizations.of(context)!.diaLong), - style: TextStyle(color: settings.diaColor, fontWeight: FontWeight.w700), - ), - child2: futureInt(model.avgDia), - caption3: Text( - AppLocalizations.of(context)!.avgOf(AppLocalizations.of(context)!.pulLong), - style: TextStyle(color: settings.pulColor, fontWeight: FontWeight.w700), - ), - child3: futureInt(model.avgPul), - ), - Statistic( - caption: Text(AppLocalizations.of(context)!.measurementsPerDay), - child: futureInt(BloodPressureAnalyser(model).measurementsPerDay)), - StatisticsRow( - caption1: Text( - AppLocalizations.of(context)!.minOf(AppLocalizations.of(context)!.sysLong), - style: TextStyle(color: settings.sysColor, fontWeight: FontWeight.w700), - ), - child1: futureInt(model.minSys), - caption2: Text( - AppLocalizations.of(context)!.minOf(AppLocalizations.of(context)!.diaLong), - style: TextStyle(color: settings.diaColor, fontWeight: FontWeight.w700), - ), - child2: futureInt(model.minDia), - caption3: Text( - AppLocalizations.of(context)!.minOf(AppLocalizations.of(context)!.pulLong), - style: TextStyle(color: settings.pulColor, fontWeight: FontWeight.w700), - ), - child3: futureInt(model.minPul), - ), - StatisticsRow( - caption2: Text( - AppLocalizations.of(context)!.maxOf(AppLocalizations.of(context)!.diaLong), - style: TextStyle(color: settings.diaColor, fontWeight: FontWeight.w700), - ), - child2: futureInt(model.maxDia), - caption1: Text( - AppLocalizations.of(context)!.maxOf(AppLocalizations.of(context)!.sysLong), - style: TextStyle(color: settings.sysColor, fontWeight: FontWeight.w700), - ), - child1: futureInt(model.maxSys), - caption3: Text( - AppLocalizations.of(context)!.maxOf(AppLocalizations.of(context)!.pulLong), - style: TextStyle(color: settings.pulColor, fontWeight: FontWeight.w700), - ), - child3: futureInt(model.maxPul), - ), - // Time-Resolved Metrics - Statistic( - caption: Text(AppLocalizations.of(context)!.timeResolvedMetrics), - child: ConsistentFutureBuilder>>( - future: BloodPressureAnalyser(model).allAvgsRelativeToDaytime, - onData: (context, data) { - const opacity = 0.5; - return SizedBox( - width: 500, - height: 500, - child: RadarChart( - RadarChartData( - radarShape: RadarShape.circle, - radarBorderData: const BorderSide(color: Colors.transparent), - gridBorderData: BorderSide(color: Theme.of(context).dividerColor, width: 2), - tickBorderData: BorderSide(color: Theme.of(context).dividerColor, width: 2), - ticksTextStyle: const TextStyle(color: Colors.transparent), - tickCount: 5, - titleTextStyle: const TextStyle(fontSize: 25), - getTitle: (pos, value) { - if (pos % 2 == 0) { - return RadarChartTitle(text: '$pos', positionPercentageOffset: 0.05); - } - return const RadarChartTitle(text: ''); - }, - dataSets: [ - RadarDataSet( - dataEntries: intListToRadarEntry(data[0]), - borderColor: settings.diaColor, - fillColor: settings.diaColor.withOpacity(opacity), - entryRadius: 0, - borderWidth: settings.graphLineThickness), - RadarDataSet( - dataEntries: intListToRadarEntry(data[1]), - borderColor: settings.sysColor, - fillColor: settings.sysColor.withOpacity(opacity), - entryRadius: 0, - borderWidth: settings.graphLineThickness), - RadarDataSet( - dataEntries: intListToRadarEntry(data[2]), - borderColor: settings.pulColor, - fillColor: settings.pulColor.withOpacity(opacity), - entryRadius: 0, - borderWidth: settings.graphLineThickness), - ], + return ConsistentFutureBuilder>( + future: model.getInTimeRange(settings.displayDataStart, settings.displayDataEnd), + onData: (context, data) { + final analyzer = BloodPressureAnalyser(data.toList()); + return Column( + children: [ + Statistic( + key: const Key('measurementCount'), + caption: Text(AppLocalizations.of(context)!.measurementCount), child: displayInt(analyzer.count)), + // special measurements + StatisticsRow( + caption1: Text( + AppLocalizations.of(context)!.avgOf(AppLocalizations.of(context)!.sysLong), + style: TextStyle(color: settings.sysColor, fontWeight: FontWeight.w700), + ), + child1: displayInt(analyzer.avgSys), + caption2: Text( + AppLocalizations.of(context)!.avgOf(AppLocalizations.of(context)!.diaLong), + style: TextStyle(color: settings.diaColor, fontWeight: FontWeight.w700), + ), + child2: displayInt(analyzer.avgDia), + caption3: Text( + AppLocalizations.of(context)!.avgOf(AppLocalizations.of(context)!.pulLong), + style: TextStyle(color: settings.pulColor, fontWeight: FontWeight.w700), + ), + child3: displayInt(analyzer.avgPul), + ), + Statistic( + caption: Text(AppLocalizations.of(context)!.measurementsPerDay), + child: displayInt(analyzer.measurementsPerDay)), + StatisticsRow( + caption1: Text( + AppLocalizations.of(context)!.minOf(AppLocalizations.of(context)!.sysLong), + style: TextStyle(color: settings.sysColor, fontWeight: FontWeight.w700), + ), + child1: displayInt(analyzer.minSys), + caption2: Text( + AppLocalizations.of(context)!.minOf(AppLocalizations.of(context)!.diaLong), + style: TextStyle(color: settings.diaColor, fontWeight: FontWeight.w700), + ), + child2: displayInt(analyzer.minDia), + caption3: Text( + AppLocalizations.of(context)!.minOf(AppLocalizations.of(context)!.pulLong), + style: TextStyle(color: settings.pulColor, fontWeight: FontWeight.w700), + ), + child3: displayInt(analyzer.minPul), + ), + StatisticsRow( + caption2: Text( + AppLocalizations.of(context)!.maxOf(AppLocalizations.of(context)!.diaLong), + style: TextStyle(color: settings.diaColor, fontWeight: FontWeight.w700), + ), + child2: displayInt(analyzer.maxDia), + caption1: Text( + AppLocalizations.of(context)!.maxOf(AppLocalizations.of(context)!.sysLong), + style: TextStyle(color: settings.sysColor, fontWeight: FontWeight.w700), + ), + child1: displayInt(analyzer.maxSys), + caption3: Text( + AppLocalizations.of(context)!.maxOf(AppLocalizations.of(context)!.pulLong), + style: TextStyle(color: settings.pulColor, fontWeight: FontWeight.w700), + ), + child3: displayInt(analyzer.maxPul), + ), + // Time-Resolved Metrics + Statistic( + caption: Text(AppLocalizations.of(context)!.timeResolvedMetrics), + child: (() { + final data = analyzer.allAvgsRelativeToDaytime; + const opacity = 0.5; + return SizedBox( + width: 500, + height: 500, + child: RadarChart( + RadarChartData( + radarShape: RadarShape.circle, + radarBorderData: const BorderSide(color: Colors.transparent), + gridBorderData: BorderSide(color: Theme.of(context).dividerColor, width: 2), + tickBorderData: BorderSide(color: Theme.of(context).dividerColor, width: 2), + ticksTextStyle: const TextStyle(color: Colors.transparent), + tickCount: 5, + titleTextStyle: const TextStyle(fontSize: 25), + getTitle: (pos, value) { + if (pos % 2 == 0) { + return RadarChartTitle(text: '$pos', positionPercentageOffset: 0.05); + } + return const RadarChartTitle(text: ''); + }, + dataSets: [ + RadarDataSet( + dataEntries: intListToRadarEntry(data[0]), + borderColor: settings.diaColor, + fillColor: settings.diaColor.withOpacity(opacity), + entryRadius: 0, + borderWidth: settings.graphLineThickness), + RadarDataSet( + dataEntries: intListToRadarEntry(data[1]), + borderColor: settings.sysColor, + fillColor: settings.sysColor.withOpacity(opacity), + entryRadius: 0, + borderWidth: settings.graphLineThickness), + RadarDataSet( + dataEntries: intListToRadarEntry(data[2]), + borderColor: settings.pulColor, + fillColor: settings.pulColor.withOpacity(opacity), + entryRadius: 0, + borderWidth: settings.graphLineThickness), + ], + ), ), - ), - ); - }), - ), - ], + ); + })(), + ), + ], + ); + } ); }); }, )), + bottomNavigationBar: Container( + height: 70, + margin: const EdgeInsets.only(top: 15, bottom: 5), + child: const IntervalPicker(), + ) ); } @@ -156,8 +170,8 @@ class Statistic extends StatelessWidget { @override Widget build(BuildContext context) { + const double top = 20; double sides = 20; - double top = 20; double padding = 20; if (smallEdges) { sides = 0; @@ -257,14 +271,9 @@ class StatisticsRow extends StatelessWidget { } } -Widget futureInt(Future value) { - return ConsistentFutureBuilder( - future: value, - onData: (context, data) { - if (data < 0) { - return const Text('-'); - } - return Text(data.toString()); - } - ); +Widget displayInt(int value) { + if (value < 0) { + return const Text('-'); + } + return Text(value.toString()); } diff --git a/lib/screens/subsettings/export_import_screen.dart b/lib/screens/subsettings/export_import_screen.dart index 3433debe..5fa76575 100644 --- a/lib/screens/subsettings/export_import_screen.dart +++ b/lib/screens/subsettings/export_import_screen.dart @@ -1,13 +1,13 @@ +import 'package:blood_pressure_app/components/display_interval_picker.dart'; import 'package:blood_pressure_app/components/settings_widgets.dart'; -import 'package:blood_pressure_app/model/blood_pressure.dart'; import 'package:blood_pressure_app/model/export_import.dart'; import 'package:blood_pressure_app/model/settings_store.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:intl/intl.dart'; import 'package:jsaver/jSaver.dart'; import 'package:provider/provider.dart'; +// TODO: control if warn messages work class ExportImportScreen extends StatelessWidget { const ExportImportScreen({super.key}); @@ -18,131 +18,86 @@ class ExportImportScreen extends StatelessWidget { title: Text(AppLocalizations.of(context)!.exportImport), backgroundColor: Theme.of(context).primaryColor, ), - body: Container( - margin: const EdgeInsets.only(bottom: 80), - child: Consumer(builder: (context, settings, child) { - return SingleChildScrollView( - child: Column( - children: [ - const ExportWarnBanner(), - SettingsTile( - title: Text(AppLocalizations.of(context)!.exportDir), - description: Text(settings.defaultExportDir), - onPressed: (context) async { - final appDir = await JSaver.instance.setDefaultSavingDirectory(); - settings.defaultExportDir = appDir.value; + body: Consumer(builder: (context, settings, child) { + return SingleChildScrollView( + child: Column( + children: [ + const ExportWarnBanner(), + const SizedBox(height: 15,), + Opacity( + opacity: (settings.exportFormat == ExportFormat.db) ? 0.5 : 1, // TODO: centralize when restyle + child: const IntervalPicker(), + ), + SettingsTile( + title: Text(AppLocalizations.of(context)!.exportDir), + description: Text(settings.defaultExportDir), + onPressed: (context) async { + final appDir = await JSaver.instance.setDefaultSavingDirectory(); + settings.defaultExportDir = appDir.value; + } + ), + SwitchSettingsTile( + title: Text(AppLocalizations.of(context)!.exportAfterEveryInput), + description: Text(AppLocalizations.of(context)!.exportAfterEveryInputDesc), + initialValue: settings.exportAfterEveryEntry, + onToggle: (value) { + settings.exportAfterEveryEntry = value; } - ), - SwitchSettingsTile( - title: Text(AppLocalizations.of(context)!.exportAfterEveryInput), - description: Text(AppLocalizations.of(context)!.exportAfterEveryInputDesc), - initialValue: settings.exportAfterEveryEntry, - onToggle: (value) { - settings.exportAfterEveryEntry = value; - } - ), - DropDownSettingsTile( - key: const Key('exportFormat'), - title: Text(AppLocalizations.of(context)!.exportFormat), - value: settings.exportFormat, - items: [ - DropdownMenuItem(value: ExportFormat.csv, child: Text(AppLocalizations.of(context)!.csv)), - //DropdownMenuItem(value: ExportFormat.pdf, child: Text(AppLocalizations.of(context)!.pdf)), - DropdownMenuItem(value: ExportFormat.db, child: Text(AppLocalizations.of(context)!.db)), - ], - onChanged: (ExportFormat? value) { - if (value != null) { - settings.exportFormat = value; - } - }, - ), - const ExportDataRangeSettings(), - InputSettingsTile( - title: Text(AppLocalizations.of(context)!.fieldDelimiter), - inputWidth: 40, - initialValue: settings.csvFieldDelimiter, - disabled: !(settings.exportFormat == ExportFormat.csv), - onEditingComplete: (value) { - if (value != null) { - settings.csvFieldDelimiter = value; - } - }, - ), - InputSettingsTile( - title: Text(AppLocalizations.of(context)!.textDelimiter), - inputWidth: 40, - initialValue: settings.csvTextDelimiter, + ), + DropDownSettingsTile( + key: const Key('exportFormat'), + title: Text(AppLocalizations.of(context)!.exportFormat), + value: settings.exportFormat, + items: [ + DropdownMenuItem(value: ExportFormat.csv, child: Text(AppLocalizations.of(context)!.csv)), + //DropdownMenuItem(value: ExportFormat.pdf, child: Text(AppLocalizations.of(context)!.pdf)), + DropdownMenuItem(value: ExportFormat.db, child: Text(AppLocalizations.of(context)!.db)), + ], + onChanged: (ExportFormat? value) { + if (value != null) { + settings.exportFormat = value; + } + }, + ), + InputSettingsTile( + title: Text(AppLocalizations.of(context)!.fieldDelimiter), + inputWidth: 40, + initialValue: settings.csvFieldDelimiter, + disabled: !(settings.exportFormat == ExportFormat.csv), + onEditingComplete: (value) { + if (value != null) { + settings.csvFieldDelimiter = value; + } + }, + ), + InputSettingsTile( + title: Text(AppLocalizations.of(context)!.textDelimiter), + inputWidth: 40, + initialValue: settings.csvTextDelimiter, + disabled: !(settings.exportFormat == ExportFormat.csv), + onEditingComplete: (value) { + if (value != null) { + settings.csvTextDelimiter = value; + } + }, + ), + SwitchSettingsTile( + title: Text(AppLocalizations.of(context)!.exportCsvHeadline), + description: Text(AppLocalizations.of(context)!.exportCsvHeadlineDesc), + initialValue: settings.exportCsvHeadline, disabled: !(settings.exportFormat == ExportFormat.csv), - onEditingComplete: (value) { - if (value != null) { - settings.csvTextDelimiter = value; - } - }, - ), - SwitchSettingsTile( - title: Text(AppLocalizations.of(context)!.exportCsvHeadline), - description: Text(AppLocalizations.of(context)!.exportCsvHeadlineDesc), - initialValue: settings.exportCsvHeadline, - disabled: !(settings.exportFormat == ExportFormat.csv), - onToggle: (value) { - settings.exportCsvHeadline = value; - } - ), - const ExportFieldCustomisationSetting(), - ], - ), - ); - }) - ), - floatingActionButton: const ExportImportButtons(), - ); - } -} - -class ExportDataRangeSettings extends StatelessWidget { - const ExportDataRangeSettings({super.key}); - - @override - Widget build(BuildContext context) { - return Consumer(builder: (context, settings, child) { - var exportRange = settings.exportDataRange; - String exportRangeText; - if (exportRange.start.millisecondsSinceEpoch != 0 && exportRange.end.millisecondsSinceEpoch != 0) { - var formatter = DateFormat.yMMMd(AppLocalizations.of(context)!.localeName); - exportRangeText = '${formatter.format(exportRange.start)} - ${formatter.format(exportRange.end)}'; - } else { - exportRangeText = AppLocalizations.of(context)!.errPleaseSelect; - } - return Column( - children: [ - SwitchSettingsTile( - title: Text(AppLocalizations.of(context)!.exportLimitDataRange), - initialValue: settings.exportLimitDataRange, - onToggle: (value) { - settings.exportLimitDataRange = value; - }, - disabled: settings.exportFormat == ExportFormat.db, - ), - SettingsTile( - title: Text(AppLocalizations.of(context)!.exportInterval), - description: Text(exportRangeText), - disabled: !settings.exportLimitDataRange || settings.exportFormat == ExportFormat.db, - onPressed: (context) async { - var model = Provider.of(context, listen: false); - var newRange = await showDateRangePicker(context: context, firstDate: await model.firstDay, lastDate: await model.lastDay); - if (newRange == null && context.mounted) { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.errNoRangeForExport))); - return; - } - settings.exportDataRange = newRange ?? DateTimeRange(start: DateTime.fromMillisecondsSinceEpoch(0), end: DateTime.fromMillisecondsSinceEpoch(0)); - } + onToggle: (value) { + settings.exportCsvHeadline = value; + } + ), + const ExportFieldCustomisationSetting(), + ], ), - ], - ); - }); + ); + }), + bottomNavigationBar: const ExportImportButtons(), + ); } - } class ExportFieldCustomisationSetting extends StatelessWidget { diff --git a/test/model/analyzer_test.dart b/test/model/analyzer_test.dart new file mode 100644 index 00000000..de2cefc1 --- /dev/null +++ b/test/model/analyzer_test.dart @@ -0,0 +1,67 @@ + +import 'package:blood_pressure_app/model/blood_pressure.dart'; +import 'package:blood_pressure_app/model/blood_pressure_analyzer.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('BloodPressureAnalyser', () { + test('should return averages', () async { + var m = BloodPressureAnalyser([ + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 122, 87, 65, ''), + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, ''), + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, '') + ]); + + expect(m.avgSys, 111); + expect(m.avgDia, 73); + expect(m.avgPul, 66); + }); + + test('should return max', () async { + var a = BloodPressureAnalyser([ + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 123, 87, 65, ''), + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, ''), + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, ''), + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(4), 111, 73, 73, '') + ]); + + expect(a.maxSys, 123); + expect(a.maxDia, 87); + expect(a.maxPul, 73); + }); + + test('should return min', () async { + var a = BloodPressureAnalyser([ + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 123, 87, 65, ''), + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, ''), + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, ''), + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(4), 100, 60, 62, '') + ]); + + expect(a.minSys, 100); + expect(a.minDia, 60); + expect(a.minPul, 62); + }); + + test('should know count', () async { + var m = BloodPressureAnalyser([ + for (int i = 1; i < 101; i++) + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(i), 0, 0, 0, '') + ]); + expect(m.count, 100); + }); + + test('should determine special days', () async { + var m = BloodPressureAnalyser([BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(100), 0, 0, 0, ''), + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(-2200), 0, 0, 0, ''), + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(9000000), 0, 0, 0, ''), + BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3124159), 0, 0, 0, ''), + ]); + + expect((m.firstDay), DateTime.fromMillisecondsSinceEpoch(-2200)); + expect((m.lastDay), DateTime.fromMillisecondsSinceEpoch(9000000)); + }); + + // TODO null tests, test with 1 element + }); +} \ No newline at end of file diff --git a/test/model/bood_pressure_test.dart b/test/model/bood_pressure_test.dart index e3e56f11..516e5dd5 100644 --- a/test/model/bood_pressure_test.dart +++ b/test/model/bood_pressure_test.dart @@ -95,66 +95,6 @@ void main() { expect((await m.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(0), DateTime.now())).length, 0); expect((await m.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(0), DateTime.now())).length, 0); }); - - test('should return averages', () async { - var m = await BloodPressureModel.create(dbPath: '/tmp/bp_test/should_avg'); - - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 122, 87, 65, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, '')); - - expect(await m.avgSys, 111); // 111 // gets 116 - expect(await m.avgDia, 73); // 73.3333... - expect(await m.avgPul, 66); // 66.6666... - }); - - test('should return max', () async { - var m = await BloodPressureModel.create(dbPath: '/tmp/bp_test/should_max'); - - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 123, 87, 65, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(4), 111, 73, 73, '')); - - expect(await m.maxSys, 123); - expect(await m.maxDia, 87); - expect(await m.maxPul, 73); - }); - - test('should return min', () async { - var m = await BloodPressureModel.create(dbPath: '/tmp/bp_test/should_min'); - - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 123, 87, 65, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(4), 100, 60, 62, '')); - - expect(await m.minSys, 100); - expect(await m.minDia, 60); - expect(await m.minPul, 62); - }); - - test('should know count', () async { - var m = await BloodPressureModel.create(dbPath: '/tmp/bp_test/should_count'); - - for (int i = 1; i < 101; i++) { - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(i), 0, 0, 0, '')); - } - - expect(await m.count, 100); - }); - - test('should determine special days', () async { - var m = await BloodPressureModel.create(dbPath: '/tmp/bp_test/should_special_days'); - - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(100), 0, 0, 0, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(-2200), 0, 0, 0, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(9000000), 0, 0, 0, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3124159), 0, 0, 0, '')); - - expect((await m.firstDay), DateTime.fromMillisecondsSinceEpoch(-2200)); - expect((await m.lastDay), DateTime.fromMillisecondsSinceEpoch(9000000)); - }); }); group("RamBloodPressureModel should behave like BloodPressureModel", () { @@ -200,65 +140,5 @@ void main() { m.add(r); }); - - test('should return averages', () async { - var m = RamBloodPressureModel(); - - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 122, 87, 65, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, '')); - - expect(await m.avgSys, 111); // 111 // gets 116 - expect(await m.avgDia, 73); // 73.3333... - expect(await m.avgPul, 66); // 66.6666... - }); - - test('should return max', () async { - var m = RamBloodPressureModel(); - - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 123, 87, 65, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(4), 111, 73, 73, '')); - - expect(await m.maxSys, 123); - expect(await m.maxDia, 87); - expect(await m.maxPul, 73); - }); - - test('should return min', () async { - var m = RamBloodPressureModel(); - - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 123, 87, 65, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(2), 100, 60, 62, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3), 111, 73, 73, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(4), 100, 60, 62, '')); - - expect(await m.minSys, 100); - expect(await m.minDia, 60); - expect(await m.minPul, 62); - }); - - test('should know count', () async { - var m = RamBloodPressureModel(); - - for (int i = 1; i < 101; i++) { - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(i), 0, 0, 0, '')); - } - - expect(await m.count, 100); - }); - - test('should determine special days', () async { - var m = RamBloodPressureModel(); - - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(100), 0, 0, 0, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(-2200), 0, 0, 0, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(9000000), 0, 0, 0, '')); - await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(3124159), 0, 0, 0, '')); - - expect((await m.firstDay), DateTime.fromMillisecondsSinceEpoch(-2200)); - expect((await m.lastDay), DateTime.fromMillisecondsSinceEpoch(9000000)); - }); }); } diff --git a/test/model/settings_test.dart b/test/model/settings_test.dart index 0948ab74..f6da8457 100644 --- a/test/model/settings_test.dart +++ b/test/model/settings_test.dart @@ -1,7 +1,6 @@ import 'package:blood_pressure_app/model/ram_only_implementations.dart'; import 'package:blood_pressure_app/model/settings_store.dart'; import 'package:file_saver/file_saver.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; @@ -44,8 +43,6 @@ void main() { expect(s.exportItems, ['timestampUnixMs', 'systolic', 'diastolic', 'pulse', 'notes']); expect(s.exportAddableItems, ['isoUTCTime']); expect(s.exportCsvHeadline, true); - expect(s.exportDataRange.start.millisecondsSinceEpoch, 0); - expect(s.exportLimitDataRange, false); expect(s.exportMimeType, MimeType.csv); expect(s.defaultExportDir.isEmpty, true); expect(s.exportAfterEveryEntry, false); @@ -90,7 +87,6 @@ void main() { s.exportAddableItems = ['timestampUnixMs']; s.exportItems = ['systolic', 'diastolic', 'pulse', 'notes', 'isoUTCTime']; s.exportCsvHeadline = false; - s.exportLimitDataRange = true; s.exportMimeType = MimeType.pdf; s.defaultExportDir = '/storage/emulated/0/Android/data/com.derdilla.bloodPressureApp/files/file.csv'; s.exportAfterEveryEntry = true; @@ -118,7 +114,6 @@ void main() { expect(s.exportItems, ['systolic', 'diastolic', 'pulse', 'notes', 'isoUTCTime']); expect(s.exportAddableItems, ['timestampUnixMs']); expect(s.exportCsvHeadline, false); - expect(s.exportLimitDataRange, true); expect(s.exportMimeType, MimeType.pdf); expect(s.defaultExportDir, '/storage/emulated/0/Android/data/com.derdilla.bloodPressureApp/files/file.csv'); expect(s.exportAfterEveryEntry, true); @@ -157,14 +152,12 @@ void main() { s.exportAddableItems = ['timestampUnixMs']; s.exportItems = ['systolic', 'diastolic', 'pulse', 'notes', 'isoUTCTime']; s.exportCsvHeadline = false; - s.exportDataRange = DateTimeRange(start: DateTime.fromMillisecondsSinceEpoch(20), end: DateTime.now()); - s.exportLimitDataRange = true; s.exportMimeType = MimeType.pdf; s.defaultExportDir = '/storage/emulated/0/Android/data/com.derdilla.bloodPressureApp/files/file.csv'; s.exportAfterEveryEntry = true; s.allowMissingValues = true; - expect(i, 30); + expect(i, 28); }); }); @@ -202,8 +195,6 @@ void main() { expect(s.exportItems, ['timestampUnixMs', 'systolic', 'diastolic', 'pulse', 'notes']); expect(s.exportAddableItems, ['isoUTCTime']); expect(s.exportCsvHeadline, true); - expect(s.exportDataRange.start.millisecondsSinceEpoch, 0); - expect(s.exportLimitDataRange, false); expect(s.exportMimeType, MimeType.csv); expect(s.defaultExportDir.isEmpty, true); expect(s.exportAfterEveryEntry, false); @@ -248,7 +239,6 @@ void main() { s.exportAddableItems = ['timestampUnixMs']; s.exportItems = ['systolic', 'diastolic', 'pulse', 'notes', 'isoUTCTime']; s.exportCsvHeadline = false; - s.exportLimitDataRange = true; s.exportMimeType = MimeType.pdf; s.defaultExportDir = '/storage/emulated/0/Android/data/com.derdilla.bloodPressureApp/files/file.csv'; s.exportAfterEveryEntry = true; @@ -276,7 +266,6 @@ void main() { expect(s.exportItems, ['systolic', 'diastolic', 'pulse', 'notes', 'isoUTCTime']); expect(s.exportAddableItems, ['timestampUnixMs']); expect(s.exportCsvHeadline, false); - expect(s.exportLimitDataRange, true); expect(s.exportMimeType, MimeType.pdf); expect(s.defaultExportDir, '/storage/emulated/0/Android/data/com.derdilla.bloodPressureApp/files/file.csv'); expect(s.exportAfterEveryEntry, true); @@ -316,14 +305,12 @@ void main() { s.exportAddableItems = ['timestampUnixMs']; s.exportItems = ['systolic', 'diastolic', 'pulse', 'notes', 'isoUTCTime']; s.exportCsvHeadline = false; - s.exportDataRange = DateTimeRange(start: DateTime.fromMillisecondsSinceEpoch(20), end: DateTime.now()); - s.exportLimitDataRange = true; s.exportMimeType = MimeType.pdf; s.defaultExportDir = '/storage/emulated/0/Android/data/com.derdilla.bloodPressureApp/files/file.csv'; s.exportAfterEveryEntry = true; s.allowMissingValues = true; - expect(i, 30); + expect(i, 28); }); }); } diff --git a/test/ui/statistics_test.dart b/test/ui/statistics_test.dart index e3831e08..58158f09 100644 --- a/test/ui/statistics_test.dart +++ b/test/ui/statistics_test.dart @@ -10,12 +10,12 @@ import 'package:provider/provider.dart'; void main() { group("StatisticsPage", () { testWidgets('should load page', (widgetTester) async { - await _initStatsPage(widgetTester, []); + await _initStatsPage(widgetTester, RamSettings(), []); expect(find.text('Statistics'), findsOneWidget); }); testWidgets("should report measurement count", (widgetTester) async { - await _initStatsPage(widgetTester, [ - for (int i = 0; i<50; i++) + await _initStatsPage(widgetTester, _allMeasurements(), [ + for (int i = 1; i<51; i++) // can't safe entries at or before epoch BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(i), 40+i, 60+i, 30+i, 'Test comment $i'), ]); final measurementCountWidget = find.byKey(const Key('measurementCount')); @@ -25,9 +25,8 @@ void main() { }); } -Future _initStatsPage(WidgetTester widgetTester, List records) async { +Future _initStatsPage(WidgetTester widgetTester, Settings settings, List records) async { final model = RamBloodPressureModel(); - final settings = RamSettings(); for (var r in records) { model.add(r); @@ -45,4 +44,10 @@ Future _initStatsPage(WidgetTester widgetTester, List ) )); await widgetTester.pumpAndSettle(); +} + +RamSettings _allMeasurements() { + final settings = RamSettings(); + settings.changeStepSize(TimeStep.lifetime); + return settings; } \ No newline at end of file