Commit f47aa77
Changed files (29)
lib
components
model
screens
lib/components/measurement_list/measurement_list.dart
@@ -1,7 +1,8 @@
-import 'package:blood_pressure_app/components/consistent_future_builder.dart';
+import 'package:blood_pressure_app/components/blood_pressure_builder.dart';
import 'package:blood_pressure_app/components/measurement_list/measurement_list_entry.dart';
import 'package:blood_pressure_app/model/blood_pressure.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
@@ -11,29 +12,21 @@ class MeasurementList extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Consumer<Settings>(
- builder: (context, settings, child) {
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const MeasurementListHeader(),
- Expanded(
- child: Consumer<BloodPressureModel>(
- builder: (context, model, child) {
- return ConsistentFutureBuilder(
- future: model.getInTimeRange(settings.displayDataStart, settings.displayDataEnd),
- onData: (context, data) {
- return MeasurementListEntries(
- entries: data
- );
- }
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const MeasurementListHeader(),
+ Expanded(
+ child: BloodPressureBuilder(
+ rangeType: IntervallStoreManagerLocation.mainPage,
+ onData: (context, data) {
+ return MeasurementListEntries(
+ entries: data
);
},
)
- )
- ]
- );
- },
+ )
+ ]
);
}
}
lib/components/measurement_list/measurement_list_entry.dart
@@ -1,5 +1,5 @@
import 'package:blood_pressure_app/model/blood_pressure.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:blood_pressure_app/screens/add_measurement.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
lib/components/blood_pressure_builder.dart
@@ -0,0 +1,32 @@
+import 'dart:collection';
+
+import 'package:blood_pressure_app/components/consistent_future_builder.dart';
+import 'package:blood_pressure_app/model/blood_pressure.dart';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+/// Shorthand class for getting the blood pressure values
+class BloodPressureBuilder extends StatelessWidget {
+ const BloodPressureBuilder({required this.onData, required this.rangeType, super.key});
+
+ final Widget Function(BuildContext context, UnmodifiableListView<BloodPressureRecord> records) onData;
+ final IntervallStoreManagerLocation rangeType;
+
+ @override
+ Widget build(BuildContext context) {
+ return Consumer<BloodPressureModel>(
+ builder: (context, model, child) =>
+ Consumer<IntervallStoreManager>(
+ builder: (context, intervallManager, child) {
+ final range = intervallManager.get(rangeType).currentRange;
+ return ConsistentFutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
+ future: model.getInTimeRange(range.start, range.end),
+ onData: onData,
+ );
+ }
+ )
+ );
+ }
+
+}
lib/components/display_interval_picker.dart
@@ -1,30 +1,33 @@
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class IntervalPicker extends StatelessWidget {
- const IntervalPicker({super.key});
+ const IntervalPicker({super.key, required this.type});
+ final IntervallStoreManagerLocation type;
+
@override
Widget build(BuildContext context) {
- return Consumer<Settings>(builder: (context, settings, child) {
+ return Consumer<IntervallStoreManager>(builder: (context, intervallStoreManager, child) {
+ final intervall = intervallStoreManager.get(type);
final String intervallDisplayText;
- switch (settings.graphStepSize) {
+ switch (intervall.stepSize) {
case TimeStep.day:
- intervallDisplayText = DateFormat.yMMMd(AppLocalizations.of(context)!.localeName).format(settings.displayDataStart);
+ intervallDisplayText = DateFormat.yMMMd(AppLocalizations.of(context)!.localeName).format(intervall.currentRange.start);
break;
case TimeStep.week:
- final dayOfYear = int.parse(DateFormat("D").format(settings.displayDataStart));
- final weekOfYear = ((dayOfYear - settings.displayDataStart.weekday + 10) / 7).floor();
- intervallDisplayText = AppLocalizations.of(context)!.weekOfYear(weekOfYear, settings.displayDataStart.year);
+ final dayOfYear = int.parse(DateFormat("D").format(intervall.currentRange.start));
+ final weekOfYear = ((dayOfYear - intervall.currentRange.start.weekday + 10) / 7).floor();
+ intervallDisplayText = AppLocalizations.of(context)!.weekOfYear(weekOfYear, intervall.currentRange.start.year);
break;
case TimeStep.month:
- intervallDisplayText = DateFormat.yMMM(AppLocalizations.of(context)!.localeName).format(settings.displayDataStart);
+ intervallDisplayText = DateFormat.yMMM(AppLocalizations.of(context)!.localeName).format(intervall.currentRange.start);
break;
case TimeStep.year:
- intervallDisplayText = DateFormat.y(AppLocalizations.of(context)!.localeName).format(settings.displayDataStart);
+ intervallDisplayText = DateFormat.y(AppLocalizations.of(context)!.localeName).format(intervall.currentRange.start);
break;
case TimeStep.lifetime:
intervallDisplayText = '-';
@@ -33,7 +36,7 @@ class IntervalPicker extends StatelessWidget {
case TimeStep.last30Days:
case TimeStep.custom:
final f = DateFormat.yMMMd(AppLocalizations.of(context)!.localeName);
- intervallDisplayText = '${f.format(settings.displayDataStart)} - ${f.format(settings.displayDataEnd)}';
+ intervallDisplayText = '${f.format(intervall.currentRange.start)} - ${f.format(intervall.currentRange.end)}';
break;
}
return Column(
@@ -47,7 +50,7 @@ class IntervalPicker extends StatelessWidget {
flex: 3,
child: MaterialButton(
onPressed: () {
- settings.moveDisplayDataByStep(-1);
+ intervall.moveDataRangeByStep(-1);
},
child: const Icon(
Icons.chevron_left,
@@ -58,7 +61,7 @@ class IntervalPicker extends StatelessWidget {
Expanded(
flex: 4,
child: DropdownButton<TimeStep>(
- value: settings.graphStepSize,
+ value: intervall.stepSize,
isExpanded: true,
onChanged: (TimeStep? value) async {
if (value == TimeStep.custom) {
@@ -67,12 +70,11 @@ class IntervalPicker extends StatelessWidget {
firstDate: DateTime.fromMillisecondsSinceEpoch(1),
lastDate: DateTime.now());
if (res != null) {
- settings.graphStepSize = value!;
- settings.displayDataStart = res.start;
- settings.displayDataEnd = res.end;
+ intervall.changeStepSize(value!);
+ intervall.currentRange = res;
}
} else if (value != null) {
- settings.changeStepSize(value);
+ intervall.changeStepSize(value);
}
},
items: TimeStep.options.map<DropdownMenuItem<TimeStep>>((v) {
@@ -84,7 +86,7 @@ class IntervalPicker extends StatelessWidget {
flex: 3,
child: MaterialButton(
onPressed: () {
- settings.moveDisplayDataByStep(1);
+ intervall.moveDataRangeByStep(1);
},
child: const Icon(
Icons.chevron_right,
lib/components/export_item_order.dart
@@ -3,9 +3,10 @@ import 'dart:async';
import 'package:badges/badges.dart' as badges;
import 'package:blood_pressure_app/components/consistent_future_builder.dart';
-import 'package:blood_pressure_app/model/export_import.dart';
import 'package:blood_pressure_app/model/export_options.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
import 'package:blood_pressure_app/screens/subsettings/export_column_data.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -27,7 +28,7 @@ class _ExportItemsCustomizerState extends State<ExportItemsCustomizer> {
@override
Widget build(BuildContext context) {
return ConsistentFutureBuilder(
- future: ExportConfigurationModel.get(Provider.of<Settings>(context, listen: false), AppLocalizations.of(context)!),
+ future: ExportConfigurationModel.get(AppLocalizations.of(context)!),
onData: (BuildContext context, ExportConfigurationModel result) {
return _buildAddItemBadge(context, result,
child: _buildManagePresetsBadge(context, result,
@@ -117,12 +118,12 @@ class _ExportItemsCustomizerState extends State<ExportItemsCustomizer> {
];
},
onSelected: (value) {
- final settings = Provider.of<Settings>(context, listen: false);
- if (settings.exportFormat == ExportFormat.csv) {
- settings.exportItemsCsv = exportConfigurations[value].$2;
+ final exportSettings = Provider.of<ExportSettings>(context, listen: false);
+ if (exportSettings.exportFormat == ExportFormat.csv) {
+ Provider.of<CsvExportSettings>(context, listen: false).customFields = exportConfigurations[value].$2;
} else {
- assert(settings.exportFormat == ExportFormat.pdf);
- settings.exportItemsPdf = exportConfigurations[value].$2;
+ assert(exportSettings.exportFormat == ExportFormat.pdf);
+ Provider.of<PdfExportSettings>(context, listen: false).customFields = exportConfigurations[value].$2;
}
},
),
lib/components/input_dialoge.dart
@@ -1,8 +1,6 @@
-import 'package:blood_pressure_app/model/settings_store.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'package:provider/provider.dart';
// TODO: redo dialoges in flutter style
class InputDialoge extends StatefulWidget {
@@ -42,15 +40,12 @@ class _InputDialogeState extends State<InputDialoge> {
decoration: InputDecoration(hintText: widget.hintText),
),
actions: [
- Consumer<Settings>(builder: (context, settings, child) {
- return ElevatedButton(
- onPressed: () {
- widget.onSubmit(controller.text);
- },
- child: Text(AppLocalizations.of(context)!.btnConfirm)
- );
- }),
-
+ ElevatedButton(
+ onPressed: () {
+ widget.onSubmit(controller.text);
+ },
+ child: Text(AppLocalizations.of(context)!.btnConfirm)
+ )
],
);
}
lib/components/legacy_measurement_list.dart
@@ -1,8 +1,9 @@
import 'dart:collection';
-import 'package:blood_pressure_app/components/consistent_future_builder.dart';
+import 'package:blood_pressure_app/components/blood_pressure_builder.dart';
import 'package:blood_pressure_app/model/blood_pressure.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:blood_pressure_app/screens/add_measurement.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -25,181 +26,173 @@ class LegacyMeasurementsList extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Column(
+ return Consumer<Settings>(builder: (context, settings, child) =>
+ Column(
children: [
- Consumer<Settings>(builder: (context, settings, child) {
- return Column(children: [
- Row(
- children: [
- Expanded(
- flex: _sideFlex,
- child: const SizedBox(),
- ),
- Expanded(
- flex: _tableElementsSizes[0],
- child: Text(AppLocalizations.of(context)!.time, style: const TextStyle(fontWeight: FontWeight.bold))),
- Expanded(
- flex: _tableElementsSizes[1],
- child: Text(AppLocalizations.of(context)!.sysShort, style: TextStyle(fontWeight: FontWeight.bold, color: settings.sysColor))),
- Expanded(
- flex: _tableElementsSizes[2],
- child: Text(AppLocalizations.of(context)!.diaShort, style: TextStyle(fontWeight: FontWeight.bold, color: settings.diaColor))),
- Expanded(
- flex: _tableElementsSizes[3],
- child: Text(AppLocalizations.of(context)!.pulShort, style: TextStyle(fontWeight: FontWeight.bold, color: settings.pulColor))),
- Expanded(
- flex: _tableElementsSizes[4],
- child: Text(AppLocalizations.of(context)!.notes, style: const TextStyle(fontWeight: FontWeight.bold))),
- Expanded(
- flex: _sideFlex,
- child: const SizedBox(),
- ),
- ],
+ Row(
+ children: [
+ Expanded(
+ flex: _sideFlex,
+ child: const SizedBox(),
),
- const SizedBox(
- height: 10,
+ Expanded(
+ flex: _tableElementsSizes[0],
+ child: Text(AppLocalizations.of(context)!.time, style: const TextStyle(fontWeight: FontWeight.bold))),
+ Expanded(
+ flex: _tableElementsSizes[1],
+ child: Text(AppLocalizations.of(context)!.sysShort, style: TextStyle(fontWeight: FontWeight.bold, color: settings.sysColor))),
+ Expanded(
+ flex: _tableElementsSizes[2],
+ child: Text(AppLocalizations.of(context)!.diaShort, style: TextStyle(fontWeight: FontWeight.bold, color: settings.diaColor))),
+ Expanded(
+ flex: _tableElementsSizes[3],
+ child: Text(AppLocalizations.of(context)!.pulShort, style: TextStyle(fontWeight: FontWeight.bold, color: settings.pulColor))),
+ Expanded(
+ flex: _tableElementsSizes[4],
+ child: Text(AppLocalizations.of(context)!.notes, style: const TextStyle(fontWeight: FontWeight.bold))),
+ Expanded(
+ flex: _sideFlex,
+ child: const SizedBox(),
),
- Divider(
- height: 0,
- thickness: 2,
- color: Theme.of(context).colorScheme.primaryContainer,
- )
- ]);
- }),
+ ],
+ ),
+ const SizedBox(
+ height: 10,
+ ),
+ Divider(
+ height: 0,
+ thickness: 2,
+ color: Theme.of(context).colorScheme.primaryContainer,
+ ),
Expanded(
- child: Consumer<BloodPressureModel>(builder: (context, model, child) {
- return Consumer<Settings>(builder: (context, settings, child) {
- return ConsistentFutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
- future: model.getInTimeRange(settings.displayDataStart, settings.displayDataEnd),
- onData: (context, data) {
- if (data.isNotEmpty) {
- return ListView.builder(
- itemCount: data.length,
- shrinkWrap: true,
- padding: const EdgeInsets.all(2),
- itemBuilder: (context, index) {
- final formatter = DateFormat(settings.dateFormatString);
- return Column(
- children: [
- Dismissible(
- key: Key(data[index].creationTime.toIso8601String()),
- confirmDismiss: (direction) async {
- final model = Provider.of<BloodPressureModel>(context, listen: false);
- if (direction == DismissDirection.startToEnd) { // edit
- Navigator.push(
- context,
- MaterialPageRoute(builder: (context) => AddMeasurementPage.edit(data[index])),
- );
- return false;
- } else { // delete
- bool dialogeDeletionConfirmed = false;
- if (settings.confirmDeletion) {
- await showDialog(
- context: context,
- builder: (context) {
- return AlertDialog(
- title: Text(AppLocalizations.of(context)!.confirmDelete),
- content: Text(AppLocalizations.of(context)!.confirmDeleteDesc),
- actions: [
- ElevatedButton(
- onPressed: () => Navigator.of(context).pop(),
- child: Text(AppLocalizations.of(context)!.btnCancel)),
- ElevatedButton(
- onPressed: () {
- model.delete(data[index].creationTime);
+ child: BloodPressureBuilder(
+ rangeType: IntervallStoreManagerLocation.mainPage,
+ onData: (BuildContext context, UnmodifiableListView<BloodPressureRecord> data) {
+ if (data.isNotEmpty) {
+ return ListView.builder(
+ itemCount: data.length,
+ shrinkWrap: true,
+ padding: const EdgeInsets.all(2),
+ itemBuilder: (context, index) {
+ final formatter = DateFormat(settings.dateFormatString);
+ return Column(
+ children: [
+ Dismissible(
+ key: Key(data[index].creationTime.toIso8601String()),
+ confirmDismiss: (direction) async {
+ final model = Provider.of<BloodPressureModel>(context, listen: false);
+ if (direction == DismissDirection.startToEnd) { // edit
+ Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => AddMeasurementPage.edit(data[index])),
+ );
+ return false;
+ } else { // delete
+ bool dialogeDeletionConfirmed = false;
+ if (settings.confirmDeletion) {
+ await showDialog(
+ context: context,
+ builder: (context) {
+ return AlertDialog(
+ title: Text(AppLocalizations.of(context)!.confirmDelete),
+ content: Text(AppLocalizations.of(context)!.confirmDeleteDesc),
+ actions: [
+ ElevatedButton(
+ onPressed: () => Navigator.of(context).pop(),
+ child: Text(AppLocalizations.of(context)!.btnCancel)),
+ ElevatedButton(
+ onPressed: () {
+ model.delete(data[index].creationTime);
- dialogeDeletionConfirmed = true;
- Navigator.of(context).pop();
- },
- child: Text(AppLocalizations.of(context)!.btnConfirm)),
- ],
- );
- });
- } else {
- model.delete(data[index].creationTime);
- dialogeDeletionConfirmed = true;
- }
+ dialogeDeletionConfirmed = true;
+ Navigator.of(context).pop();
+ },
+ child: Text(AppLocalizations.of(context)!.btnConfirm)),
+ ],
+ );
+ });
+ } else {
+ model.delete(data[index].creationTime);
+ dialogeDeletionConfirmed = true;
+ }
- if (dialogeDeletionConfirmed) {
- if (!context.mounted) return true;
- ScaffoldMessenger.of(context).removeCurrentSnackBar();
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(
- duration: const Duration(seconds: 5),
- content: Text(AppLocalizations.of(context)!.deletionConfirmed),
- action: SnackBarAction(
- label: AppLocalizations.of(context)!.btnUndo,
- onPressed: () async {
- model.add(BloodPressureRecord(
- data[index].creationTime,
- data[index].systolic,
- data[index].diastolic,
- data[index].pulse,
- data[index].notes));
- },
- ),
- ));
- }
- return dialogeDeletionConfirmed;
- }
- },
- onDismissed: (direction) {},
- background: Container(
- width: 10,
- decoration:
- BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(5)),
- child: const Align(alignment: Alignment(-0.95, 0), child: Icon(Icons.edit)),
- ),
- secondaryBackground: Container(
- width: 10,
- decoration:
- BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(5)),
- child: const Align(alignment: Alignment(0.95, 0), child: Icon(Icons.delete)),
- ),
- child: Container(
- constraints: const BoxConstraints(minHeight: 40),
- child: Row(children: [
- Expanded(
- flex: _sideFlex,
- child: const SizedBox(),
+ if (dialogeDeletionConfirmed) {
+ if (!context.mounted) return true;
+ ScaffoldMessenger.of(context).removeCurrentSnackBar();
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ duration: const Duration(seconds: 5),
+ content: Text(AppLocalizations.of(context)!.deletionConfirmed),
+ action: SnackBarAction(
+ label: AppLocalizations.of(context)!.btnUndo,
+ onPressed: () async {
+ model.add(BloodPressureRecord(
+ data[index].creationTime,
+ data[index].systolic,
+ data[index].diastolic,
+ data[index].pulse,
+ data[index].notes));
+ },
),
- Expanded(
- flex: _tableElementsSizes[0],
- child: Text(formatter.format(data[index].creationTime))),
- Expanded(
- flex: _tableElementsSizes[1],
- child: Text((data[index].systolic ?? '').toString())),
- Expanded(
- flex: _tableElementsSizes[2],
- child: Text((data[index].diastolic ?? '').toString())),
- Expanded(
- flex: _tableElementsSizes[3],
- child: Text((data[index].pulse ?? '').toString())),
- Expanded(
- flex: _tableElementsSizes[4],
- child: Text(data[index].notes ?? '')),
- Expanded(
- flex: _sideFlex,
- child: const SizedBox(),
- ),
- ]),
+ ));
+ }
+ return dialogeDeletionConfirmed;
+ }
+ },
+ onDismissed: (direction) {},
+ background: Container(
+ width: 10,
+ decoration:
+ BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(5)),
+ child: const Align(alignment: Alignment(-0.95, 0), child: Icon(Icons.edit)),
+ ),
+ secondaryBackground: Container(
+ width: 10,
+ decoration:
+ BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(5)),
+ child: const Align(alignment: Alignment(0.95, 0), child: Icon(Icons.delete)),
+ ),
+ child: Container(
+ constraints: const BoxConstraints(minHeight: 40),
+ child: Row(children: [
+ Expanded(
+ flex: _sideFlex,
+ child: const SizedBox(),
+ ),
+ Expanded(
+ flex: _tableElementsSizes[0],
+ child: Text(formatter.format(data[index].creationTime))),
+ Expanded(
+ flex: _tableElementsSizes[1],
+ child: Text((data[index].systolic ?? '').toString())),
+ Expanded(
+ flex: _tableElementsSizes[2],
+ child: Text((data[index].diastolic ?? '').toString())),
+ Expanded(
+ flex: _tableElementsSizes[3],
+ child: Text((data[index].pulse ?? '').toString())),
+ Expanded(
+ flex: _tableElementsSizes[4],
+ child: Text(data[index].notes ?? '')),
+ Expanded(
+ flex: _sideFlex,
+ child: const SizedBox(),
),
- ),
- const Divider(
- thickness: 1,
- height: 1,
- )
- ],
- );
- });
- } else {
- return Text(AppLocalizations.of(context)!.errNoData);
- }
- },
- );
- });
- }),
+ ]),
+ ),
+ ),
+ const Divider(
+ thickness: 1,
+ height: 1,
+ )
+ ],
+ );
+ });
+ } else {
+ return Text(AppLocalizations.of(context)!.errNoData);
+ }
+ }),
)
],
- );
+ ));
}
}
lib/components/measurement_graph.dart
@@ -1,10 +1,11 @@
import 'dart:math';
-import 'package:blood_pressure_app/components/consistent_future_builder.dart';
+import 'package:blood_pressure_app/components/blood_pressure_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/horizontal_graph_line.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:collection/collection.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
@@ -28,69 +29,66 @@ class _LineChartState extends State<_LineChart> {
height: widget.height,
child: Consumer<Settings>(
builder: (context, settings, child) {
- return Consumer<BloodPressureModel>(builder: (context, model, child) {
- var end = settings.displayDataEnd;
- return ConsistentFutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
- future: model.getInTimeRange(settings.displayDataStart, end),
- onData: (context, fetchedData) {
- List<BloodPressureRecord> data = fetchedData.toList();
- data.sort((a, b) => a.creationTime.compareTo(b.creationTime));
-
- // calculate lines for graph
- List<FlSpot> pulSpots = [], diaSpots = [], sysSpots = [];
- int maxValue = 0;
- int minValue = (settings.validateInputs ? 30 : 0);
- double? graphBegin;
- double? graphEnd;
- for (var e in data) {
- final x = e.creationTime.millisecondsSinceEpoch.toDouble();
- if (e.diastolic != null) {
- diaSpots.add(FlSpot(x, e.diastolic!.toDouble()));
- maxValue = max(maxValue, e.diastolic!);
- }
- if (e.systolic != null) {
- sysSpots.add(FlSpot(x, e.systolic!.toDouble()));
- maxValue = max(maxValue, e.systolic!);
- }
- if (e.pulse != null) {
- pulSpots.add(FlSpot(x, e.pulse!.toDouble()));
- maxValue = max(maxValue, e.pulse!);
- }
- graphBegin ??= x;
- graphEnd ??= x;
- if (x < graphBegin) graphBegin = x;
- if (x > graphEnd) graphEnd = x;
- }
-
- if (diaSpots.length < 2 && sysSpots.length < 2 && pulSpots.length < 2 || graphBegin == null || graphEnd == null) {
- return Text(AppLocalizations.of(context)!.errNotEnoughDataToGraph);
- }
-
-
- return TweenAnimationBuilder<double>(
- duration: const Duration(milliseconds: 200), // interacts with LineChart duration property
- tween: Tween<double>(begin: 0, end: settings.graphLineThickness),
- builder: (context, animatedThickness, child) {
- return LineChart(
- duration: const Duration(milliseconds: 200),
- LineChartData(
- minY: minValue.toDouble(),
- maxY: maxValue + 5,
- clipData: const FlClipData.all(),
- titlesData: _buildFlTitlesData(settings,
- DateTimeRange(start: data.first.creationTime, end: data.last.creationTime)),
- lineTouchData: const LineTouchData(
- touchTooltipData: LineTouchTooltipData(tooltipMargin: -200, tooltipRoundedRadius: 20)
- ),
- lineBarsData: buildBars(animatedThickness, settings, sysSpots, diaSpots, pulSpots,
- maxValue, minValue, graphBegin, graphEnd, fetchedData)
+ return BloodPressureBuilder(
+ rangeType: IntervallStoreManagerLocation.mainPage,
+ onData: (BuildContext context, UnmodifiableListView<BloodPressureRecord> records) {
+ List<BloodPressureRecord> data = records.toList();
+ data.sort((a, b) => a.creationTime.compareTo(b.creationTime));
+
+ // calculate lines for graph
+ List<FlSpot> pulSpots = [], diaSpots = [], sysSpots = [];
+ int maxValue = 0;
+ int minValue = (settings.validateInputs ? 30 : 0);
+ double? graphBegin;
+ double? graphEnd;
+ for (var e in data) {
+ final x = e.creationTime.millisecondsSinceEpoch.toDouble();
+ if (e.diastolic != null) {
+ diaSpots.add(FlSpot(x, e.diastolic!.toDouble()));
+ maxValue = max(maxValue, e.diastolic!);
+ }
+ if (e.systolic != null) {
+ sysSpots.add(FlSpot(x, e.systolic!.toDouble()));
+ maxValue = max(maxValue, e.systolic!);
+ }
+ if (e.pulse != null) {
+ pulSpots.add(FlSpot(x, e.pulse!.toDouble()));
+ maxValue = max(maxValue, e.pulse!);
+ }
+ graphBegin ??= x;
+ graphEnd ??= x;
+ if (x < graphBegin) graphBegin = x;
+ if (x > graphEnd) graphEnd = x;
+ }
+
+ if (diaSpots.length < 2 && sysSpots.length < 2 && pulSpots.length < 2 || graphBegin == null || graphEnd == null) {
+ return Text(AppLocalizations.of(context)!.errNotEnoughDataToGraph);
+ }
+
+
+ return TweenAnimationBuilder<double>(
+ duration: const Duration(milliseconds: 200), // interacts with LineChart duration property
+ tween: Tween<double>(begin: 0, end: settings.graphLineThickness),
+ builder: (context, animatedThickness, child) {
+ return LineChart(
+ duration: const Duration(milliseconds: 200),
+ LineChartData(
+ minY: minValue.toDouble(),
+ maxY: maxValue + 5,
+ clipData: const FlClipData.all(),
+ titlesData: _buildFlTitlesData(settings,
+ DateTimeRange(start: data.first.creationTime, end: data.last.creationTime)),
+ lineTouchData: const LineTouchData(
+ touchTooltipData: LineTouchTooltipData(tooltipMargin: -200, tooltipRoundedRadius: 20)
),
- );
- },
+ lineBarsData: buildBars(animatedThickness, settings, sysSpots, diaSpots, pulSpots,
+ maxValue, minValue, graphBegin, graphEnd, records)
+ ),
);
- }
- );
- });
+ },
+ );
+ },
+ );
},
)
);
@@ -238,7 +236,7 @@ class MeasurementGraph extends StatelessWidget {
child: Column(
children: [
_LineChart(height: height - 100),
- const IntervalPicker()
+ const IntervalPicker(type: IntervallStoreManagerLocation.mainPage,)
],
),
),
lib/model/storage/common_settings_intervaces.dart
@@ -0,0 +1,9 @@
+/// This file hosts interfaces with attributes that different settings classes provide.
+
+
+abstract class CustomFieldsSettings {
+ bool get exportCustomFields;
+ set exportCustomFields(bool value);
+ List<String> get customFields;
+ set customFields(List<String> value);
+}
\ No newline at end of file
lib/model/storage/export_csv_settings_store.dart
@@ -1,10 +1,11 @@
import 'dart:convert';
import 'package:blood_pressure_app/model/export_options.dart';
+import 'package:blood_pressure_app/model/storage/common_settings_intervaces.dart';
import 'package:blood_pressure_app/model/storage/convert_util.dart';
import 'package:flutter/material.dart';
-class CsvExportSettings extends ChangeNotifier {
+class CsvExportSettings extends ChangeNotifier implements CustomFieldsSettings {
CsvExportSettings({
String? fieldDelimiter,
String? textDelimiter,
lib/model/storage/export_pdf_settings_store.dart
@@ -1,10 +1,11 @@
import 'dart:convert';
import 'package:blood_pressure_app/model/export_options.dart';
+import 'package:blood_pressure_app/model/storage/common_settings_intervaces.dart';
import 'package:blood_pressure_app/model/storage/convert_util.dart';
import 'package:flutter/material.dart';
-class PdfExportSettings extends ChangeNotifier {
+class PdfExportSettings extends ChangeNotifier implements CustomFieldsSettings {
PdfExportSettings({
bool? exportTitle,
bool? exportStatistics,
lib/model/storage/intervall_store.dart
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:blood_pressure_app/model/storage/convert_util.dart';
+import 'package:blood_pressure_app/model/storage/db/config_dao.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -228,4 +229,42 @@ enum TimeStep {
return TimeStep.last7Days;
}
}
+}
+
+/// Class that stores the interval objects that are needed in the app and provides named access to them.
+class IntervallStoreManager extends ChangeNotifier {
+ IntervallStoreManager(this.mainPage, this.exportPage, this.statsPage) {
+ mainPage.addListener(notifyListeners);
+ exportPage.addListener(notifyListeners);
+ statsPage.addListener(notifyListeners);
+ }
+
+ static Future<IntervallStoreManager> load(ConfigDao configDao, int profileID) async =>
+ IntervallStoreManager(
+ await configDao.loadIntervallStorage(profileID, 0),
+ await configDao.loadIntervallStorage(profileID, 1),
+ await configDao.loadIntervallStorage(profileID, 2),
+ );
+
+ IntervallStorage get(IntervallStoreManagerLocation type) {
+ switch (type) {
+ case IntervallStoreManagerLocation.mainPage:
+ return mainPage;
+ case IntervallStoreManagerLocation.exportPage:
+ return exportPage;
+ case IntervallStoreManagerLocation.statsPage:
+ return statsPage;
+ }
+ }
+
+ IntervallStorage mainPage;
+ IntervallStorage exportPage;
+ IntervallStorage statsPage;
+}
+
+/// enum of all locations supported by IntervallStoreManager
+enum IntervallStoreManagerLocation {
+ mainPage,
+ exportPage,
+ statsPage,
}
\ No newline at end of file
lib/model/storage/settings_store.dart
@@ -1,5 +1,6 @@
import 'dart:convert';
+import 'package:blood_pressure_app/model/horizontal_graph_line.dart';
import 'package:blood_pressure_app/model/storage/convert_util.dart';
import 'package:flutter/material.dart';
@@ -26,6 +27,7 @@ class Settings extends ChangeNotifier {
MaterialColor? sysColor,
MaterialColor? diaColor,
MaterialColor? pulColor,
+ List<HorizontalGraphLine>? horizontalGraphLines,
String? dateFormatString,
double? graphLineThickness,
int? animationSpeed,
@@ -59,6 +61,7 @@ class Settings extends ChangeNotifier {
if (drawRegressionLines != null) _drawRegressionLines = drawRegressionLines;
if (startWithAddMeasurementPage != null) _startWithAddMeasurementPage = startWithAddMeasurementPage;
if (useLegacyList != null) _useLegacyList = useLegacyList;
+ if (horizontalGraphLines != null) _horizontalGraphLines = horizontalGraphLines;
_language = language; // No check here, as null is the default as well. In general values should not be null
}
@@ -81,7 +84,9 @@ class Settings extends ChangeNotifier {
drawRegressionLines: ConvertUtil.parseBool(map['drawRegressionLines']),
startWithAddMeasurementPage: ConvertUtil.parseBool(map['startWithAddMeasurementPage']),
useLegacyList: ConvertUtil.parseBool(map['useLegacyList']),
- language: ConvertUtil.parseLocale(map['language'])
+ language: ConvertUtil.parseLocale(map['language']),
+ horizontalGraphLines: ConvertUtil.parseList<String>(map['horizontalGraphLines'])?.map((e) =>
+ HorizontalGraphLine.fromJson(jsonDecode(e))).toList(),
);
factory Settings.fromJson(String json) {
@@ -112,6 +117,7 @@ class Settings extends ChangeNotifier {
'startWithAddMeasurementPage': startWithAddMeasurementPage,
'useLegacyList': useLegacyList,
'language': ConvertUtil.serializeLocale(language),
+ 'horizontalGraphLines': horizontalGraphLines.map((e) => jsonEncode(e)).toList()
};
String toJson() => jsonEncode(toMap());
@@ -152,6 +158,13 @@ class Settings extends ChangeNotifier {
notifyListeners();
}
+ List<HorizontalGraphLine> _horizontalGraphLines = [];
+ List<HorizontalGraphLine> get horizontalGraphLines => _horizontalGraphLines;
+ set horizontalGraphLines(List<HorizontalGraphLine> value) {
+ _horizontalGraphLines = value;
+ notifyListeners();
+ }
+
String _dateFormatString = 'yyyy-MM-dd HH:mm';
String get dateFormatString => _dateFormatString;
set dateFormatString(String value) {
lib/model/export_import.dart
@@ -5,7 +5,10 @@ import 'dart:typed_data';
import 'package:blood_pressure_app/model/blood_pressure_analyzer.dart';
import 'package:blood_pressure_app/model/export_options.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:csv/csv.dart';
import 'package:file_picker/file_picker.dart';
import 'package:file_saver/file_saver.dart';
@@ -16,6 +19,7 @@ import 'package:jsaver/jSaver.dart';
import 'package:path/path.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
+import 'package:provider/provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:sqflite/sqflite.dart';
@@ -28,14 +32,18 @@ extension PdfCompatability on Color {
// TODO: more testing
class ExportFileCreator {
final Settings settings;
+ final ExportSettings exportSettings;
+ final CsvExportSettings csvExportSettings;
+ final PdfExportSettings pdfExportSettings;
final AppLocalizations localizations;
final ThemeData theme;
final ExportConfigurationModel exportColumnsConfig;
- ExportFileCreator(this.settings, this.localizations, this.theme, this.exportColumnsConfig);
+ ExportFileCreator(this.settings, this.exportSettings, this.csvExportSettings, this.pdfExportSettings,
+ this.localizations, this.theme, this.exportColumnsConfig,);
- Future<Uint8List> createFile(List<BloodPressureRecord> records) async {
- switch (settings.exportFormat) {
+ Future<Uint8List> createFile(Iterable<BloodPressureRecord> records) async {
+ switch (exportSettings.exportFormat) {
case ExportFormat.csv:
return createCSVFile(records);
case ExportFormat.pdf:
@@ -46,7 +54,7 @@ class ExportFileCreator {
}
Future<List<BloodPressureRecord>?> parseFile(String filePath, Uint8List data) async {
- switch(settings.exportFormat) {
+ switch(exportSettings.exportFormat) {
case ExportFormat.csv:
try {
return parseCSVFile(data);
@@ -60,9 +68,11 @@ class ExportFileCreator {
}
}
- Uint8List createCSVFile(List<BloodPressureRecord> records) {
- final items = exportColumnsConfig.createTable(records, ExportFormat.csv, createHeadline: settings.exportCsvHeadline);
- final converter = ListToCsvConverter(fieldDelimiter: settings.csvFieldDelimiter, textDelimiter: settings.csvTextDelimiter);
+ Uint8List createCSVFile(Iterable<BloodPressureRecord> records) {
+ final columns = exportColumnsConfig.getActiveExportColumns(ExportFormat.csv, csvExportSettings);
+ final items = exportColumnsConfig.createTable(records, columns, createHeadline: csvExportSettings.exportHeadline);
+ final converter = ListToCsvConverter(fieldDelimiter: csvExportSettings.fieldDelimiter,
+ textDelimiter: csvExportSettings.textDelimiter);
final csvData = converter.convert(items);
return Uint8List.fromList(utf8.encode(csvData));
}
@@ -71,10 +81,10 @@ class ExportFileCreator {
List<BloodPressureRecord> records = [];
String fileContents = utf8.decode(data.toList());
- var converter = CsvToListConverter(fieldDelimiter: settings.csvFieldDelimiter, textDelimiter: settings.csvTextDelimiter);
+ var converter = CsvToListConverter(fieldDelimiter: csvExportSettings.fieldDelimiter, textDelimiter: csvExportSettings.textDelimiter);
var csvLines = converter.convert(fileContents);
if (csvLines.length <= 1) { // legacy files
- converter = CsvToListConverter(fieldDelimiter: settings.csvFieldDelimiter, textDelimiter: settings.csvTextDelimiter, eol: '\n');
+ converter = CsvToListConverter(fieldDelimiter: csvExportSettings.fieldDelimiter, textDelimiter: csvExportSettings.textDelimiter, eol: '\n');
csvLines = converter.convert(fileContents);
}
@@ -136,7 +146,7 @@ class ExportFileCreator {
return records;
}
- Future<Uint8List> createPdfFile(List<BloodPressureRecord> data) async {
+ Future<Uint8List> createPdfFile(Iterable<BloodPressureRecord> data) async {
final analyzer = BloodPressureAnalyser(data.toList());
final dateFormatter = DateFormat(settings.dateFormatString);
@@ -146,11 +156,11 @@ class ExportFileCreator {
pageFormat: PdfPageFormat.a4,
build: (pw.Context context) {
return [
- if (settings.exportPdfExportTitle)
+ if (pdfExportSettings.exportTitle)
_buildPdfTitle(dateFormatter, analyzer),
- if (settings.exportPdfExportStatistics)
+ if (pdfExportSettings.exportStatistics)
_buildPdfStatistics(analyzer),
- if (settings.exportPdfExportData)
+ if (pdfExportSettings.exportData)
_buildPdfTable(data, dateFormatter),
];
}));
@@ -182,8 +192,9 @@ class ExportFileCreator {
);
}
- pw.Widget _buildPdfTable(List<BloodPressureRecord> data, DateFormat dateFormatter) {
- final tableData = exportColumnsConfig.createTable(data, ExportFormat.pdf, createHeadline: true);
+ pw.Widget _buildPdfTable(Iterable<BloodPressureRecord> data, DateFormat dateFormatter) {
+ final columns = exportColumnsConfig.getActiveExportColumns(ExportFormat.pdf, pdfExportSettings);
+ final tableData = exportColumnsConfig.createTable(data, columns, createHeadline: true);
return pw.TableHelper.fromTextArray(
border: null,
@@ -191,16 +202,16 @@ class ExportFileCreator {
headerDecoration: const pw.BoxDecoration(
border: pw.Border(bottom: pw.BorderSide(color: PdfColors.black))
),
- headerHeight: settings.exportPdfHeaderHeight,
- cellHeight: settings.exportPdfCellHeight,
+ headerHeight: pdfExportSettings.headerHeight,
+ cellHeight: pdfExportSettings.cellHeight,
cellAlignments: { for (var v in List.generate(tableData.first.length, (idx)=>idx)) v : pw.Alignment.centerLeft },
headerStyle: pw.TextStyle(
color: PdfColors.black,
- fontSize: settings.exportPdfHeaderFontSize,
+ fontSize: pdfExportSettings.headerFontSize,
fontWeight: pw.FontWeight.bold,
),
cellStyle: pw.TextStyle(
- fontSize: settings.exportPdfCellFontSize,
+ fontSize: pdfExportSettings.cellFontSize,
),
headerCellDecoration: pw.BoxDecoration(
border: pw.Border(
@@ -239,21 +250,39 @@ class ExportFileCreator {
}
class Exporter {
+ final Iterable<BloodPressureRecord> data;
final Settings settings;
+ final ExportSettings exportSettings;
+ final CsvExportSettings csvExportSettings;
+ final PdfExportSettings pdfExportSettings;
final BloodPressureModel model;
final ScaffoldMessengerState messenger;
final AppLocalizations localizations;
final ThemeData theme;
final ExportConfigurationModel exportColumnsConfig;
- Exporter(this.settings, this.model, this.messenger, this.localizations, this.theme, this.exportColumnsConfig);
+ Exporter(this.data, this.model, this.messenger, this.localizations, this.theme, this.exportColumnsConfig,
+ this.settings, this.exportSettings, this.csvExportSettings, this.pdfExportSettings);
+ factory Exporter.load(BuildContext context, Iterable<BloodPressureRecord> data, ExportConfigurationModel exportColumnsConfig) {
+
+ final settings = Provider.of<Settings>(context);
+ final exportSettings = Provider.of<ExportSettings>(context);
+ final csvExportSettings = Provider.of<CsvExportSettings>(context);
+ final pdfExportSettings = Provider.of<PdfExportSettings>(context);
+ final model = Provider.of<BloodPressureModel>(context);
+ final localizations = AppLocalizations.of(context)!;
+ final theme = Theme.of(context);
+ final messenger = ScaffoldMessenger.of(context);
+ return Exporter(data, model, messenger, localizations, theme, exportColumnsConfig, settings, exportSettings,
+ csvExportSettings, pdfExportSettings);
+ }
Future<void> export() async {
- final entries = await model.getInTimeRange(settings.displayDataStart, settings.displayDataEnd);
- var fileContents = await ExportFileCreator(settings, localizations, theme, exportColumnsConfig).createFile(entries);
+ var fileContents = await ExportFileCreator(settings, exportSettings, csvExportSettings, pdfExportSettings,
+ localizations, theme, exportColumnsConfig).createFile(data);
String filename = 'blood_press_${DateTime.now().toIso8601String()}';
String ext;
- switch(settings.exportFormat) {
+ switch(exportSettings.exportFormat) {
case ExportFormat.csv:
ext = 'csv';
break;
@@ -269,12 +298,12 @@ class Exporter {
if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
messenger.showSnackBar(SnackBar(content: Text(localizations.success(path))));
} else if (Platform.isAndroid || Platform.isIOS) {
- if (settings.defaultExportDir.isNotEmpty) {
+ if (exportSettings.defaultExportDir.isNotEmpty) {
JSaver.instance.save(
fromPath: path,
androidPathOptions: AndroidPathOptions(toDefaultDirectory: true)
);
- messenger.showSnackBar(SnackBar(content: Text(localizations.success(settings.defaultExportDir))));
+ messenger.showSnackBar(SnackBar(content: Text(localizations.success(exportSettings.defaultExportDir))));
} else {
Share.shareXFiles([
XFile(
@@ -290,7 +319,7 @@ class Exporter {
Future<void> import() async {
- if (!([ExportFormat.csv, ExportFormat.db].contains(settings.exportFormat))) {
+ if (!([ExportFormat.csv, ExportFormat.db].contains(exportSettings.exportFormat))) {
messenger.showSnackBar(SnackBar(content: Text(localizations.errWrongImportFormat)));
return;
}
@@ -311,7 +340,8 @@ class Exporter {
var path = result.files.single.path;
assert(path != null); // null state directly linked to binary content
- final fileContentsFuture = ExportFileCreator(settings, localizations, theme, exportColumnsConfig).parseFile(path! ,binaryContent);
+ final fileContentsFuture = ExportFileCreator(settings, exportSettings, csvExportSettings, pdfExportSettings,
+ localizations, theme, exportColumnsConfig).parseFile(path! ,binaryContent);
Object? fileContentsError;
fileContentsFuture.onError((error, stackTrace) {
fileContentsError = error;
@@ -330,10 +360,4 @@ class Exporter {
model.add(e);
}
}
-}
-
-enum ExportFormat {
- csv,
- pdf,
- db
}
\ No newline at end of file
lib/model/export_options.dart
@@ -2,9 +2,9 @@ import 'dart:collection';
import 'package:blood_pressure_app/main.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:blood_pressure_app/model/storage/common_settings_intervaces.dart';
import 'package:blood_pressure_app/model/storage/db/config_dao.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:function_tree/function_tree.dart';
@@ -20,7 +20,6 @@ class ExportConfigurationModel {
// 2 sources.
static ExportConfigurationModel? _instance;
- final Settings settings;
final AppLocalizations localizations;
final ConfigDao _configDao; // TODO: remove after #181 is complete
@@ -34,30 +33,30 @@ class ExportConfigurationModel {
('"My Heart" export', ['DATUM', 'SYSTOLE', 'DIASTOLE', 'PULS', 'Beschreibung', 'Tags', 'Gewicht', 'Sauerstoffsättigung']),
];
- ExportConfigurationModel._create(this.settings, this.localizations, this._configDao);
+ ExportConfigurationModel._create(this.localizations, this._configDao);
Future<void> _asyncInit() async {
-
_availableFormats.addAll(getDefaultFormates());
_availableFormats.addAll(await _configDao.loadExportColumns());
}
- static Future<ExportConfigurationModel> get(Settings settings, AppLocalizations localizations, {String? dbPath, bool isFullPath = false}) async {
+ static Future<ExportConfigurationModel> get(AppLocalizations localizations) async {
if (_instance == null) {
- _instance = ExportConfigurationModel._create(settings, localizations, globalConfigDao);
+ _instance = ExportConfigurationModel._create(localizations, globalConfigDao);
await _instance!._asyncInit();
}
return _instance!;
}
- List<ExportColumn> getActiveExportColumns(ExportFormat format) {
+ /// Determines which export columns should be used.
+ ///
+ /// The [fieldSettings] parameter describes the settings of the current export format and should be set accordingly.
+ List<ExportColumn> getActiveExportColumns(ExportFormat format, CustomFieldsSettings fieldsSettings) {
switch (format) {
case ExportFormat.csv:
- return availableFormats.where((e) =>
- ((settings.exportCustomEntriesCsv) ? settings.exportItemsCsv : ExportFields.defaultCsv)
- .contains(e.internalName)).toList();
+ final fields = (fieldsSettings.exportCustomFields) ? fieldsSettings.customFields : ExportFields.defaultCsv;
+ return availableFormats.where((e) => fields.contains(e.internalName)).toList();
case ExportFormat.pdf:
- return availableFormats.where((e) =>
- ((settings.exportCustomEntriesPdf) ? settings.exportItemsPdf : ExportFields.defaultPdf)
- .contains(e.internalName)).toList();
+ final fields = (fieldsSettings.exportCustomFields) ? fieldsSettings.customFields : ExportFields.defaultPdf;
+ return availableFormats.where((e) => fields.contains(e.internalName)).toList();
case ExportFormat.db:
// Export formats don't work on this one
return [];
@@ -66,7 +65,7 @@ class ExportConfigurationModel {
List<ExportColumn> getDefaultFormates() => [
ExportColumn(internalName: 'timestampUnixMs', columnTitle: localizations.unixTimestamp, formatPattern: r'$TIMESTAMP', editable: false),
- ExportColumn(internalName: 'formattedTimestamp', columnTitle: localizations.time, formatPattern: '\$FORMAT{\$TIMESTAMP,${settings.dateFormatString}}', editable: false),
+ ExportColumn(internalName: 'formattedTimestamp', columnTitle: localizations.time, formatPattern: '\$FORMAT{\$TIMESTAMP,yyyy-MM-dd HH:mm:ss}', editable: false),
ExportColumn(internalName: 'systolic', columnTitle: localizations.sysLong, formatPattern: r'$SYS', editable: false),
ExportColumn(internalName: 'diastolic', columnTitle: localizations.diaLong, formatPattern: r'$DIA', editable: false),
ExportColumn(internalName: 'pulse', columnTitle: localizations.pulLong, formatPattern: r'$PUL', editable: false),
@@ -100,14 +99,17 @@ class ExportConfigurationModel {
UnmodifiableMapView<String, ExportColumn> get availableFormatsMap =>
UnmodifiableMapView(Map.fromIterable(_availableFormats, key: (e) => e.internalName));
- List<List<String>> createTable(List<BloodPressureRecord> data, ExportFormat format, {bool createHeadline = true,}) {
- final exportItems = getActiveExportColumns(format);
+
+ List<List<String>> createTable(Iterable<BloodPressureRecord> data, List<ExportColumn> activeExportColumns,
+ {bool createHeadline = true,}) {
List<List<String>> items = [];
if (createHeadline) {
- items.add(exportItems.map((e) => e.internalName).toList());
+ items.add(activeExportColumns.map((e) => e.internalName).toList());
}
- final dataRows = data.map((record) => exportItems.map((attribute) => attribute.formatRecord(record)).toList());
+ final dataRows = data.map((record) =>
+ activeExportColumns.map((attribute) =>
+ attribute.formatRecord(record)).toList());
items.addAll(dataRows);
return items;
}
lib/model/settings_store.dart
@@ -1,639 +1,10 @@
-import 'dart:convert';
-
-import 'package:blood_pressure_app/model/blood_pressure.dart';
-import 'package:blood_pressure_app/model/export_import.dart';
-import 'package:blood_pressure_app/model/export_options.dart';
-import 'package:blood_pressure_app/model/horizontal_graph_line.dart';
-import 'package:file_saver/file_saver.dart' show MimeType;
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'package:package_info_plus/package_info_plus.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-
-class Settings extends ChangeNotifier {
- late final SharedPreferences _prefs;
- final PackageInfo _packageInfo;
-
- DateTime? _displayDataStart;
- DateTime? _displayDataEnd;
-
- Settings._create(this._packageInfo);
- // factory method, to allow for async constructor
- static Future<Settings> create([PackageInfo? packageInfo]) async {
- final component = Settings._create(packageInfo ?? (await PackageInfo.fromPlatform()));
- component._prefs = await SharedPreferences.getInstance();
- await component._update();
- return component;
- }
-
- Future<void> _update() async {
- final keys = _prefs.getKeys();
- List<Future> toAwait = [];
-
- // delete old keys
- if (keys.contains('age')) {
- final lastAge = _prefs.getInt('age') ?? 30;
- sysWarn = BloodPressureWarnValues.getUpperSysWarnValue(lastAge);
- diaWarn = BloodPressureWarnValues.getUpperDiaWarnValue(lastAge);
- toAwait.add(_prefs.remove('age'));
- }
- 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'));
- }
- if (keys.contains('exportAddableItems')) {
- toAwait.add(_prefs.remove('exportAddableItems'));
- }
- if (keys.contains('exportCustomEntries')) {
- await _prefs.setBool('exportCustomEntriesCsv', _prefs.getBool('exportCustomEntries') ?? false);
- toAwait.add(_prefs.remove('exportCustomEntries'));
- }
- if (keys.contains('exportItems')) {
- await _prefs.setStringList('exportItemsCsv', _prefs.getStringList('exportItems') ?? ExportFields.defaultCsv);
- toAwait.add(_prefs.remove('exportItems'));
- }
- if (keys.contains('iconSize')) {
- toAwait.add(_prefs.remove('iconSize'));
- }
- if (keys.contains('titlesCount')) {
- toAwait.add(_prefs.remove('titlesCount'));
- }
-
- // reset variables for new version. Necessary for reusing variable names in new version and avoid having unexpected
- // breaking values in the preferences
- switch (_prefs.getInt('lastAppVersion')) {
- case null:
- toAwait.add(_prefs.remove('exportCsvHeadline'));
- toAwait.add(_prefs.remove('exportCustomEntries'));
- toAwait.add(_prefs.remove('exportItems'));
- toAwait.add(_prefs.remove('exportMimeType'));
- }
-
- for (var e in toAwait) {
- await e;
- }
- await _prefs.setInt('lastAppVersion', int.parse(_packageInfo.buildNumber));
- return;
- }
-
- 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;
- case 7:
- return TimeStep.custom;
- }
- assert(false);
- return TimeStep.day;
- }
-
- set graphStepSize(TimeStep newStepSize) {
- _prefs.setInt('graphStepSize', ((){
- switch (newStepSize) {
- case TimeStep.day:
- return 0;
- case TimeStep.month:
- return 1;
- case TimeStep.year:
- return 2;
- case TimeStep.lifetime:
- return 3;
- case TimeStep.week:
- return 4;
- case TimeStep.last7Days:
- return 5;
- case TimeStep.last30Days:
- return 6;
- case TimeStep.custom:
- return 7;
- }
- })());
-
-
- notifyListeners();
- }
-
- void changeStepSize(TimeStep value) {
- graphStepSize = value;
- final newInterval = getMostRecentDisplayIntervall();
- displayDataStart = newInterval[0];
- displayDataEnd = newInterval[1];
- }
-
- void setToMostRecentIntervall() {
- changeStepSize(graphStepSize);
- }
-
- void moveDisplayDataByStep(int directionalStep) {
- final oldStart = displayDataStart;
- final oldEnd = displayDataEnd;
- switch (graphStepSize) {
- case TimeStep.day:
- displayDataStart = oldStart.copyWith(day: oldStart.day + directionalStep);
- 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;
- case TimeStep.month:
- displayDataStart = oldStart.copyWith(month: oldStart.month + directionalStep);
- displayDataEnd = oldEnd.copyWith(month: oldEnd.month + directionalStep);
- break;
- case TimeStep.year:
- displayDataStart = oldStart.copyWith(year: oldStart.year + directionalStep);
- displayDataEnd = oldEnd.copyWith(year: oldEnd.year + directionalStep);
- break;
- case TimeStep.lifetime:
- displayDataStart = DateTime.fromMillisecondsSinceEpoch(1);
- displayDataEnd = DateTime.now().copyWith(hour: 23, minute: 59, second: 59);
- break;
- case TimeStep.last30Days:
- displayDataStart = oldStart.copyWith(day: oldStart.day + directionalStep * 30);
- displayDataEnd = oldEnd.copyWith(day: oldEnd.day + directionalStep * 30);
- break;
- case TimeStep.custom:
- final step = oldEnd.difference(oldStart) * directionalStep;
- displayDataStart = oldStart.add(step);
- displayDataEnd = oldEnd.add(step);
- break;
- }
- }
-
- List<DateTime> getMostRecentDisplayIntervall() {
- final now = DateTime.now();
- switch (graphStepSize) {
- case TimeStep.day:
- final start = DateTime(now.year, now.month, now.day);
- return [start, start.copyWith(day: now.day + 1)];
- case TimeStep.week:
- final start = DateTime(now.year, now.month, now.day - (now.weekday - 1)); // monday
- return [start, start.copyWith(day: start.day + DateTime.sunday)]; // end of sunday
- case TimeStep.month:
- final start = DateTime(now.year, now.month);
- return [start, start.copyWith(month: now.month + 1)];
- case TimeStep.year:
- final start = DateTime(now.year);
- return [start, start.copyWith(year: now.year + 1)];
- case TimeStep.lifetime:
- final start = DateTime.fromMillisecondsSinceEpoch(1);
- final endOfToday = now.copyWith(hour: 23, minute: 59, second: 59);
- return [start, endOfToday];
- case TimeStep.last7Days:
- final start = now.copyWith(day: now.day - 7);
- final endOfToday = now.copyWith(hour: 23, minute: 59, second: 59);
- return [start, endOfToday];
- case TimeStep.last30Days:
- final start = now.copyWith(day: now.day - 30);
- final endOfToday = now.copyWith(hour: 23, minute: 59, second: 59);
- return [start, endOfToday];
- case TimeStep.custom:
- // fallback, TimeStep will be reset by getter
- return [DateTime.fromMillisecondsSinceEpoch(-1), DateTime.fromMillisecondsSinceEpoch(-1)];
- }
- }
-
- DateTime get displayDataStart {
- final s = _displayDataStart ?? getMostRecentDisplayIntervall()[0];
- if(s.millisecondsSinceEpoch < 1) {
- changeStepSize(TimeStep.last7Days);
- }
- return s;
- }
-
- set displayDataStart(DateTime newGraphStart) {
- _displayDataStart = newGraphStart;
- notifyListeners();
- }
-
- DateTime get displayDataEnd {
- final s = _displayDataEnd ?? getMostRecentDisplayIntervall()[1];
- if(s.millisecondsSinceEpoch < 1) {
- changeStepSize(TimeStep.last7Days);
- }
- return s;
- }
-
- set displayDataEnd(DateTime newGraphEnd) {
- _displayDataEnd = newGraphEnd;
- notifyListeners();
- }
-
- bool get followSystemDarkMode {
- return _prefs.getBool('followSystemDarkMode') ?? true;
- }
- set followSystemDarkMode(bool newSetting) {
- _prefs.setBool('followSystemDarkMode', newSetting);
- notifyListeners();
- }
-
- bool get darkMode {
- return _prefs.getBool('darkMode') ?? true;
- }
-
- set darkMode(bool newSetting) {
- _prefs.setBool('darkMode', newSetting);
- notifyListeners();
- }
-
- MaterialColor get accentColor {
- return createMaterialColor(_prefs.getInt('accentColor') ?? 0xFF009688);
- }
-
- set accentColor(MaterialColor newColor) {
- _prefs.setInt('accentColor', newColor.value);
- notifyListeners();
- }
-
- MaterialColor get diaColor {
- return createMaterialColor(_prefs.getInt('diaColor') ?? 0xFF4CAF50);
- }
-
- set diaColor(MaterialColor newColor) {
- _prefs.setInt('diaColor', newColor.value);
- notifyListeners();
- }
-
- MaterialColor get sysColor {
- return createMaterialColor(_prefs.getInt('sysColor') ?? 0xFF009688);
- }
-
- set sysColor(MaterialColor newColor) {
- _prefs.setInt('sysColor', newColor.value);
- notifyListeners();
- }
-
- MaterialColor get pulColor {
- return createMaterialColor(_prefs.getInt('pulColor') ?? 0xFFF44336);
- }
-
- set pulColor(MaterialColor newColor) {
- _prefs.setInt('pulColor', newColor.value);
- notifyListeners();
- }
-
- bool get allowManualTimeInput {
- return _prefs.getBool('allowManualTimeInput') ?? true;
- }
-
- set allowManualTimeInput(bool newSetting) {
- _prefs.setBool('allowManualTimeInput', newSetting);
- notifyListeners();
- }
-
- String get dateFormatString {
- return _prefs.getString('dateFormatString') ?? 'yyyy-MM-dd HH:mm';
- }
-
- set dateFormatString(String newFormatString) {
- _prefs.setString('dateFormatString', newFormatString);
- notifyListeners();
- }
-
- int get sysWarn {
- return _prefs.getInt('sysWarn') ?? 120;
- }
-
- set sysWarn(int newWarn) {
- _prefs.setInt('sysWarn', newWarn);
- notifyListeners();
- }
-
- int get diaWarn {
- return _prefs.getInt('diaWarn') ?? 80;
- }
-
- set diaWarn(int newWarn) {
- _prefs.setInt('diaWarn', newWarn);
- notifyListeners();
- }
-
- bool get validateInputs {
- return _prefs.getBool('validateInputs') ?? true;
- }
-
- set validateInputs(bool validateInputs) {
- _prefs.setBool('validateInputs', validateInputs);
- notifyListeners();
- }
-
- bool get allowMissingValues {
- return _prefs.getBool('allowMissingValues') ?? false;
- }
-
- set allowMissingValues(bool allowMissingValues) {
- _prefs.setBool('allowMissingValues', allowMissingValues);
- notifyListeners();
- }
-
- double get graphLineThickness {
- return _prefs.getDouble('graphLineThickness') ?? 3;
- }
-
- set graphLineThickness(double newThickness) {
- _prefs.setDouble('graphLineThickness', newThickness);
- notifyListeners();
- }
+// TODO: remove file
- int get animationSpeed {
- return _prefs.getInt('animationSpeed') ?? 150;
- }
-
- set animationSpeed(int newSpeed) {
- _prefs.setInt('animationSpeed', newSpeed);
- notifyListeners();
- }
-
- bool get confirmDeletion {
- return _prefs.getBool('confirmDeletion') ?? true;
- }
-
- set confirmDeletion(bool confirmDeletion) {
- _prefs.setBool('confirmDeletion', confirmDeletion);
- notifyListeners();
- }
-
- ExportFormat get exportFormat {
- switch (_prefs.getInt('exportFormat') ?? 0) {
- case 0:
- return ExportFormat.csv;
- case 1:
- return ExportFormat.pdf;
- case 2:
- return ExportFormat.db;
- default:
- assert(false);
- return ExportFormat.csv;
- }
- }
-
- set exportFormat(ExportFormat format) {
- switch (format) {
- case ExportFormat.csv:
- _prefs.setInt('exportFormat', 0);
- break;
- case ExportFormat.pdf:
- _prefs.setInt('exportFormat', 1);
- break;
- case ExportFormat.db:
- _prefs.setInt('exportFormat', 2);
- break;
- default:
- assert(false);
- }
- notifyListeners();
- }
-
- String get csvFieldDelimiter {
- return _prefs.getString('csvFieldDelimiter') ?? ',';
- }
-
- set csvFieldDelimiter(String value) {
- _prefs.setString('csvFieldDelimiter', value);
- notifyListeners();
- }
-
- String get csvTextDelimiter {
- return _prefs.getString('csvTextDelimiter') ?? '"';
- }
-
- set csvTextDelimiter(String value) {
- _prefs.setString('csvTextDelimiter', value);
- notifyListeners();
- }
-
- MimeType get exportMimeType {
- switch (_prefs.getInt('exportMimeType') ?? 0) {
- case 0:
- return MimeType.csv;
- case 1:
- return MimeType.text;
- case 2:
- return MimeType.pdf;
- case 3:
- return MimeType.other;
- default:
- throw UnimplementedError();
- }
- }
- set exportMimeType(MimeType value) {
- switch (value) {
- case MimeType.csv:
- _prefs.setInt('exportMimeType', 0);
- break;
- case MimeType.text:
- _prefs.setInt('exportMimeType', 1);
- break;
- case MimeType.pdf:
- _prefs.setInt('exportMimeType', 2);
- break;
- case MimeType.other:
- _prefs.setInt('exportMimeType', 3);
- break;
- default:
- throw UnimplementedError();
- }
- notifyListeners();
- }
-
- bool get exportCustomEntriesCsv {
- return _prefs.getBool('exportCustomEntriesCsv') ?? false;
- }
-
- set exportCustomEntriesCsv(bool value) {
- _prefs.setBool('exportCustomEntriesCsv', value);
- notifyListeners();
- }
-
- List<String> get exportItemsCsv {
- return _prefs.getStringList('exportItemsCsv') ?? ExportFields.defaultCsv;
- }
-
- set exportItemsCsv(List<String> value) {
- _prefs.setStringList('exportItemsCsv', value);
- notifyListeners();
- }
-
- bool get exportCsvHeadline {
- return _prefs.getBool('exportCsvHeadline') ?? true;
- }
-
- set exportCsvHeadline(bool value) {
- _prefs.setBool('exportCsvHeadline', value);
- notifyListeners();
- }
-
- String get defaultExportDir {
- return _prefs.getString('defaultExportDir') ?? '';
- }
- set defaultExportDir (String value) {
- _prefs.setString('defaultExportDir', value);
- notifyListeners();
- }
-
- bool get exportAfterEveryEntry {
- return _prefs.getBool('exportAfterEveryEntry') ?? false;
- }
-
- set exportAfterEveryEntry(bool value) {
- _prefs.setBool('exportAfterEveryEntry', value);
- notifyListeners();
- }
-
- Locale? get language {
- final value = _prefs.getString('language');
- if (value?.isEmpty ?? true) return null;
- return Locale(value ?? 'en');
- }
-
- set language (Locale? value) {
- _prefs.setString('language', value?.languageCode ?? '');
- notifyListeners();
- }
-
- bool get drawRegressionLines {
- return _prefs.getBool('drawRegressionLines') ?? false;
- }
-
- set drawRegressionLines(bool value) {
- _prefs.setBool('drawRegressionLines', value);
- notifyListeners();
- }
-
- double get exportPdfHeaderHeight {
- return _prefs.getDouble('exportPdfHeaderHeight') ?? 20;
- }
-
- set exportPdfHeaderHeight(double value) {
- _prefs.setDouble('exportPdfHeaderHeight', value);
- notifyListeners();
- }
- double get exportPdfCellHeight {
- return _prefs.getDouble('exportPdfCellHeight') ?? 15;
- }
-
- set exportPdfCellHeight(double value) {
- _prefs.setDouble('exportPdfCellHeight', value);
- notifyListeners();
- }
-
- double get exportPdfHeaderFontSize {
- return _prefs.getDouble('exportPdfHeaderFontSize') ?? 10;
- }
-
- set exportPdfHeaderFontSize(double value) {
- _prefs.setDouble('exportPdfHeaderFontSize', value);
- notifyListeners();
- }
-
- double get exportPdfCellFontSize {
- return _prefs.getDouble('exportPdfCellFontSize') ?? 8;
- }
-
- set exportPdfCellFontSize(double value) {
- _prefs.setDouble('exportPdfCellFontSize', value);
- notifyListeners();
- }
-
- bool get exportPdfExportTitle {
- return _prefs.getBool('exportPdfExportTitle') ?? true;
- }
-
- set exportPdfExportTitle(bool value) {
- _prefs.setBool('exportPdfExportTitle', value);
- notifyListeners();
- }
-
- bool get exportPdfExportStatistics {
- return _prefs.getBool('exportPdfExportStatistics') ?? false;
- }
-
- set exportPdfExportStatistics(bool value) {
- _prefs.setBool('exportPdfExportStatistics', value);
- notifyListeners();
- }
-
- /// whether to add a section with all entries to pdf export
- bool get exportPdfExportData {
- return _prefs.getBool('exportPdfExportData') ?? true;
- }
-
- set exportPdfExportData(bool value) {
- _prefs.setBool('exportPdfExportData', value);
- notifyListeners();
- }
-
- bool get startWithAddMeasurementPage {
- return _prefs.getBool('startWithAddMeasurementPage') ?? false;
- }
-
- set startWithAddMeasurementPage(bool value) {
- _prefs.setBool('startWithAddMeasurementPage', value);
- notifyListeners();
- }
-
- bool get exportCustomEntriesPdf {
- return _prefs.getBool('exportCustomEntriesPdf') ?? false;
- }
-
- set exportCustomEntriesPdf(bool value) {
- _prefs.setBool('exportCustomEntriesPdf', value);
- notifyListeners();
- }
-
- List<String> get exportItemsPdf {
- return _prefs.getStringList('exportItemsPdf') ?? ExportFields.defaultPdf;
- }
-
- set exportItemsPdf(List<String> value) {
- _prefs.setStringList('exportItemsPdf', value);
- notifyListeners();
- }
-
- Iterable<HorizontalGraphLine> get horizontalGraphLines {
- final linesStr = _prefs.getStringList('horizontalGraphLines') ?? [];
- return linesStr.map((e) => HorizontalGraphLine.fromJson(jsonDecode(e)));
- }
-
- set horizontalGraphLines(Iterable<HorizontalGraphLine> value) {
- _prefs.setStringList('horizontalGraphLines', value.map((e) => jsonEncode(e)).toList());
- notifyListeners();
- }
-
- bool get useLegacyList {
- return _prefs.getBool('useLegacyList') ?? false;
- }
-
- set useLegacyList(bool value) {
- _prefs.setBool('useLegacyList', value);
- notifyListeners();
- }
-}
-enum TimeStep {
+enum _TimeStep {
day,
month,
year,
@@ -643,31 +14,31 @@ enum TimeStep {
last30Days,
custom;
- static const options = [TimeStep.day, TimeStep.week, TimeStep.month, TimeStep.year, TimeStep.lifetime, TimeStep.last7Days, TimeStep.last30Days, TimeStep.custom];
+ static const options = [_TimeStep.day, _TimeStep.week, _TimeStep.month, _TimeStep.year, _TimeStep.lifetime, _TimeStep.last7Days, _TimeStep.last30Days, _TimeStep.custom];
- static String getName(TimeStep opt, BuildContext context) {
+ static String getName(_TimeStep opt, BuildContext context) {
switch (opt) {
- case TimeStep.day:
+ case _TimeStep.day:
return AppLocalizations.of(context)!.day;
- case TimeStep.month:
+ case _TimeStep.month:
return AppLocalizations.of(context)!.month;
- case TimeStep.year:
+ case _TimeStep.year:
return AppLocalizations.of(context)!.year;
- case TimeStep.lifetime:
+ case _TimeStep.lifetime:
return AppLocalizations.of(context)!.lifetime;
- case TimeStep.week:
+ case _TimeStep.week:
return AppLocalizations.of(context)!.week;
- case TimeStep.last7Days:
+ case _TimeStep.last7Days:
return AppLocalizations.of(context)!.last7Days;
- case TimeStep.last30Days:
+ case _TimeStep.last30Days:
return AppLocalizations.of(context)!.last30Days;
- case TimeStep.custom:
+ case _TimeStep.custom:
return AppLocalizations.of(context)!.custom;
}
}
}
-MaterialColor createMaterialColor(int value) {
+MaterialColor createMaterialColor(int value) { // TODO: remove
final color = Color(value);
List strengths = <double>[.05];
Map<int, Color> swatch = {};
lib/screens/subsettings/enter_timeformat.dart
@@ -1,4 +1,4 @@
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:blood_pressure_app/screens/subsettings/time_formats_explainer.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
lib/screens/subsettings/export_column_data.dart
@@ -1,10 +1,8 @@
import 'package:blood_pressure_app/model/blood_pressure.dart';
import 'package:blood_pressure_app/model/export_options.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
import 'package:blood_pressure_app/screens/subsettings/export_field_format_documentation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'package:provider/provider.dart';
class EditExportColumnPage extends StatefulWidget {
final String? initialInternalName;
@@ -163,9 +161,8 @@ class _EditExportColumnPageState extends State<EditExportColumnPage> {
label: Text(localizations.btnSave),
onPressed: (widget.editable) ? (() async {
if (_formKey.currentState?.validate() ?? false) {
- final settings = Provider.of<Settings>(context, listen: false);
final navigator = Navigator.of(context);
- (await ExportConfigurationModel.get(settings, localizations)).addOrUpdate(ExportColumn(
+ (await ExportConfigurationModel.get(localizations)).addOrUpdate(ExportColumn(
internalName: _internalName!,
columnTitle: _displayName!,
formatPattern: _formatPattern!
lib/screens/subsettings/export_import_screen.dart
@@ -5,7 +5,12 @@ 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/export_options.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/common_settings_intervaces.dart';
+import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:jsaver/jSaver.dart';
@@ -22,7 +27,7 @@ class ExportImportScreen extends StatelessWidget {
title: Text(localizations.exportImport),
backgroundColor: Theme.of(context).primaryColor,
),
- body: Consumer<Settings>(builder: (context, settings, child) {
+ body: Consumer<ExportSettings>(builder: (context, settings, child) {
return SingleChildScrollView(
child: Column(
children: [
@@ -34,7 +39,7 @@ class ExportImportScreen extends StatelessWidget {
opacity: (settings.exportFormat == ExportFormat.db) ? 0.7 : 1,
child: IgnorePointer(
ignoring: (settings.exportFormat == ExportFormat.db),
- child: const IntervalPicker()),
+ child: const IntervalPicker(type: IntervallStoreManagerLocation.exportPage,)),
),
SettingsTile(
title: Text(localizations.exportDir),
@@ -69,106 +74,120 @@ class ExportImportScreen extends StatelessWidget {
}
},
),
- InputSettingsTile(
- title: Text(localizations.fieldDelimiter),
- inputWidth: 40,
- initialValue: settings.csvFieldDelimiter,
- disabled: !(settings.exportFormat == ExportFormat.csv),
- onEditingComplete: (value) {
- if (value != null) {
- settings.csvFieldDelimiter = value;
- }
- },
- ),
- InputSettingsTile(
- title: Text(localizations.textDelimiter),
- inputWidth: 40,
- initialValue: settings.csvTextDelimiter,
- disabled: !(settings.exportFormat == ExportFormat.csv),
- onEditingComplete: (value) {
- if (value != null) {
- settings.csvTextDelimiter = value;
- }
- },
- ),
- SwitchSettingsTile(
- title: Text(localizations.exportCsvHeadline),
- description: Text(localizations.exportCsvHeadlineDesc),
- initialValue: settings.exportCsvHeadline,
- disabled: settings.exportFormat != ExportFormat.csv,
- onToggle: (value) {
- settings.exportCsvHeadline = value;
- }),
- SwitchSettingsTile(
- title: Text(localizations.exportPdfExportTitle),
- initialValue: settings.exportPdfExportTitle,
- disabled: settings.exportFormat != ExportFormat.pdf,
- onToggle: (value) {
- settings.exportPdfExportTitle = value;
- }),
- SwitchSettingsTile(
- title: Text(localizations.exportPdfExportStatistics),
- initialValue: settings.exportPdfExportStatistics,
- disabled: settings.exportFormat != ExportFormat.pdf,
- onToggle: (value) {
- settings.exportPdfExportStatistics = value;
- }),
- SwitchSettingsTile(
- title: Text(localizations.exportPdfExportData),
- initialValue: settings.exportPdfExportData,
- disabled: settings.exportFormat != ExportFormat.pdf,
- onToggle: (value) {
- settings.exportPdfExportData = value;
- }),
- InputSettingsTile(
- initialValue: settings.exportPdfHeaderHeight.toString(),
- title: Text(localizations.exportPdfHeaderHeight),
- onEditingComplete: (value) {
- final pV = double.tryParse(value ?? '');
- if (pV != null) settings.exportPdfHeaderHeight = pV;
- },
- disabled: !(settings.exportFormat == ExportFormat.pdf &&
- settings.exportPdfExportData),
- keyboardType: TextInputType.number,
- inputWidth: 40,
- ),
- InputSettingsTile(
- initialValue: settings.exportPdfCellHeight.toString(),
- title: Text(localizations.exportPdfCellHeight),
- onEditingComplete: (value) {
- final pV = double.tryParse(value ?? '');
- if (pV != null) settings.exportPdfCellHeight = pV;
- },
- disabled: !(settings.exportFormat == ExportFormat.pdf &&
- settings.exportPdfExportData),
- keyboardType: TextInputType.number,
- inputWidth: 40,
- ),
- InputSettingsTile(
- initialValue: settings.exportPdfHeaderFontSize.toString(),
- title: Text(localizations.exportPdfHeaderFontSize),
- onEditingComplete: (value) {
- final pV = double.tryParse(value ?? '');
- if (pV != null) settings.exportPdfHeaderFontSize = pV;
- },
- disabled: !(settings.exportFormat == ExportFormat.pdf &&
- settings.exportPdfExportData),
- keyboardType: TextInputType.number,
- inputWidth: 40,
- ),
- InputSettingsTile(
- initialValue: settings.exportPdfCellFontSize.toString(),
- title: Text(localizations.exportPdfCellFontSize),
- onEditingComplete: (value) {
- final pV = double.tryParse(value ?? '');
- if (pV != null) settings.exportPdfCellFontSize = pV;
- },
- disabled: !(settings.exportFormat == ExportFormat.pdf &&
- settings.exportPdfExportData),
- keyboardType: TextInputType.number,
- inputWidth: 40,
- ),
- const ExportFieldCustomisationSetting(),
+ if (settings.exportFormat == ExportFormat.csv)
+ Consumer<CsvExportSettings>(builder: (context, csvExportSettings, child) =>
+ Column(
+ children: [
+ InputSettingsTile(
+ title: Text(localizations.fieldDelimiter),
+ inputWidth: 40,
+ initialValue: csvExportSettings.fieldDelimiter,
+ disabled: !(settings.exportFormat == ExportFormat.csv),
+ onEditingComplete: (value) {
+ if (value != null) {
+ csvExportSettings.fieldDelimiter = value;
+ }
+ },
+ ),
+ InputSettingsTile(
+ title: Text(localizations.textDelimiter),
+ inputWidth: 40,
+ initialValue: csvExportSettings.textDelimiter,
+ disabled: !(settings.exportFormat == ExportFormat.csv),
+ onEditingComplete: (value) {
+ if (value != null) {
+ csvExportSettings.textDelimiter = value;
+ }
+ },
+ ),
+ SwitchSettingsTile(
+ title: Text(localizations.exportCsvHeadline),
+ description: Text(localizations.exportCsvHeadlineDesc),
+ initialValue: csvExportSettings.exportHeadline,
+ disabled: settings.exportFormat != ExportFormat.csv,
+ onToggle: (value) {
+ csvExportSettings.exportHeadline = value;
+ }
+ ),
+ ExportFieldCustomisationSetting(
+ fieldsSettings: csvExportSettings,
+ ),
+ ],
+ )
+ ),
+ if (settings.exportFormat == ExportFormat.pdf)
+ Consumer<PdfExportSettings>(builder: (context, pdfExportSettings, child) =>
+ Column(
+ children: [
+ SwitchSettingsTile(
+ title: Text(localizations.exportPdfExportTitle),
+ initialValue: pdfExportSettings.exportTitle,
+ onToggle: (value) {
+ pdfExportSettings.exportTitle = value;
+ }),
+ SwitchSettingsTile(
+ title: Text(localizations.exportPdfExportStatistics),
+ initialValue: pdfExportSettings.exportStatistics,
+ onToggle: (value) {
+ pdfExportSettings.exportStatistics = value;
+ }),
+ SwitchSettingsTile(
+ title: Text(localizations.exportPdfExportData),
+ initialValue: pdfExportSettings.exportData,
+ onToggle: (value) {
+ pdfExportSettings.exportData = value;
+ }),
+ InputSettingsTile(
+ initialValue: pdfExportSettings.headerHeight.toString(),
+ title: Text(localizations.exportPdfHeaderHeight),
+ onEditingComplete: (value) {
+ final pV = double.tryParse(value ?? '');
+ if (pV != null) pdfExportSettings.headerHeight = pV;
+ },
+ disabled: !(pdfExportSettings.exportData),
+ keyboardType: TextInputType.number,
+ inputWidth: 40,
+ ),
+ InputSettingsTile(
+ initialValue: pdfExportSettings.cellHeight.toString(),
+ title: Text(localizations.exportPdfCellHeight),
+ onEditingComplete: (value) {
+ final pV = double.tryParse(value ?? '');
+ if (pV != null) pdfExportSettings.cellHeight = pV;
+ },
+ disabled: !pdfExportSettings.exportData,
+ keyboardType: TextInputType.number,
+ inputWidth: 40,
+ ),
+ InputSettingsTile(
+ initialValue: pdfExportSettings.headerFontSize.toString(),
+ title: Text(localizations.exportPdfHeaderFontSize),
+ onEditingComplete: (value) {
+ final pV = double.tryParse(value ?? '');
+ if (pV != null) pdfExportSettings.headerFontSize = pV;
+ },
+ disabled: !pdfExportSettings.exportData,
+ keyboardType: TextInputType.number,
+ inputWidth: 40,
+ ),
+ InputSettingsTile(
+ initialValue: pdfExportSettings.cellFontSize.toString(),
+ title: Text(localizations.exportPdfCellFontSize),
+ onEditingComplete: (value) {
+ final pV = double.tryParse(value ?? '');
+ if (pV != null) pdfExportSettings.cellFontSize = pV;
+ },
+ disabled: !pdfExportSettings.exportData,
+ keyboardType: TextInputType.number,
+ inputWidth: 40,
+ ),
+ if (pdfExportSettings.exportData)
+ ExportFieldCustomisationSetting(
+ fieldsSettings: pdfExportSettings,
+ ),
+ ]
+ )
+ ),
],
),
);
@@ -179,70 +198,48 @@ class ExportImportScreen extends StatelessWidget {
}
class ExportFieldCustomisationSetting extends StatelessWidget {
- const ExportFieldCustomisationSetting({super.key});
+ const ExportFieldCustomisationSetting({super.key, required this.fieldsSettings});
+
+ final CustomFieldsSettings fieldsSettings;
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
return ConsistentFutureBuilder(
- future: ExportConfigurationModel.get(Provider.of<Settings>(context, listen: false), localizations),
- onData: (context, configurationModel) {
- return Consumer<Settings>(builder: (context, settings, child) {
- /// whether or not the currently selected export format supports field customization
- final isApplicable = (settings.exportFormat == ExportFormat.csv ||
- settings.exportFormat == ExportFormat.pdf &&
- settings.exportPdfExportData);
- final exportCustomEntries =
- (settings.exportFormat == ExportFormat.csv)
- ? settings.exportCustomEntriesCsv
- : settings.exportCustomEntriesPdf;
- final exportItems = (settings.exportFormat == ExportFormat.csv)
- ? settings.exportItemsCsv
- : settings.exportItemsPdf;
+ future: ExportConfigurationModel.get(localizations),
+ onData: (context, configurationModel) {
+ return Consumer<ExportSettings>(builder: (context, settings, child) {
+ final formats = configurationModel.availableFormats.toSet();
+ List<ExportColumn> activeFields = configurationModel
+ .getActiveExportColumns(settings.exportFormat, fieldsSettings);
+ List<ExportColumn> hiddenFields = [];
+ for (final internalName in fieldsSettings.customFields) {
+ formats.removeWhere((e) => e.internalName == internalName);
+ }
+ hiddenFields = formats.toList();
- final formats = configurationModel.availableFormats.toSet();
- List<ExportColumn> activeFields = configurationModel
- .getActiveExportColumns(settings.exportFormat);
- List<ExportColumn> hiddenFields = [];
- for (final internalName in exportItems) {
- formats.removeWhere((e) => e.internalName == internalName);
- }
- hiddenFields = formats.toList();
-
- return Column(
- children: [
- SwitchSettingsTile(
- title: Text(localizations.exportCustomEntries),
- initialValue: exportCustomEntries,
- disabled: !isApplicable,
- onToggle: (value) {
- if (settings.exportFormat == ExportFormat.csv) {
- settings.exportCustomEntriesCsv = value;
- } else {
- assert(settings.exportFormat == ExportFormat.pdf);
- settings.exportCustomEntriesPdf = value;
- }
- }),
- (exportCustomEntries && isApplicable)
- ? ExportItemsCustomizer(
- shownItems: activeFields,
- disabledItems: hiddenFields,
- onReorder: (exportItems, exportAddableItems) {
- if (settings.exportFormat == ExportFormat.csv) {
- settings.exportItemsCsv =
- exportItems.map((e) => e.internalName).toList();
- } else {
- assert(settings.exportFormat == ExportFormat.pdf);
- settings.exportItemsPdf =
- exportItems.map((e) => e.internalName).toList();
- }
- },
- )
- : const SizedBox.shrink()
- ],
- );
- });
+ return Column(
+ children: [
+ SwitchSettingsTile(
+ title: Text(localizations.exportCustomEntries),
+ initialValue: fieldsSettings.exportCustomFields,
+ onToggle: (value) {
+ fieldsSettings.exportCustomFields = value;
+ }
+ ),
+ if (fieldsSettings.exportCustomFields)
+ ExportItemsCustomizer(
+ shownItems: activeFields,
+ disabledItems: hiddenFields,
+ onReorder: (exportItems, exportAddableItems) {
+ fieldsSettings.customFields = exportItems.map((e) => e.internalName).toList();
+ },
+ ),
+ ],
+ );
});
+ }
+ );
}
}
@@ -251,11 +248,7 @@ class ExportImportButtons extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final settings = Provider.of<Settings>(context, listen: false);
- final model = Provider.of<BloodPressureModel>(context, listen: false);
- final messenger = ScaffoldMessenger.of(context);
final localizations = AppLocalizations.of(context)!;
- final theme = Theme.of(context);
return Container(
height: 60,
@@ -268,15 +261,15 @@ class ExportImportButtons extends StatelessWidget {
child: MaterialButton(
height: 60,
child: Text(localizations.export),
- onPressed: () async => Exporter(
- settings,
- model,
- messenger,
- localizations,
- theme,
- await ExportConfigurationModel.get(
- settings, localizations))
- .export(),
+ onPressed: () async {
+ final model = Provider.of<BloodPressureModel>(context, listen: false);
+ final interval = Provider.of<IntervallStoreManager>(context, listen: false).exportPage.currentRange;
+
+ Exporter.load(context,
+ await model.getInTimeRange(interval.start, interval.end),
+ await ExportConfigurationModel.get(localizations)
+ ).export();
+ }
)),
const VerticalDivider(),
Expanded(
@@ -284,14 +277,8 @@ class ExportImportButtons extends StatelessWidget {
child: MaterialButton(
height: 60,
child: Text(localizations.import),
- onPressed: () async => Exporter(
- settings,
- model,
- messenger,
- localizations,
- theme,
- await ExportConfigurationModel.get(settings, localizations))
- .import(),
+ onPressed: () async =>
+ Exporter.load(context, [], await ExportConfigurationModel.get(localizations)).import(),
)),
],
),
@@ -313,57 +300,63 @@ class _ExportWarnBannerState extends State<ExportWarnBanner> {
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
- return Consumer<Settings>(builder: (context, settings, child) {
- return ConsistentFutureBuilder(
- future: ExportConfigurationModel.get(Provider.of<Settings>(context, listen: false), localizations),
- onData: (context, configurationModel) {
- String? message;
- final exportCustomEntries =
- (settings.exportFormat == ExportFormat.csv)
- ? settings.exportCustomEntriesCsv
- : settings.exportCustomEntriesPdf;
- final exportFormats = configurationModel
- .getActiveExportColumns(settings.exportFormat)
- .map((e) => e.parsableFormat);
- var missingAttributes = {
- RowDataFieldType.timestamp,
- RowDataFieldType.sys,
- RowDataFieldType.dia,
- RowDataFieldType.pul,
- RowDataFieldType.notes,
- RowDataFieldType.color
- };
- missingAttributes.removeWhere((e) => exportFormats.contains(e));
- if (ExportFormat.db == settings.exportFormat) {
- // When exporting as database no wrong configuration is possible
- } else if (_showWarnBanner && ((ExportFormat.pdf == settings.exportFormat) ||
- settings.exportCsvHeadline == false ||
- exportCustomEntries &&
- missingAttributes.contains(RowDataFieldType.timestamp) ||
- ![',', '|'].contains(settings.csvFieldDelimiter) ||
- !['"', '\''].contains(settings.csvTextDelimiter))) {
- message = localizations.exportWarnConfigNotImportable;
- } else if (_showWarnBanner && exportCustomEntries && missingAttributes.isNotEmpty) {
- message = localizations.exportWarnNotEveryFieldExported(
- missingAttributes.length, missingAttributes.join(', '));
- }
+ return Consumer<Settings>(builder: (context, settings, child) =>
+ Consumer<ExportSettings>(builder: (context, exportSettings, child) =>
+ Consumer<CsvExportSettings>(builder: (context, csvExportSettings, child) =>
+ Consumer<PdfExportSettings>(builder: (context, pdfExportSettings, child) {
+ return ConsistentFutureBuilder(
+ future: ExportConfigurationModel.get(localizations),
+ onData: (context, configurationModel) {
+ String? message;
+ final exportCustomEntries = (exportSettings.exportFormat == ExportFormat.csv)
+ ? csvExportSettings.exportCustomFields : pdfExportSettings.exportCustomFields;
+ final CustomFieldsSettings fieldSettings = (exportSettings.exportFormat == ExportFormat.csv
+ ? csvExportSettings : pdfExportSettings) as CustomFieldsSettings;
+ final exportFormats = configurationModel
+ .getActiveExportColumns(exportSettings.exportFormat, fieldSettings)
+ .map((e) => e.parsableFormat);
+ var missingAttributes = {
+ RowDataFieldType.timestamp,
+ RowDataFieldType.sys,
+ RowDataFieldType.dia,
+ RowDataFieldType.pul,
+ RowDataFieldType.notes,
+ RowDataFieldType.color
+ };
+ missingAttributes.removeWhere((e) => exportFormats.contains(e));
+ if (ExportFormat.db == exportSettings.exportFormat) {
+ // When exporting as database no wrong configuration is possible
+ } else if (_showWarnBanner && ((ExportFormat.pdf == exportSettings.exportFormat) ||
+ csvExportSettings.exportHeadline == false ||
+ exportCustomEntries &&
+ missingAttributes.contains(RowDataFieldType.timestamp) ||
+ ![',', '|'].contains(csvExportSettings.fieldDelimiter) ||
+ !['"', '\''].contains(csvExportSettings.textDelimiter))) {
+ message = localizations.exportWarnConfigNotImportable;
+ } else if (_showWarnBanner && exportCustomEntries && missingAttributes.isNotEmpty) {
+ message = localizations.exportWarnNotEveryFieldExported(
+ missingAttributes.length, missingAttributes.join(', '));
+ }
- if (message != null) {
- return MaterialBanner(
- padding: const EdgeInsets.all(20),
- content: Text(message),
- actions: [
- TextButton(
- onPressed: () {
- setState(() {
- _showWarnBanner = false;
- });
- },
- child: Text(localizations.btnConfirm))
- ]);
- }
- return const SizedBox.shrink();
- });
- });
+ if (message != null) {
+ return MaterialBanner(
+ padding: const EdgeInsets.all(20),
+ content: Text(message),
+ actions: [
+ TextButton(
+ onPressed: () {
+ setState(() {
+ _showWarnBanner = false;
+ });
+ },
+ child: Text(localizations.btnConfirm))
+ ]);
+ }
+ return const SizedBox.shrink();
+ });
+ })
+ )
+ )
+ );
}
}
lib/screens/subsettings/graph_markings.dart
@@ -1,6 +1,6 @@
import 'package:blood_pressure_app/components/input_dialoge.dart';
import 'package:blood_pressure_app/model/horizontal_graph_line.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
lib/screens/add_measurement.dart
@@ -2,7 +2,8 @@ import 'package:blood_pressure_app/components/date_time_picker.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/export_options.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -10,6 +11,8 @@ import 'package:flutter_material_color_picker/flutter_material_color_picker.dart
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
+import '../model/storage/intervall_store.dart';
+
class AddMeasurementPage extends StatefulWidget {
final DateTime? initTime;
final int? initSys;
@@ -162,7 +165,8 @@ class _AddMeasurementPageState extends State<AddMeasurementPage> {
if ((_formKey.currentState?.validate() ?? false) ||
(_systolic == null && _diastolic == null && _pulse == null &&
(_note != null || _needlePin != null))){
- final settings = Provider.of<Settings>(context, listen: false);
+ final settings = Provider.of<ExportSettings>(context, listen: false);
+ final intervalls = Provider.of<IntervallStoreManager>(context, listen: false);
final model = Provider.of<BloodPressureModel>(context, listen: false);
final navigator = Navigator.of(context);
@@ -172,14 +176,12 @@ class _AddMeasurementPageState extends State<AddMeasurementPage> {
await model.add(BloodPressureRecord(_time, _systolic, _diastolic, _pulse, _note ?? '',
needlePin: _needlePin));
if (settings.exportAfterEveryEntry && context.mounted) {
- final exporter = Exporter(settings, model, ScaffoldMessenger.of(context),
- localizations, Theme.of(context),
- await ExportConfigurationModel.get(Provider.of<Settings>(context, listen: false), localizations));
+ final exporter = Exporter.load(context, await model.all, await ExportConfigurationModel.get(localizations));
exporter.export();
}
// ensures the most recent entry is visible when submitting a new measurement
- if (settings.graphStepSize != TimeStep.custom) {
- settings.setToMostRecentIntervall();
+ if (intervalls.mainPage.stepSize != TimeStep.custom) {
+ intervalls.mainPage.setToMostRecentIntervall();
}
navigator.pop();
}
lib/screens/home.dart
@@ -1,6 +1,6 @@
import 'package:blood_pressure_app/components/legacy_measurement_list.dart';
import 'package:blood_pressure_app/components/measurement_graph.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:blood_pressure_app/screens/add_measurement.dart';
import 'package:blood_pressure_app/screens/settings.dart';
import 'package:blood_pressure_app/screens/statistics.dart';
lib/screens/settings.dart
@@ -4,6 +4,7 @@ import 'package:blood_pressure_app/components/settings_widgets.dart';
import 'package:blood_pressure_app/model/blood_pressure.dart';
import 'package:blood_pressure_app/model/iso_lang_names.dart';
import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:blood_pressure_app/screens/subsettings/enter_timeformat.dart';
import 'package:blood_pressure_app/screens/subsettings/export_import_screen.dart';
import 'package:blood_pressure_app/screens/subsettings/graph_markings.dart';
lib/screens/statistics.dart
@@ -1,10 +1,8 @@
-import 'dart:collection';
-
-import 'package:blood_pressure_app/components/consistent_future_builder.dart';
+import 'package:blood_pressure_app/components/blood_pressure_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';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -20,133 +18,131 @@ class StatisticsPage extends StatelessWidget {
title: Text(AppLocalizations.of(context)!.statistics),
backgroundColor: Theme.of(context).primaryColor,
),
- body: SingleChildScrollView(child: Consumer<BloodPressureModel>(
- builder: (context, model, child) {
- return Consumer<Settings>(builder: (context, settings, child) {
- return ConsistentFutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
- 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),
+ body: SingleChildScrollView(
+ child: Consumer<Settings>(builder: (context, settings, child) {
+ return BloodPressureBuilder(
+ rangeType: IntervallStoreManagerLocation.statsPage,
+ 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),
+ ],
+ ),
),
- 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(),
+ child: const IntervalPicker(type: IntervallStoreManagerLocation.statsPage,),
)
);
}
lib/main.dart
@@ -1,7 +1,8 @@
import 'package:blood_pressure_app/model/blood_pressure.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
import 'package:blood_pressure_app/model/storage/db/config_dao.dart';
import 'package:blood_pressure_app/model/storage/db/config_db.dart';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:blood_pressure_app/screens/home.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -17,17 +18,31 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 2 different db files
final dataModel = await BloodPressureModel.create();
- final settingsModel = await Settings.create();
// TODO:
- globalConfigDao = ConfigDao(await ConfigDB.open());
+ final configDB = await ConfigDB.open();
+ final configDao = ConfigDao(configDB);
+
+ final settings = await configDao.loadSettings(0);
+ final exportSettings = await configDao.loadExportSettings(0);
+ final csvExportSettings = await configDao.loadCsvExportSettings(0);
+ final pdfExportSettings = await configDao.loadPdfExportSettings(0);
+ final intervalStorageManager = await IntervallStoreManager.load(configDao, 0);
+
+ // TODO: old settings migration
+
+ globalConfigDao = configDao;
// Reset the step size intervall to current on startup
- settingsModel.changeStepSize(settingsModel.graphStepSize);
+ intervalStorageManager.mainPage.setToMostRecentIntervall();
runApp(MultiProvider(providers: [
ChangeNotifierProvider(create: (context) => dataModel),
- ChangeNotifierProvider(create: (context) => settingsModel),
+ ChangeNotifierProvider(create: (context) => settings),
+ ChangeNotifierProvider(create: (context) => exportSettings),
+ ChangeNotifierProvider(create: (context) => csvExportSettings),
+ ChangeNotifierProvider(create: (context) => pdfExportSettings),
+ ChangeNotifierProvider(create: (context) => intervalStorageManager),
], child: const AppRoot()));
}
test/model/json_serialization_test.dart
@@ -1,4 +1,5 @@
+import 'package:blood_pressure_app/model/horizontal_graph_line.dart';
import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
@@ -87,6 +88,7 @@ void main() {
drawRegressionLines: false,
startWithAddMeasurementPage: false,
useLegacyList: false,
+ horizontalGraphLines: [HorizontalGraphLine(Colors.blue, 1230)],
);
final fromJson = Settings.fromJson(initial.toJson());
@@ -109,6 +111,9 @@ void main() {
expect(initial.drawRegressionLines, fromJson.drawRegressionLines);
expect(initial.startWithAddMeasurementPage, fromJson.startWithAddMeasurementPage);
expect(initial.useLegacyList, fromJson.useLegacyList);
+ expect(initial.horizontalGraphLines.length, fromJson.horizontalGraphLines.length);
+ expect(initial.horizontalGraphLines.first.color.value, fromJson.horizontalGraphLines.first.color.value);
+ expect(initial.horizontalGraphLines.first.height, fromJson.horizontalGraphLines.first.height);
expect(initial.toJson(), fromJson.toJson());
});
test/ui/add_measurement.dart → test/ui/add_measurement_test.dart
@@ -1,6 +1,10 @@
import 'package:blood_pressure_app/model/blood_pressure.dart';
import 'package:blood_pressure_app/model/ram_only_implementations.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:blood_pressure_app/screens/add_measurement.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -126,13 +130,30 @@ void main() {
});
}
-Future<void> _initPage(WidgetTester widgetTester, Widget pageWidget, {Settings? settings, BloodPressureModel? model}) async {
+Future<void> _initPage(WidgetTester widgetTester, Widget pageWidget, {
+ Settings? settings,
+ ExportSettings? exportSettings,
+ CsvExportSettings? csvExportSettings,
+ PdfExportSettings? pdfExportSettings,
+ IntervallStoreManager? intervallStoreManager,
+ BloodPressureModel? model,
+}) async {
+ model ??= RamBloodPressureModel();
+ settings ??= Settings();
+ exportSettings ??= ExportSettings();
+ csvExportSettings ??= CsvExportSettings();
+ pdfExportSettings ??= PdfExportSettings();
+ intervallStoreManager ??= IntervallStoreManager(IntervallStorage(), IntervallStorage(), IntervallStorage());
await widgetTester.pumpWidget(
MaterialApp(
home: MultiProvider(
providers: [
- ChangeNotifierProvider<Settings>(create: (_) => settings ?? RamSettings()),
- ChangeNotifierProvider<BloodPressureModel>(create: (_) => model ?? RamBloodPressureModel()),
+ ChangeNotifierProvider(create: (_) => settings),
+ ChangeNotifierProvider(create: (_) => exportSettings),
+ ChangeNotifierProvider(create: (_) => csvExportSettings),
+ ChangeNotifierProvider(create: (_) => pdfExportSettings),
+ ChangeNotifierProvider(create: (_) => intervallStoreManager),
+ ChangeNotifierProvider<BloodPressureModel>(create: (_) => model!),
],
child: Localizations(
delegates: AppLocalizations.localizationsDelegates,
test/ui/statistics_test.dart
@@ -1,6 +1,10 @@
import 'package:blood_pressure_app/model/blood_pressure.dart';
import 'package:blood_pressure_app/model/ram_only_implementations.dart';
-import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:blood_pressure_app/screens/statistics.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -10,23 +14,38 @@ import 'package:provider/provider.dart';
void main() {
group("StatisticsPage", () {
testWidgets('should load page', (widgetTester) async {
- await _initStatsPage(widgetTester, RamSettings(), []);
+ await _initStatsPage(widgetTester, []);
expect(find.text('Statistics'), findsOneWidget);
});
testWidgets("should report measurement count", (widgetTester) async {
- await _initStatsPage(widgetTester, _allMeasurements(), [
+ await _initStatsPage(widgetTester, [
for (int i = 1; i<51; i++) // can't safe entries at or before epoch
BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1582991592 + i), 40+i, 60+i, 30+i, 'Test comment $i'),
- ]);
+ ],
+ intervallStoreManager: IntervallStoreManager(IntervallStorage(), IntervallStorage(), IntervallStorage(stepSize: TimeStep.lifetime))
+ );
final measurementCountWidget = find.byKey(const Key('measurementCount'));
expect(measurementCountWidget, findsOneWidget);
+ expect(find.descendant(of: measurementCountWidget, matching: find.text('49')), findsNothing);
+ expect(find.descendant(of: measurementCountWidget, matching: find.text('51')), findsNothing);
expect(find.descendant(of: measurementCountWidget, matching: find.text('50')), findsOneWidget);
});
});
}
-Future<void> _initStatsPage(WidgetTester widgetTester, Settings settings, List<BloodPressureRecord> records) async {
+Future<void> _initStatsPage(WidgetTester widgetTester, List<BloodPressureRecord> records, {
+ Settings? settings,
+ ExportSettings? exportSettings,
+ CsvExportSettings? csvExportSettings,
+ PdfExportSettings? pdfExportSettings,
+ IntervallStoreManager? intervallStoreManager,
+}) async {
final model = RamBloodPressureModel();
+ settings ??= Settings();
+ exportSettings ??= ExportSettings();
+ csvExportSettings ??= CsvExportSettings();
+ pdfExportSettings ??= PdfExportSettings();
+ intervallStoreManager ??= IntervallStoreManager(IntervallStorage(), IntervallStorage(), IntervallStorage());
for (var r in records) {
model.add(r);
@@ -34,7 +53,11 @@ Future<void> _initStatsPage(WidgetTester widgetTester, Settings settings, List<B
await widgetTester.pumpWidget(MultiProvider(
providers: [
- ChangeNotifierProvider<Settings>(create: (_) => settings),
+ ChangeNotifierProvider(create: (_) => settings),
+ ChangeNotifierProvider(create: (_) => exportSettings),
+ ChangeNotifierProvider(create: (_) => csvExportSettings),
+ ChangeNotifierProvider(create: (_) => pdfExportSettings),
+ ChangeNotifierProvider(create: (_) => intervallStoreManager),
ChangeNotifierProvider<BloodPressureModel>(create: (_) => model),
],
child: Localizations(
@@ -44,10 +67,4 @@ Future<void> _initStatsPage(WidgetTester widgetTester, Settings settings, List<B
)
));
await widgetTester.pumpAndSettle();
-}
-
-RamSettings _allMeasurements() {
- final settings = RamSettings();
- settings.changeStepSize(TimeStep.lifetime);
- return settings;
}
\ No newline at end of file