diameter/lib/settings.dart
2022-03-21 01:07:29 +01:00

597 lines
26 KiB
Dart

import 'package:diameter/components/forms/boolean_form_field.dart';
import 'package:diameter/components/forms/number_form_field.dart';
import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart';
import 'package:diameter/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class SettingsScreen extends StatefulWidget {
static const String routeName = '/settings';
const SettingsScreen({Key? key}) : super(key: key);
@override
_SettingsScreenState createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
late Settings _settings;
final ScrollController _scrollController = ScrollController();
bool _measurementsIsExpanded = true;
bool _promptsIsExpanded = true;
bool _formatIsExpanded = true;
final _nutritionMeasurementLabelController = TextEditingController(text: '');
final _glucoseMeasurementLabelController = TextEditingController(text: '');
final _dateFormatController = TextEditingController(text: '');
final _longDateFormatController = TextEditingController(text: '');
final _timeFormatController = TextEditingController(text: '');
final _longTimeFormatController = TextEditingController(text: '');
final _insulinIncrementsController = TextEditingController(text: '');
final _nutritionIncrementsController = TextEditingController(text: '');
final _mmolPerLIncrementsController = TextEditingController(text: '');
final _targetGlucoseMgPerDlController = TextEditingController(text: '');
final _targetGlucoseMmolPerLController = TextEditingController(text: '');
late bool _onlyDisplayActiveGlucoseMeasurement;
late bool _displayBothGlucoseMeasurementsInDetailView;
late bool _displayBothGlucoseMeasurementsInListView;
late bool _showConfirmationDialogOnCancel;
late bool _showConfirmationDialogOnDelete;
late bool _showConfirmationDialogOnStopEvent;
@override
void initState() {
super.initState();
_settings = Settings.get();
_nutritionMeasurementLabelController.text =
nutritionMeasurementLabels[_settings.nutritionMeasurementIndex];
_glucoseMeasurementLabelController.text =
glucoseMeasurementLabels[_settings.glucoseMeasurementIndex];
_insulinIncrementsController.text = _settings.insulinIncrements.toString();
_nutritionIncrementsController.text =
_settings.nutritionIncrements.toString();
_mmolPerLIncrementsController.text =
_settings.mmolPerLIncrements.toString();
_targetGlucoseMgPerDlController.text =
_settings.targetGlucoseMgPerDl.toInt().toString();
_targetGlucoseMmolPerLController.text =
_settings.targetGlucoseMmolPerL.toString();
_onlyDisplayActiveGlucoseMeasurement = _settings.glucoseDisplayModeIndex ==
GlucoseDisplayMode.activeOnly.index;
_displayBothGlucoseMeasurementsInDetailView =
_settings.glucoseDisplayModeIndex == GlucoseDisplayMode.both.index ||
_settings.glucoseDisplayModeIndex ==
GlucoseDisplayMode.bothForDetail.index;
_displayBothGlucoseMeasurementsInListView =
_settings.glucoseDisplayModeIndex == GlucoseDisplayMode.both.index ||
_settings.glucoseDisplayModeIndex ==
GlucoseDisplayMode.bothForList.index;
_dateFormatController.text = _settings.dateFormat;
_longDateFormatController.text = _settings.longDateFormat ?? '';
_timeFormatController.text = _settings.timeFormat;
_longTimeFormatController.text = _settings.longTimeFormat ?? '';
_showConfirmationDialogOnCancel = _settings.showConfirmationDialogOnCancel;
_showConfirmationDialogOnDelete = _settings.showConfirmationDialogOnDelete;
_showConfirmationDialogOnStopEvent =
_settings.showConfirmationDialogOnStopEvent;
}
@override
void dispose() {
_scrollController.dispose();
_nutritionMeasurementLabelController.dispose();
_glucoseMeasurementLabelController.dispose();
_dateFormatController.dispose();
_longDateFormatController.dispose();
_timeFormatController.dispose();
_longTimeFormatController.dispose();
_insulinIncrementsController.dispose();
_nutritionIncrementsController.dispose();
_mmolPerLIncrementsController.dispose();
_targetGlucoseMgPerDlController.dispose();
_targetGlucoseMmolPerLController.dispose();
super.dispose();
}
void reload({String? message}) {
setState(() {
_settings = Settings.get();
});
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
void saveSettings() {
Settings.put(Settings(
id: _settings.id,
nutritionMeasurementIndex: nutritionMeasurementLabels
.indexOf(_nutritionMeasurementLabelController.text),
glucoseMeasurementIndex: glucoseMeasurementLabels
.indexOf(_glucoseMeasurementLabelController.text),
glucoseDisplayModeIndex: _onlyDisplayActiveGlucoseMeasurement
? GlucoseDisplayMode.activeOnly.index
: _displayBothGlucoseMeasurementsInDetailView &&
_displayBothGlucoseMeasurementsInListView
? GlucoseDisplayMode.both.index
: _displayBothGlucoseMeasurementsInDetailView
? GlucoseDisplayMode.bothForDetail.index
: GlucoseDisplayMode.bothForList.index,
targetGlucoseMgPerDl:
int.tryParse(_targetGlucoseMgPerDlController.text) ?? _settings.targetGlucoseMgPerDl,
targetGlucoseMmolPerL:
double.tryParse(_targetGlucoseMmolPerLController.text) ?? _settings.targetGlucoseMmolPerL,
insulinIncrements:
double.tryParse(_insulinIncrementsController.text) ?? _settings.insulinIncrements,
nutritionIncrements:
double.tryParse(_nutritionIncrementsController.text) ?? _settings.nutritionIncrements,
mmolPerLIncrements:
double.tryParse(_mmolPerLIncrementsController.text) ?? _settings.mmolPerLIncrements,
dateFormat: _dateFormatController.text,
longDateFormat: _longDateFormatController.text,
timeFormat: _timeFormatController.text,
longTimeFormat: _longTimeFormatController.text,
showConfirmationDialogOnCancel: _showConfirmationDialogOnCancel,
showConfirmationDialogOnDelete: _showConfirmationDialogOnDelete,
showConfirmationDialogOnStopEvent: _showConfirmationDialogOnStopEvent,
));
reload(message: 'Settings updated');
}
void onReset() {
Settings.reset();
reload(message: 'Settings have been reset to default');
}
void handleResetAction() async {
DialogUtils.showConfirmationDialog(
context: context,
onConfirm: onReset,
message: 'Are you sure you want to reset all settings?',
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Application Settings'),
),
drawer: const Navigation(currentLocation: SettingsScreen.routeName),
body: SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: GestureDetector(
onTap: () => setState(() {
_measurementsIsExpanded = !_measurementsIsExpanded;
}),
child: Row(
children: [
Expanded(
child: Text(
'MEASUREMENTS',
style: Theme.of(context).textTheme.subtitle2,
),
),
Icon(_measurementsIsExpanded
? Icons.expand_less
: Icons.expand_more),
],
),
),
),
Column(
children: _measurementsIsExpanded
? [
AutoCompleteDropdownButton<String>(
controller: _nutritionMeasurementLabelController,
selectedItem: _nutritionMeasurementLabelController.text,
label: 'Preferred Nutrition Measurement',
items: nutritionMeasurementLabels,
onChanged: (value) {
_nutritionMeasurementLabelController.text =
value ?? '';
saveSettings();
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: AutoCompleteDropdownButton<String>(
controller: _glucoseMeasurementLabelController,
selectedItem: _glucoseMeasurementLabelController.text,
label: 'Preferred Glucose Measurement',
items: glucoseMeasurementLabels,
onChanged: (value) {
_glucoseMeasurementLabelController.text =
value ?? '';
saveSettings();
},
),
),
Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl
? NumberFormField(
label: 'Target glucose',
suffix: 'mg/dl',
controller: _targetGlucoseMgPerDlController,
showSteppers: false,
onChanged: (_) async {
await Future.delayed(
const Duration(seconds: 1));
if (Settings.glucoseMeasurement ==
GlucoseMeasurement.mgPerDl) {
final value = int.tryParse(
_targetGlucoseMgPerDlController.text);
_targetGlucoseMmolPerLController.text = Utils
.toStringMatchingTemplateFractionPrecision(
Utils.convertMgPerDlToMmolPerL(value ?? 0),
Settings.mmolPerLSteps);
await Future.delayed(
const Duration(seconds: 1));
saveSettings();
}
},
)
: NumberFormField(
label: 'Target glucose',
suffix: 'mmol/l',
controller: _targetGlucoseMmolPerLController,
showSteppers: false,
onChanged: (_) async {
await Future.delayed(
const Duration(seconds: 1));
if (Settings.glucoseMeasurement ==
GlucoseMeasurement.mmolPerL) {
final value = double.tryParse(
_targetGlucoseMmolPerLController.text);
_targetGlucoseMgPerDlController.text =
Utils.convertMmolPerLToMgPerDl(value ?? 0)
.toString();
saveSettings();
}
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: NumberFormField(
controller: _insulinIncrementsController,
showSteppers: false,
label: 'Insulin increment',
onChanged: (value) {
_insulinIncrementsController.text =
(value ?? 0).toString();
saveSettings();
}),
),
NumberFormField(
controller: _nutritionIncrementsController,
showSteppers: false,
label: 'Nutrition increment',
onChanged: (value) {
_nutritionIncrementsController.text =
(value ?? 0).toString();
saveSettings();
}),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: NumberFormField(
controller: _mmolPerLIncrementsController,
showSteppers: false,
label: 'Mmol/L increment',
onChanged: (value) {
_mmolPerLIncrementsController.text =
(value ?? 0).toString();
saveSettings();
}),
),
BooleanFormField(
value: _onlyDisplayActiveGlucoseMeasurement,
label: 'only display active glucose measurement',
onChanged: (value) {
_onlyDisplayActiveGlucoseMeasurement = value;
saveSettings();
},
),
BooleanFormField(
value: _displayBothGlucoseMeasurementsInDetailView,
enabled: !_onlyDisplayActiveGlucoseMeasurement,
label:
'display both glucose measurements in detail view',
onChanged: (value) {
_displayBothGlucoseMeasurementsInDetailView = value;
saveSettings();
},
),
BooleanFormField(
value: _displayBothGlucoseMeasurementsInListView,
enabled: !_onlyDisplayActiveGlucoseMeasurement,
label: 'display both glucose measurements in list view',
onChanged: (value) {
_displayBothGlucoseMeasurementsInListView = value;
saveSettings();
},
),
]
: [],
),
const Divider(),
Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: GestureDetector(
onTap: () => setState(() {
_promptsIsExpanded = !_promptsIsExpanded;
}),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Text(
'CONFIRMATION PROMPTS',
style: Theme.of(context).textTheme.subtitle2,
),
),
Icon(_promptsIsExpanded
? Icons.expand_less
: Icons.expand_more),
],
),
),
),
Column(
children: _promptsIsExpanded
? [
BooleanFormField(
value: _showConfirmationDialogOnCancel,
label:
'on cancelling edit or creation of a record if changes have already been made',
onChanged: (value) {
_showConfirmationDialogOnCancel = value;
saveSettings();
},
),
BooleanFormField(
value: _showConfirmationDialogOnDelete,
label: 'on deleting a record',
onChanged: (value) {
_showConfirmationDialogOnDelete = value;
saveSettings();
},
),
BooleanFormField(
value: _showConfirmationDialogOnStopEvent,
label: 'on stopping (ending) an event',
onChanged: (value) {
_showConfirmationDialogOnStopEvent = value;
saveSettings();
},
),
]
: [],
),
const Divider(),
Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: GestureDetector(
onTap: () => setState(() {
_formatIsExpanded = !_formatIsExpanded;
}),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Text(
'TIME & DATE FORMAT',
style: Theme.of(context).textTheme.subtitle2,
),
),
Icon(_formatIsExpanded
? Icons.expand_less
: Icons.expand_more),
],
),
),
),
Column(
children: _formatIsExpanded
? [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: TextFormField(
controller: _dateFormatController,
decoration: const InputDecoration(
labelText: 'Date Format',
),
validator: (value) {
if (value!.trim().isEmpty) {
return 'Empty title';
}
return null;
},
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 5.0, bottom: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Example', textScaleFactor: 0.75),
Text(
DateFormat(_dateFormatController.text)
.format(DateTime.now()),
textScaleFactor: 1.25,
),
],
),
),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: TextFormField(
controller: _longDateFormatController,
decoration: const InputDecoration(
labelText: 'Long Date Format',
),
validator: (value) {
if (value!.trim().isEmpty) {
return 'Empty title';
}
return null;
},
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 5.0, bottom: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Example',
textScaleFactor: 0.75),
Text(
DateFormat(_longDateFormatController.text)
.format(DateTime.now()),
textScaleFactor: 1.25,
),
],
),
),
),
],
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: TextFormField(
controller: _timeFormatController,
decoration: const InputDecoration(
labelText: 'Time Format',
),
validator: (value) {
if (value!.trim().isEmpty) {
return 'Empty title';
}
return null;
},
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 5.0, bottom: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Example', textScaleFactor: 0.75),
Text(
DateFormat(_timeFormatController.text)
.format(DateTime.now()),
textScaleFactor: 1.25,
),
],
),
),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: TextFormField(
controller: _longTimeFormatController,
decoration: const InputDecoration(
labelText: 'Long Time Format',
),
validator: (value) {
if (value!.trim().isEmpty) {
return 'Empty title';
}
return null;
},
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 5.0, bottom: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Example',
textScaleFactor: 0.75),
Text(
DateFormat(_longTimeFormatController.text)
.format(DateTime.now()),
textScaleFactor: 1.25,
),
],
),
),
),
],
),
),
]
: [],
),
],
),
),
bottomNavigationBar: BottomAppBar(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
children: [
ElevatedButton.icon(
onPressed: handleResetAction,
icon: const Icon(
Icons.settings_backup_restore,
size: 18.0,
),
label: const Text('RESET ALL'),
),
const Spacer(),
],
),
),
),
);
}
}