Commit 44c3a32
Changed files (4)
lib
lib/model/blood_pressure.dart
@@ -113,6 +113,40 @@ class BloodPressureModel extends ChangeNotifier {
}
return UnmodifiableListView(recordsInRange);
}
+
+ Future<int> get count async {
+ return (await _database.rawQuery('SELECT COUNT(*) FROM bloodPressureModel'))[0]['COUNT(*)'] as int? ?? -1;
+ }
+ Future<int> get avgDia async {
+ var res = (await _database.rawQuery('SELECT AVG(diastolic) as dia FROM bloodPressureModel'))[0]['dia'];
+ int? val;
+ try {
+ val = (res as int?);
+ } catch (e) {
+ val = (res as double?)?.toInt();
+ }
+ return val ?? -1;
+ }
+ Future<int> get avgSys async {
+ var res = (await _database.rawQuery('SELECT AVG(systolic) as sys FROM bloodPressureModel'))[0]['sys'];
+ int? val;
+ try {
+ val = (res as int?);
+ } catch (e) {
+ val = (res as double?)?.toInt();
+ }
+ return val ?? -1;
+ }
+ Future<int> get avgPul async {
+ var res = (await _database.rawQuery('SELECT AVG(pulse) as pul FROM bloodPressureModel'))[0]['pul'];
+ int? val;
+ try {
+ val = (res as int?);
+ } catch (e) {
+ val = (res as double?)?.toInt();
+ }
+ return val ?? -1;
+ }
Future<void> save(void Function(bool success, String? msg) callback, {bool exportAsText = false}) async {
// create csv
lib/screens/home.dart
@@ -1,6 +1,7 @@
import 'package:blood_pressure_app/model/settings.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';
import 'package:flutter/material.dart';
import 'package:blood_pressure_app/components/measurement_graph.dart';
import 'package:blood_pressure_app/components/measurement_list.dart';
@@ -51,51 +52,69 @@ class AppHome extends StatelessWidget {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
return Consumer<Settings>(
builder: (context, settings, child) {
- return SizedBox(
- child: Column(
- verticalDirection: VerticalDirection.up,
- children: [
- Ink(
- decoration: ShapeDecoration(
- shape: const CircleBorder(),
- color: Theme.of(context).primaryColor
- ),
- child: IconButton(
- iconSize: settings.iconSize,
- icon: const Icon(
- Icons.add,
- color: Colors.black,
- ),
- onPressed: () {
- Navigator.push(
- context,
- MaterialPageRoute(builder: (context) => const AddMeasurementPage()),
- );
- },
+ return Column(
+ verticalDirection: VerticalDirection.up,
+ children: [
+ Ink(
+ decoration: ShapeDecoration(
+ shape: const CircleBorder(),
+ color: Theme.of(context).primaryColor
+ ),
+ child: IconButton(
+ iconSize: settings.iconSize,
+ icon: const Icon(
+ Icons.add,
+ color: Colors.black,
),
+ onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const AddMeasurementPage()),
+ );
+ },
),
- const SizedBox(height: 10,),
- Ink(
- decoration: ShapeDecoration(
- shape: const CircleBorder(),
- color: Theme.of(context).unselectedWidgetColor
+ ),
+ const SizedBox(height: 10,),
+ Ink(
+ decoration: ShapeDecoration(
+ shape: const CircleBorder(),
+ color: Theme.of(context).unselectedWidgetColor
+ ),
+ child: IconButton(
+ iconSize: settings.iconSize,
+ icon: const Icon(
+ Icons.insights,
+ color: Colors.black
),
- child: IconButton(
- iconSize: settings.iconSize,
- icon: const Icon(
- Icons.settings,
- color: Colors.black
- ),
- onPressed: () {
- Navigator.push(
- context,
- MaterialPageRoute(builder: (context) => const SettingsPage()),
- );
- },
+ onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const StatisticsPage()),
+ );
+ },
+ ),
+ ),
+ const SizedBox(height: 10,),
+ Ink(
+ decoration: ShapeDecoration(
+ shape: const CircleBorder(),
+ color: Theme.of(context).unselectedWidgetColor
+ ),
+ child: IconButton(
+ iconSize: settings.iconSize,
+ icon: const Icon(
+ Icons.settings,
+ color: Colors.black
),
+ onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const SettingsPage()),
+ );
+ },
),
- ],
- ),
+ ),
+ ],
);
}
);
lib/screens/settings.dart
@@ -16,127 +16,125 @@ class SettingsPage extends StatelessWidget {
backgroundColor: Theme.of(context).primaryColor,
),
body: Consumer<Settings>(
- builder: (context, settings, child) {
- return Scaffold(
- body: ListView(
+ builder: (context, settings, child) {
+ return ListView(
+ children: [
+ SettingsSection(
+ title: const Text('layout'),
children: [
- SettingsSection(
- title: const Text('layout'),
- children: [
- SwitchSettingsTile(
- initialValue: settings.allowManualTimeInput,
- onToggle: (value) {
- settings.allowManualTimeInput = value;
- },
- leading: const Icon(Icons.details),
- title: const Text('allow manual time input')
- ),
- SettingsTile(
- title: const Text('time format'),
- leading: const Icon(Icons.schedule),
- trailing: Icon(Icons.arrow_forward_ios, color: Theme.of(context).highlightColor,),
- description: Text(settings.dateFormatString),
- onPressed: (context) {
- Navigator.push(
- context,
- MaterialPageRoute(builder: (context) => const EnterTimeFormatScreen()),
- );
- },
- ),
- SwitchSettingsTile(
- initialValue: settings.followSystemDarkMode,
- onToggle: (value) {
- settings.followSystemDarkMode = value;
- },
- leading: const Icon(Icons.auto_mode),
- title: const Text('follow system dark mode')
- ),
- SwitchSettingsTile(
- initialValue: (() {
- if (settings.followSystemDarkMode) {
- return MediaQuery.of(context).platformBrightness == Brightness.dark;
- }
- return settings.darkMode;
- })(),
- onToggle: (value) {
- settings.darkMode = value;
- },
- leading: const Icon(Icons.dark_mode),
- title: const Text('enable dark mode'),
- disabled: settings.followSystemDarkMode,
- ),
- SliderSettingsTile(title: const Text('icon size'),
- onChanged: (double value) {
- settings.iconSize = value;
- },
- initialValue: settings.iconSize,
- start: 15,
- end: 70,
- stepSize: 5,
- ),
- ColorSelectionSettingsTile(
- onMainColorChanged: (color) => settings.accentColor = settings.createMaterialColor((color ?? Colors.teal).value),
- initialColor: settings.accentColor,
- title: const Text('theme color')
- ),
- ColorSelectionSettingsTile(
- onMainColorChanged: (color) => settings.sysColor = settings.createMaterialColor((color ?? Colors.green).value),
- initialColor: settings.sysColor,
- title: const Text('systolic color')
- ),
- ColorSelectionSettingsTile(
- onMainColorChanged: (color) => settings.diaColor = settings.createMaterialColor((color ?? Colors.teal).value),
- initialColor: settings.diaColor,
- title: const Text('diastolic color')
- ),
- ColorSelectionSettingsTile(
- onMainColorChanged: (color) => settings.pulColor = settings.createMaterialColor((color ?? Colors.red).value),
- initialColor: settings.pulColor,
- title: const Text('pulse color')
- ),
- ]
+ SwitchSettingsTile(
+ initialValue: settings.allowManualTimeInput,
+ onToggle: (value) {
+ settings.allowManualTimeInput = value;
+ },
+ leading: const Icon(Icons.details),
+ title: const Text('allow manual time input')
),
- SettingsSection(
- title: const Text('data'),
- children: [
- SwitchSettingsTile(
- initialValue: settings.useExportCompatability,
- title: const Text('compatability export'),
- description: const Text('sets export mime type to text instead of csv'),
- onToggle: (value) {
- settings.useExportCompatability = value;
- }
- ),
- SettingsTile(
- title: const Text('export'),
- leading: const Icon(Icons.save),
- onPressed: (context) => Provider.of<BloodPressureModel>(context, listen: false).save((success, msg) {
- if (success && msg != null) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text(msg)));
- } else if (!success && msg != null) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text('Error: $msg')));
- }
- }
- , exportAsText: settings.useExportCompatability),
- ),
- SettingsTile(
- title: const Text('import'),
- leading: const Icon(Icons.file_upload),
- onPressed: (context) => Provider.of<BloodPressureModel>(context, listen: false).import((res, String? err) {
- if (res) {
+ SettingsTile(
+ title: const Text('time format'),
+ leading: const Icon(Icons.schedule),
+ trailing: Icon(Icons.arrow_forward_ios, color: Theme.of(context).highlightColor,),
+ description: Text(settings.dateFormatString),
+ onPressed: (context) {
+ Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const EnterTimeFormatScreen()),
+ );
+ },
+ ),
+ SwitchSettingsTile(
+ initialValue: settings.followSystemDarkMode,
+ onToggle: (value) {
+ settings.followSystemDarkMode = value;
+ },
+ leading: const Icon(Icons.auto_mode),
+ title: const Text('follow system dark mode')
+ ),
+ SwitchSettingsTile(
+ initialValue: (() {
+ if (settings.followSystemDarkMode) {
+ return MediaQuery.of(context).platformBrightness == Brightness.dark;
+ }
+ return settings.darkMode;
+ })(),
+ onToggle: (value) {
+ settings.darkMode = value;
+ },
+ leading: const Icon(Icons.dark_mode),
+ title: const Text('enable dark mode'),
+ disabled: settings.followSystemDarkMode,
+ ),
+ SliderSettingsTile(title: const Text('icon size'),
+ onChanged: (double value) {
+ settings.iconSize = value;
+ },
+ initialValue: settings.iconSize,
+ start: 15,
+ end: 70,
+ stepSize: 5,
+ ),
+ ColorSelectionSettingsTile(
+ onMainColorChanged: (color) => settings.accentColor = settings.createMaterialColor((color ?? Colors.teal).value),
+ initialColor: settings.accentColor,
+ title: const Text('theme color')
+ ),
+ ColorSelectionSettingsTile(
+ onMainColorChanged: (color) => settings.sysColor = settings.createMaterialColor((color ?? Colors.green).value),
+ initialColor: settings.sysColor,
+ title: const Text('systolic color')
+ ),
+ ColorSelectionSettingsTile(
+ onMainColorChanged: (color) => settings.diaColor = settings.createMaterialColor((color ?? Colors.teal).value),
+ initialColor: settings.diaColor,
+ title: const Text('diastolic color')
+ ),
+ ColorSelectionSettingsTile(
+ onMainColorChanged: (color) => settings.pulColor = settings.createMaterialColor((color ?? Colors.red).value),
+ initialColor: settings.pulColor,
+ title: const Text('pulse color')
+ ),
+ ]
+ ),
+ SettingsSection(
+ title: const Text('data'),
+ children: [
+ SwitchSettingsTile(
+ initialValue: settings.useExportCompatability,
+ title: const Text('compatability export'),
+ description: const Text('sets export mime type to text instead of csv'),
+ onToggle: (value) {
+ settings.useExportCompatability = value;
+ }
+ ),
+ SettingsTile(
+ title: const Text('export'),
+ leading: const Icon(Icons.save),
+ onPressed: (context) => Provider.of<BloodPressureModel>(context, listen: false).save((success, msg) {
+ if (success && msg != null) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(msg)));
+ } else if (!success && msg != null) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text('Error: $msg')));
+ }
+ }
+ , exportAsText: settings.useExportCompatability),
+ ),
+ SettingsTile(
+ title: const Text('import'),
+ leading: const Icon(Icons.file_upload),
+ onPressed: (context) => Provider.of<BloodPressureModel>(context, listen: false).import((res, String? err) {
+ if (res) {
- } else {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text('Error: ${err ?? 'unknown error'}')));
- }
- }),
- ),
- ],
- )
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text('Error: ${err ?? 'unknown error'}')));
+ }
+ }),
+ ),
],
- ),
+ )
+ ],
);
}
),
lib/screens/statistics.dart
@@ -0,0 +1,118 @@
+import 'package:blood_pressure_app/model/blood_pressure.dart';
+import 'package:fl_chart/fl_chart.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class StatisticsPage extends StatelessWidget {
+ const StatisticsPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Statistics'),
+ backgroundColor: Theme.of(context).primaryColor,
+ ),
+ body: SingleChildScrollView(
+ child: Consumer<BloodPressureModel>(
+ builder: (context, model, child) {
+ return Wrap(
+ children: [
+ Statistic(
+ caption: const Text('Measurement count'),
+ child: futureInt(model.count)
+ ),
+ Statistic(
+ caption: const Text('Diastolic avg.'),
+ child: futureInt(model.avgDia)
+ ),
+ Statistic(
+ caption: const Text('Systolic avg.'),
+ child: futureInt(model.avgSys)
+ ),
+ Statistic(
+ caption: const Text('Pulse avg.'),
+ child: futureInt(model.avgPul)
+ ),
+ ],
+ );
+ },
+ )
+ ),
+ );
+ }
+
+ Widget futureInt(Future<int> value) {
+ return FutureBuilder<int>(
+ future: value,
+ builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
+ switch (snapshot.connectionState) {
+ case ConnectionState.none:
+ return const Text('not started');
+ case ConnectionState.waiting:
+ return const Text('loading...');
+ default:
+ if (snapshot.hasError) {
+ return Text('ERROR: ${snapshot.error}');
+ }
+ assert(snapshot.hasData);
+ if ((snapshot.data??-1) < 0) {
+ return const Text('invalid data');
+ }
+ return Text(snapshot.data?.toString() ?? 'error');
+ }
+ }
+ );
+ }
+}
+
+class Statistic extends StatelessWidget {
+ final Widget caption;
+ final Widget child;
+
+ const Statistic({super.key, required this.caption, required this.child, });
+
+ @override
+ Widget build(BuildContext context) { // TODO
+ return Container(
+ margin: const EdgeInsets.only(left:20, right: 20, top: 20),
+ constraints: const BoxConstraints(
+ minHeight: 50,
+ minWidth: 150
+ ),
+ decoration: BoxDecoration(
+ border: Border.all(
+ width: 4,
+ color: Theme.of(context).textTheme.bodyMedium?.color ?? Colors.white38
+ ),
+ borderRadius: const BorderRadius.all(Radius.circular(25)),
+ ),
+ child: Stack(
+ children: [
+ Positioned(
+ top: 6,
+ left: 12,
+ child: DefaultTextStyle(
+ style: TextStyle(color: Theme.of(context).textTheme.bodyMedium?.color ?? Colors.white38),
+ child: caption
+ ),
+ ),
+ Container(
+ padding: const EdgeInsets.all(30),
+ child: Align(
+ alignment: Alignment.center,
+ child: DefaultTextStyle(
+ style: TextStyle(
+ color: Theme.of(context).primaryColor,
+ fontWeight: FontWeight.bold,
+ fontSize: 40
+ ),
+ child: child,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}