Commit 6fbebb1
Changed files (4)
lib
lib/components/measurement_graph.dart
@@ -6,211 +6,160 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:provider/provider.dart';
import 'dart:math';
import 'package:intl/intl.dart';
+import 'package:blood_pressure_app/model/settings.dart';
-class _LineChart extends StatefulWidget {
- @override
- State<StatefulWidget> createState() {
- return _LineChartState();
- }
-}
-
-class _LineChartState extends State<_LineChart> {
- var _displayMode = DisplayModes.day;
-
+class _LineChart extends StatelessWidget {
@override
Widget build(BuildContext context) {
const pulseColor = Colors.red;
const diaColor = Colors.green;
const sysColor = Colors.teal;
-
return Stack(
children: [
- Container(
- height: 200,
- child: Consumer<BloodPressureModel>(
- builder: (context, model, child) {
- late final Future<UnmodifiableListView<BloodPressureRecord>> dataFuture;
- DateTime now = DateTime.now();
- switch (_displayMode) {
- case DisplayModes.day:
- dataFuture = model.getInTimeRange(DateTime(now.year, now.month, now.day), now);
- break;
- case DisplayModes.month:
- dataFuture = model.getInTimeRange(DateTime(now.year, now.month), now);
- break;
- case DisplayModes.year:
- dataFuture = model.getInTimeRange(DateTime(now.year), now);
- break;
- case DisplayModes.lifetime:
- dataFuture = model.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(0), now);
- break;
- }
-
-
- return FutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
- future: dataFuture,
- builder: (BuildContext context, AsyncSnapshot<UnmodifiableListView<BloodPressureRecord>> snapshot) {
- Widget res;
- switch (snapshot.connectionState) {
- case ConnectionState.none:
- res = const Text('not started');
+ Align(
+ alignment: Alignment.topCenter,
+ child: Container(
+ height: 200,
+ child: Consumer<Settings>(
+ builder: (context, settings, child) {
+ return Consumer<BloodPressureModel>(
+ builder: (context, model, child) {
+ late final Future<UnmodifiableListView<BloodPressureRecord>> dataFuture;
+ DateTime now = DateTime.now();
+ switch (settings.graphStepSize) {
+ case TimeStep.day:
+ dataFuture = model.getInTimeRange(DateTime(now.year, now.month, now.day), now);
break;
- case ConnectionState.waiting:
- res = const Text('loading...');
+ case TimeStep.month:
+ dataFuture = model.getInTimeRange(DateTime(now.year, now.month), now);
break;
- default:
- if (snapshot.hasError) {
- res = Text('ERROR: ${snapshot.error}');
- } else {
- assert(snapshot.hasData);
- final data = snapshot.data ?? [];
-
- List<FlSpot> pulseSpots = [];
- List<FlSpot> diastolicSpots = [];
- List<FlSpot> systolicSpots = [];
- int pulMax = 0;
- int diaMax = 0;
- int sysMax = 0;
- for (var element in data) {
- final x = element.creationTime.millisecondsSinceEpoch.toDouble();
- diastolicSpots.add(FlSpot(x, element.diastolic.toDouble()));
- systolicSpots.add(FlSpot(x, element.systolic.toDouble()));
- pulseSpots.add(FlSpot(x, element.pulse.toDouble()));
- pulMax = max(pulMax, element.pulse);
- diaMax = max(diaMax, element.diastolic);
- sysMax = max(sysMax, element.systolic);
- }
-
+ case TimeStep.year:
+ dataFuture = model.getInTimeRange(DateTime(now.year), now);
+ break;
+ case TimeStep.lifetime:
+ dataFuture = model.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(0), now);
+ break;
+ }
- final noTitels = AxisTitles(sideTitles: SideTitles(reservedSize: 40, showTitles: false));
- res = LineChart(
- swapAnimationDuration: const Duration(milliseconds: 250),
- LineChartData(
- minY: 30,
- maxY: max(pulMax.toDouble(), max(diaMax.toDouble(), sysMax.toDouble())) + 5,
- titlesData: FlTitlesData(topTitles: noTitels, rightTitles: noTitels,
- bottomTitles: AxisTitles(
- sideTitles: SideTitles(
- showTitles: true,
- getTitlesWidget: (double pos, TitleMeta meta) {
- late final DateFormat formater;
- switch (_displayMode) {
- case DisplayModes.day:
- formater = DateFormat('H:mm');
- break;
- case DisplayModes.month:
- formater = DateFormat('d');
- break;
- case DisplayModes.year:
- formater = DateFormat('MMM');
- break;
- case DisplayModes.lifetime:
- formater = DateFormat('yyyy');
- }
- return Text(
- formater.format(DateTime.fromMillisecondsSinceEpoch(pos.toInt()))
- );
- }
+ return FutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
+ future: dataFuture,
+ builder: (BuildContext context, AsyncSnapshot<UnmodifiableListView<BloodPressureRecord>> snapshot) {
+ Widget res;
+ switch (snapshot.connectionState) {
+ case ConnectionState.none:
+ res = const Text('not started');
+ break;
+ case ConnectionState.waiting:
+ res = const Text('loading...');
+ break;
+ default:
+ if (snapshot.hasError) {
+ res = Text('ERROR: ${snapshot.error}');
+ } else {
+ assert(snapshot.hasData);
+ final data = snapshot.data ?? [];
+
+ List<FlSpot> pulseSpots = [];
+ List<FlSpot> diastolicSpots = [];
+ List<FlSpot> systolicSpots = [];
+ int pulMax = 0;
+ int diaMax = 0;
+ int sysMax = 0;
+ for (var element in data) {
+ final x = element.creationTime.millisecondsSinceEpoch.toDouble();
+ diastolicSpots.add(FlSpot(x, element.diastolic.toDouble()));
+ systolicSpots.add(FlSpot(x, element.systolic.toDouble()));
+ pulseSpots.add(FlSpot(x, element.pulse.toDouble()));
+ pulMax = max(pulMax, element.pulse);
+ diaMax = max(diaMax, element.diastolic);
+ sysMax = max(sysMax, element.systolic);
+ }
+
+
+ final noTitels = AxisTitles(sideTitles: SideTitles(reservedSize: 40, showTitles: false));
+ res = LineChart(
+ swapAnimationDuration: const Duration(milliseconds: 250),
+ LineChartData(
+ minY: 30,
+ maxY: max(pulMax.toDouble(), max(diaMax.toDouble(), sysMax.toDouble())) + 5,
+ titlesData: FlTitlesData(topTitles: noTitels, rightTitles: noTitels,
+ bottomTitles: AxisTitles(
+ sideTitles: SideTitles(
+ showTitles: true,
+ getTitlesWidget: (double pos, TitleMeta meta) {
+ late final DateFormat formater;
+ switch (settings.graphStepSize) {
+ case TimeStep.day:
+ formater = DateFormat('H:mm');
+ break;
+ case TimeStep.month:
+ formater = DateFormat('d');
+ break;
+ case TimeStep.year:
+ formater = DateFormat('MMM');
+ break;
+ case TimeStep.lifetime:
+ formater = DateFormat('yyyy');
+ }
+ return Text(
+ formater.format(DateTime.fromMillisecondsSinceEpoch(pos.toInt()))
+ );
+ }
+ ),
+ )
),
- )
- ),
- lineBarsData: [
- // high blood pressure marking acordning to https://www.texasheart.org/heart-health/heart-information-center/topics/high-blood-pressure-hypertension/
- LineChartBarData(
- spots: pulseSpots,
- color: pulseColor,
- barWidth: 4,
- isCurved: true,
- preventCurveOverShooting: true
- ),
- LineChartBarData(
- spots: diastolicSpots,
- color: diaColor,
- barWidth: 4,
- isCurved: true,
- preventCurveOverShooting: true,
- belowBarData: BarAreaData(
- show: true,
- color: Colors.red.shade400.withAlpha(100),
- cutOffY: 80,
- applyCutOffY: true
- )
- ),
- LineChartBarData(
- spots: systolicSpots,
- color: sysColor,
- barWidth: 4,
- isCurved: true,
- preventCurveOverShooting: true,
- belowBarData: BarAreaData(
- show: true,
- color: Colors.red.shade400.withAlpha(100),
- cutOffY: 130,
- applyCutOffY: true
- )
+ lineBarsData: [
+ // high blood pressure marking acordning to https://www.texasheart.org/heart-health/heart-information-center/topics/high-blood-pressure-hypertension/
+ LineChartBarData(
+ spots: pulseSpots,
+ color: pulseColor,
+ barWidth: 4,
+ isCurved: true,
+ preventCurveOverShooting: true
+ ),
+ LineChartBarData(
+ spots: diastolicSpots,
+ color: diaColor,
+ barWidth: 4,
+ isCurved: true,
+ preventCurveOverShooting: true,
+ belowBarData: BarAreaData(
+ show: true,
+ color: Colors.red.shade400.withAlpha(100),
+ cutOffY: 80,
+ applyCutOffY: true
+ )
+ ),
+ LineChartBarData(
+ spots: systolicSpots,
+ color: sysColor,
+ barWidth: 4,
+ isCurved: true,
+ preventCurveOverShooting: true,
+ belowBarData: BarAreaData(
+ show: true,
+ color: Colors.red.shade400.withAlpha(100),
+ cutOffY: 130,
+ applyCutOffY: true
+ )
+ )
+ ]
)
- ]
- )
- );
+ );
+ }
+ }
+ return res;
}
- }
- return res;
+ );
}
- );
- },
- )
+ );
+ },
+ )
+ ),
),
- Align(
- alignment: Alignment.topRight,
- child: Container(
- color: Colors.white,
- child: DropdownButton<int>(
- value: _displayMode,
- onChanged: (int? value) {
- setState(() {
- _displayMode = value ?? 1;
- });
- },
- dropdownColor: Colors.white,
- items: DisplayModes.options.map<DropdownMenuItem<int>>((v) {
- return DropdownMenuItem(
- value: v,
- child: Text(
- DisplayModes.getName(v)
- )
- );
- }).toList(),
- ),
- )
- )
],
);
-
- }
-
-}
-
-class DisplayModes {
- static const options = [0, 1, 2, 3];
-
- static const day = 0;
- static const month = 1;
- static const year = 2;
- static const lifetime = 3;
-
- static String getName(int opt) {
- switch (opt) {
- case day:
- return 'day';
- case month:
- return 'month';
- case year:
- return 'year';
- case lifetime:
- return 'lifetime';
- }
- return 'invalid';
}
}
@@ -221,10 +170,38 @@ class MeasurementGraph extends StatelessWidget {
Widget build(BuildContext context) {
// TODO: implement build
return SizedBox(
- height: 100,
+ height: 1000,
child: Padding(
- padding: const EdgeInsets.only(right: 16, left: 6, top: 10),
- child: _LineChart(),
+ padding: const EdgeInsets.only(right: 16, left: 6, top: 2),
+ child: Column(
+ children: [
+ _LineChart(),
+ Consumer<Settings>(
+ builder: (context, settings, child) {
+ return Container(
+ color: Colors.white,
+ child: DropdownButton<int>(
+ value: settings.graphStepSize,
+ onChanged: (int? value) {
+ if (value != null) {
+ settings.graphStepSize = value;
+ }
+ },
+ dropdownColor: Colors.white,
+ items: TimeStep.options.map<DropdownMenuItem<int>>((v) {
+ return DropdownMenuItem(
+ value: v,
+ child: Text(
+ TimeStep.getName(v)
+ )
+ );
+ }).toList(),
+ ),
+ );
+ }
+ )
+ ],
+ ),
),
);
}
lib/model/blood_pressure.dart
@@ -63,7 +63,7 @@ class BloodPressureModel extends ChangeNotifier {
measurement.pulse,
measurement.notes]);
- _cacheLast();
+ _cacheLast(); // hast notifyListeners()
}
/// Returns the last x BloodPressureRecords from new to old.
lib/model/settings.dart
@@ -0,0 +1,94 @@
+
+import 'dart:collection';
+import 'dart:io';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:path/path.dart';
+import 'package:sqflite_common_ffi/sqflite_ffi.dart';
+import 'package:sqflite/sqflite.dart';
+import 'package:file_saver/file_saver.dart';
+import 'package:share_plus/share_plus.dart';
+import 'dart:convert' show utf8;
+import 'dart:typed_data';
+
+class Settings extends ChangeNotifier {
+ static const maxEntries = 2E64; // https://www.sqlite.org/limits.html Nr.13
+ late final Database _database;
+
+ late int _graphStepSize;
+
+ Settings._create();
+ Future<void> _asyncInit() async {
+ _database = await openDatabase(
+ join(await getDatabasesPath(), 'settings.db'),
+ // runs when the database is first created
+ onCreate: (db, version) {
+ return db.execute('CREATE TABLE settings(key STRING PRIMARY KEY, value STRING)');
+ },
+ version: 1,
+ );
+ await _loadSettings();
+ }
+ // factory method, to allow for async contructor
+ static Future<Settings> create() async {
+ final component = Settings._create();
+ await component._asyncInit();
+ return component;
+ }
+
+ Future<Object?> _getSetting(String key) async {
+ final r = await _database.query('settings', where: 'key = ?', whereArgs: [key]);
+ if (r.isNotEmpty) {
+ return r[0]['value'];
+ }
+ }
+
+ Future<void> _saveSetting(String key, Object value) async {
+ if ((await _database.query('settings', where: 'key = ?', whereArgs: [key])).isEmpty) {
+ _database.insert('settings', {'key': key, 'value': value});
+ } else {
+ _database.update('settings', {'value': value}, where: 'key = ?', whereArgs: [key]);
+ }
+ }
+
+ Future<void> _loadSettings() async {
+ var pGraphStepSize = _getSetting('_graphStepSize');
+ // var ...
+
+ _graphStepSize = (await pGraphStepSize as int?) ?? TimeStep.day;
+ // ...
+ }
+
+ int get graphStepSize {
+ return _graphStepSize;
+ }
+ set graphStepSize(int newStepSize) {
+ _graphStepSize = newStepSize;
+ _saveSetting('_graphStepSize', newStepSize);
+ notifyListeners();
+ }
+
+}
+
+class TimeStep {
+ static const options = [0, 1, 2, 3];
+
+ static const day = 0;
+ static const month = 1;
+ static const year = 2;
+ static const lifetime = 3;
+
+ static String getName(int opt) {
+ switch (opt) {
+ case day:
+ return 'day';
+ case month:
+ return 'month';
+ case year:
+ return 'year';
+ case lifetime:
+ return 'lifetime';
+ }
+ return 'invalid';
+ }
+}
\ No newline at end of file
lib/main.dart
@@ -1,3 +1,4 @@
+import 'package:blood_pressure_app/model/settings.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:blood_pressure_app/model/blood_pressure.dart';
@@ -16,13 +17,18 @@ void main() async {
databaseFactory = databaseFactoryFfi;
}
+ // 2 different db files
final dataModel = await BloodPressureModel.create();
+ final settingsModel = await Settings.create();
runApp(
- ChangeNotifierProvider(
- create: (context) => dataModel,
- child: const AppRoot(),
- ),
+ MultiProvider(
+ providers: [
+ ChangeNotifierProvider(create: (context) => dataModel),
+ ChangeNotifierProvider(create: (context) => settingsModel),
+ ],
+ child: const AppRoot()
+ )
);
}