sync server support, additional settings, number field component, usability improvements
This commit is contained in:
parent
6b4f588d5d
commit
09c5230caf
102
TODO
102
TODO
@ -1,53 +1,95 @@
|
||||
MAIN TASKS:
|
||||
General/Framework:
|
||||
☐ create app icon
|
||||
☐ show indicator and make all fields readonly if user somehow gets to a deleted record detail view
|
||||
☐ clean up controllers (dispose method of each stateful widget)
|
||||
☐ account for deleted/disabled elements in dropdowns
|
||||
☐ check through all detail forms and set required fields/according messages
|
||||
Components/Framework:
|
||||
☐ update number fields to use corresponding components
|
||||
☐ meal detail (carbs ratio, portion size, carbs per portion)
|
||||
☐ log meal detail (amount, carbs ratio, portion size, carbs per portion)
|
||||
☐ add "set manually" switch (like in log bolus detail) wherever parameters can be calculated from others
|
||||
☐ meal detail
|
||||
☐ log meal detail
|
||||
☐ come up with new concept for duration component
|
||||
☐ update duration fields to use corresponding component
|
||||
☐ log event type detail (reminder duration)
|
||||
☐ log event detail (reminder duration)
|
||||
☐ meal (bolus delay)
|
||||
☐ log bolus (delay)
|
||||
☐ put dropdowns first if they override name field
|
||||
☐ set name properties as unique (and add checks to forms)
|
||||
☐ implement component for durations
|
||||
☐ check through all detail forms and set required fields/according messages
|
||||
☐ change placement of delete and floating button because its very easy to accidentally hit delete
|
||||
Recipe:
|
||||
✔ recipe list screen @done(21-12-11 22:01)
|
||||
✔ recipe detail screen @done(21-12-11 22:01)
|
||||
☐ add functionality to create a meal from a recipe
|
||||
Event Types:
|
||||
☐ add colors as indicators for log entries (and later graphs in reports)
|
||||
Settings:
|
||||
☐ add setting for decimal places/unit steps
|
||||
☐ add fields for preferred date and time formats
|
||||
☐ add fields for glucose target (as map of cutoff glucose and colors)
|
||||
☐ add field for active insulin duration
|
||||
☐ add setting for carb units/bread units
|
||||
☐ add option to switch 'save' and 'save & close' buttons
|
||||
☐ add functionality to delete dead records (meaning: set deleted flag and no relations to undeleted records)
|
||||
☐ implement deletion by swiping left on item instead?
|
||||
☐ check for changes before navigating as well (not just on cancel)
|
||||
|
||||
FUTURE TASKS:
|
||||
General/Framework:
|
||||
☐ setup objectbox sync server
|
||||
☐ add explanations to each section
|
||||
☐ evaluate if some fields should be readonly instead of completely hidden
|
||||
☐ alternate languages
|
||||
☐ log hba1c
|
||||
Log Overview:
|
||||
☐ only show current day
|
||||
☐ add calendar field on top to navigate
|
||||
☐ use currently selected day when adding a log entry
|
||||
Event Types:
|
||||
☐ add pagination
|
||||
Reports:
|
||||
☐ evaluate what type of reports there should be
|
||||
☐ try out graph/diagram components
|
||||
|
||||
FUTURE TASKS:
|
||||
Features:
|
||||
☐ app icon
|
||||
☐ desktop version
|
||||
☐ add explanations to each section
|
||||
☐ alternate languages
|
||||
☐ log hba1c
|
||||
☐ indicate nested creation process (creating from dropdown etc)
|
||||
☐ enable restoring data from sync
|
||||
☐ indicate read only fields
|
||||
Components/Framework:
|
||||
☐ show indicator and make all fields readonly if user somehow gets to a deleted record detail view
|
||||
☐ dropdown tweaks
|
||||
☐ edit item -> cancel: shouldn't clear dropdwon
|
||||
☐ keep focus on textfield when typing
|
||||
☐ account for deleted/disabled elements
|
||||
Accuracy:
|
||||
☐ same icons in detail as in overview to indicate what's what
|
||||
Recipe:
|
||||
☐ update to use correct components, init/dispose etc
|
||||
☐ change the entire concept of ingredients
|
||||
☐ add functionality to create a meal from a recipe
|
||||
Reports:
|
||||
☐ meal tweaking
|
||||
☐ bolus tweaking
|
||||
☐ basal test
|
||||
☐ daily graph (showing glucose curve, events, boli and meals)
|
||||
Log Overview:
|
||||
☐ add pagination
|
||||
☐ add filters
|
||||
Log Entry:
|
||||
☐ check if there is still an active bolus when suggesting glucose bolus
|
||||
Event Types:
|
||||
☐ add pagination
|
||||
☐ add colors as indicators for log entries (and later graphs in reports)
|
||||
☐ implement reminders as push notifications
|
||||
Settings:
|
||||
☐ add option to hide extra customization options (ie. changing pre calculated values)?
|
||||
☐ option to switch theme
|
||||
☐ add fields for glucose target tiers (as map of cutoff glucose and colors)
|
||||
☐ add field for active insulin duration
|
||||
☐ add setting for carb units/bread units
|
||||
☐ add option to switch 'save' and 'save & close' buttons
|
||||
☐ add functionality to delete dead records (meaning: set deleted flag and no relations to undeleted records)
|
||||
|
||||
Archive:
|
||||
✔ settings (target glucose, increments) @done(22-01-22 01:48) @project(MAIN TASKS.Components/Framework)
|
||||
✔ accuracy detail (confidence rating) @done(22-01-21 16:51) @project(MAIN TASKS.Components/Framework)
|
||||
✔ basal detail (units) @done(22-01-21 18:14) @project(MAIN TASKS.Components/Framework)
|
||||
✔ bolus detail (units, per carbs, per glucose) @done(22-01-21 20:35) @project(MAIN TASKS.Components/Framework)
|
||||
✔ log entry (glucose) @done(22-01-22 15:13) @project(MAIN TASKS.Components/Framework)
|
||||
✔ log bolus detail (units, current, target, correction, carbs) @done(22-01-22 22:59) @project(MAIN TASKS.Components/Framework)
|
||||
✔ add dispose methods everywhere and clean up controllers @done(22-01-21 17:55) @project(MAIN TASKS.Components/Framework)
|
||||
✔ fix spacing @done(22-01-21 17:20) @project(MAIN TASKS.Event Types)
|
||||
✔ calculation log meal carbs @done(22-01-08 22:21) @project(BUG FIXES.Log Entry)
|
||||
✔ implement component for durations @done(22-01-08 19:00) @project(MAIN TASKS.General/Framework)
|
||||
✔ make glucose optional @done(22-01-08 19:00) @project(MAIN TASKS.Log Entry)
|
||||
✔ add setting for decimal places/unit steps @done(22-01-08 22:18) @project(MAIN TASKS.Settings)
|
||||
✔ add fields for preferred date and time formats @done(22-01-07 21:06) @project(MAIN TASKS.Settings)
|
||||
✔ add field for glucose target @done(22-01-08 19:00) @project(MAIN TASKS.Settings)
|
||||
✔ setup objectbox sync server @done(21-12-22 15:21) @project(FUTURE TASKS.General/Framework)
|
||||
✔ recipe list screen @done(21-12-11 22:01) @project(MAIN TASKS.Recipe)
|
||||
✔ recipe detail screen @done(21-12-11 22:01) @project(MAIN TASKS.Recipe)
|
||||
✔ add model for recipe @done(21-12-11 02:23) @project(MAIN TASKS.Recipe)
|
||||
✔ add model for ingredient (relation betweeen recipe and meal) @done(21-12-11 02:23) @project(MAIN TASKS.Recipe)
|
||||
✔ give option to specify quantity @done(21-12-11 01:28) @project(MAIN TASKS.Log Entry)
|
||||
|
@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 31
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.50'
|
||||
ext.kotlin_version = '1.6.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -1,239 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FormWrapper extends StatefulWidget {
|
||||
final List<Widget>? fields;
|
||||
final List<Widget>? buttons;
|
||||
final GlobalKey<FormState>? formState;
|
||||
|
||||
const FormWrapper({Key? key, this.formState, this.fields, this.buttons})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_FormWrapperState createState() => _FormWrapperState();
|
||||
}
|
||||
|
||||
class _FormWrapperState extends State<FormWrapper> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Form(
|
||||
key: widget.formState,
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: widget.fields
|
||||
?.map((e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: e))
|
||||
.toList() ??
|
||||
[],
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: widget.buttons ?? [],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BooleanFormField extends StatefulWidget {
|
||||
final bool value;
|
||||
final String label;
|
||||
final void Function(bool) onChanged;
|
||||
final bool? enabled;
|
||||
final EdgeInsets? contentPadding;
|
||||
|
||||
const BooleanFormField(
|
||||
{Key? key,
|
||||
required this.value,
|
||||
required this.label,
|
||||
required this.onChanged,
|
||||
this.enabled,
|
||||
this.contentPadding})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_BooleanFormFieldState createState() => _BooleanFormFieldState();
|
||||
}
|
||||
|
||||
class _BooleanFormFieldState extends State<BooleanFormField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FormField<bool>(builder: (state) {
|
||||
return ListTile(
|
||||
contentPadding: widget.contentPadding,
|
||||
onTap: () => widget.onChanged(!widget.value),
|
||||
trailing: Switch(
|
||||
value: widget.value,
|
||||
onChanged: widget.onChanged,
|
||||
),
|
||||
title: Text(widget.label),
|
||||
enabled: widget.enabled ?? true,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class DateTimeFormField extends StatefulWidget {
|
||||
final DateTime date;
|
||||
final DateTime? minDate;
|
||||
final DateTime? maxDate;
|
||||
final TextEditingController controller;
|
||||
final String label;
|
||||
final void Function(DateTime?) onChanged;
|
||||
|
||||
const DateTimeFormField(
|
||||
{Key? key,
|
||||
required this.date,
|
||||
this.minDate,
|
||||
this.maxDate,
|
||||
required this.controller,
|
||||
required this.label,
|
||||
required this.onChanged})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_DateTimeFormFieldState createState() => _DateTimeFormFieldState();
|
||||
}
|
||||
|
||||
class _DateTimeFormFieldState extends State<DateTimeFormField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
readOnly: true,
|
||||
controller: widget.controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: widget.label,
|
||||
),
|
||||
onTap: () async {
|
||||
final newTime = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: widget.date,
|
||||
firstDate: widget.minDate ?? DateTime(2000, 1, 1),
|
||||
lastDate:
|
||||
widget.maxDate ?? DateTime.now().add(const Duration(days: 365)),
|
||||
);
|
||||
widget.onChanged(newTime);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimeOfDayFormField extends StatefulWidget {
|
||||
final TimeOfDay time;
|
||||
final TextEditingController controller;
|
||||
final String label;
|
||||
final void Function(TimeOfDay?) onChanged;
|
||||
|
||||
const TimeOfDayFormField(
|
||||
{Key? key,
|
||||
required this.time,
|
||||
required this.controller,
|
||||
required this.label,
|
||||
required this.onChanged})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_TimeOfDayFormFieldState createState() => _TimeOfDayFormFieldState();
|
||||
}
|
||||
|
||||
class _TimeOfDayFormFieldState extends State<TimeOfDayFormField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
readOnly: true,
|
||||
controller: widget.controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: widget.label,
|
||||
),
|
||||
onTap: () async {
|
||||
final newTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: widget.time,
|
||||
);
|
||||
widget.onChanged(newTime);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NumberFormField extends StatefulWidget {
|
||||
final TextEditingController controller;
|
||||
final String label;
|
||||
final String? suffix;
|
||||
final void Function(double?) onChanged;
|
||||
final double? min;
|
||||
final double? max;
|
||||
final double step;
|
||||
|
||||
const NumberFormField(
|
||||
{Key? key,
|
||||
required this.controller,
|
||||
required this.label,
|
||||
this.suffix,
|
||||
required this.onChanged,
|
||||
this.min,
|
||||
this.max,
|
||||
this.step = 1})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_NumberFormFieldState createState() => _NumberFormFieldState();
|
||||
}
|
||||
|
||||
class _NumberFormFieldState extends State<NumberFormField> {
|
||||
void onIncrease() {
|
||||
double value = double.tryParse(widget.controller.text) ?? 0;
|
||||
if (widget.max == null || value + widget.step <= widget.max!) {
|
||||
value += widget.step;
|
||||
widget.onChanged(value);
|
||||
}
|
||||
}
|
||||
|
||||
void onDecrease() {
|
||||
double value = double.tryParse(widget.controller.text) ?? 0;
|
||||
if (widget.min == null || value - widget.step >= widget.min!) {
|
||||
value -= widget.step;
|
||||
widget.onChanged(value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: onDecrease,
|
||||
icon: const Icon(Icons.remove),
|
||||
),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: widget.controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: widget.label,
|
||||
suffixText: widget.suffix,
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
onChanged: (value) async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
widget.onChanged(double.tryParse(value));
|
||||
},
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onIncrease,
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
39
lib/components/forms/boolean_form_field.dart
Normal file
39
lib/components/forms/boolean_form_field.dart
Normal file
@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BooleanFormField extends StatefulWidget {
|
||||
final bool value;
|
||||
final String label;
|
||||
final void Function(bool) onChanged;
|
||||
final bool? enabled;
|
||||
final EdgeInsets? contentPadding;
|
||||
|
||||
const BooleanFormField(
|
||||
{Key? key,
|
||||
required this.value,
|
||||
required this.label,
|
||||
required this.onChanged,
|
||||
this.enabled,
|
||||
this.contentPadding})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_BooleanFormFieldState createState() => _BooleanFormFieldState();
|
||||
}
|
||||
|
||||
class _BooleanFormFieldState extends State<BooleanFormField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FormField<bool>(builder: (state) {
|
||||
return ListTile(
|
||||
contentPadding: widget.contentPadding,
|
||||
onTap: () => widget.onChanged(!widget.value),
|
||||
trailing: Switch(
|
||||
value: widget.value,
|
||||
onChanged: widget.onChanged,
|
||||
),
|
||||
title: Text(widget.label),
|
||||
enabled: widget.enabled ?? true,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
47
lib/components/forms/date_time_form_field.dart
Normal file
47
lib/components/forms/date_time_form_field.dart
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DateTimeFormField extends StatefulWidget {
|
||||
final DateTime date;
|
||||
final DateTime? minDate;
|
||||
final DateTime? maxDate;
|
||||
final TextEditingController controller;
|
||||
final String label;
|
||||
final void Function(DateTime?) onChanged;
|
||||
|
||||
const DateTimeFormField(
|
||||
{Key? key,
|
||||
required this.date,
|
||||
this.minDate,
|
||||
this.maxDate,
|
||||
required this.controller,
|
||||
required this.label,
|
||||
required this.onChanged})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_DateTimeFormFieldState createState() => _DateTimeFormFieldState();
|
||||
}
|
||||
|
||||
class _DateTimeFormFieldState extends State<DateTimeFormField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
readOnly: true,
|
||||
controller: widget.controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: widget.label,
|
||||
),
|
||||
onTap: () async {
|
||||
final newTime = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: widget.date,
|
||||
firstDate: widget.minDate ?? DateTime(2000, 1, 1),
|
||||
lastDate:
|
||||
widget.maxDate ?? DateTime.now().add(const Duration(days: 365)),
|
||||
);
|
||||
widget.onChanged(newTime);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
123
lib/components/forms/duration_form_field.dart
Normal file
123
lib/components/forms/duration_form_field.dart
Normal file
@ -0,0 +1,123 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DurationFormField extends StatefulWidget {
|
||||
final String label;
|
||||
final int minutes;
|
||||
final void Function(int?) onChanged;
|
||||
final bool showSteppers;
|
||||
final bool readOnly;
|
||||
final int min;
|
||||
final int? max;
|
||||
final int step;
|
||||
|
||||
const DurationFormField(
|
||||
{Key? key,
|
||||
required this.label,
|
||||
this.minutes = 0,
|
||||
required this.onChanged,
|
||||
this.showSteppers = false,
|
||||
this.readOnly = false,
|
||||
this.min = 0,
|
||||
this.max,
|
||||
this.step = 5})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_DurationFormFieldState createState() => _DurationFormFieldState();
|
||||
}
|
||||
|
||||
class _DurationFormFieldState extends State<DurationFormField> {
|
||||
late Duration duration;
|
||||
final TextEditingController controller = TextEditingController(text: '');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
updateDuration();
|
||||
}
|
||||
|
||||
void updateDuration() {
|
||||
duration = Duration(minutes: widget.minutes);
|
||||
|
||||
int days = duration.inDays;
|
||||
int hours = duration.inHours - days * 24;
|
||||
int minutes = duration.inMinutes - hours * 60;
|
||||
int seconds = duration.inSeconds - minutes * 60;
|
||||
|
||||
String daysString = days > 9 ? '$days d' : days > 0 ? '0$days d' : '00 d';
|
||||
String hoursString = hours > 9 ? ' $hours h' : hours > 0 ? ' 0$hours h' : ' 00 h';
|
||||
String minutesString = minutes > 9 ? ' $minutes m' : minutes > 0 ? ' 0$minutes m' : ' 00 m';
|
||||
String secondsString = seconds > 9 ? ' $seconds s' : seconds > 0 ? ' 0$seconds s' : ' 00 s';
|
||||
controller.text = '$daysString $hoursString $minutesString $secondsString'.trim();
|
||||
}
|
||||
|
||||
void handleChange(String value) async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
int days = int.tryParse(value.split(' d')[0]) ?? 0;
|
||||
int hours = int.tryParse(value.split('d')[1].split(' h')[0]) ?? 0;
|
||||
int minutes = int.tryParse(value.split('h')[1].split(' m')[0]) ?? 0;
|
||||
int seconds = int.tryParse(value.split('m')[1].split(' s')[0]) ?? 0;
|
||||
int updatedMinutes =
|
||||
Duration(days: days, hours: hours, minutes: minutes, seconds: seconds)
|
||||
.inMinutes;
|
||||
|
||||
widget.onChanged(updatedMinutes);
|
||||
setState(() {
|
||||
updateDuration();
|
||||
});
|
||||
}
|
||||
|
||||
void onIncrease() {
|
||||
if (widget.max == null || widget.minutes + widget.step <= widget.max!) {
|
||||
int value = widget.minutes + widget.step;
|
||||
widget.onChanged(value);
|
||||
setState(() {
|
||||
updateDuration();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void onDecrease() {
|
||||
if (widget.minutes - widget.step >= widget.min) {
|
||||
int value = widget.minutes - widget.step;
|
||||
widget.onChanged(value);
|
||||
setState(() {
|
||||
updateDuration();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
widget.showSteppers
|
||||
? IconButton(
|
||||
onPressed: onDecrease,
|
||||
icon: const Icon(Icons.remove),
|
||||
)
|
||||
: Container(),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: widget.label,
|
||||
),
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
decimal: true, signed: widget.min.isNegative),
|
||||
onChanged: handleChange,
|
||||
),
|
||||
),
|
||||
widget.showSteppers
|
||||
? IconButton(
|
||||
onPressed: onIncrease,
|
||||
icon: const Icon(Icons.add),
|
||||
)
|
||||
: Container(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
45
lib/components/forms/form_wrapper.dart
Normal file
45
lib/components/forms/form_wrapper.dart
Normal file
@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FormWrapper extends StatefulWidget {
|
||||
final List<Widget>? fields;
|
||||
final List<Widget>? buttons;
|
||||
final GlobalKey<FormState>? formState;
|
||||
|
||||
const FormWrapper({Key? key, this.formState, this.fields, this.buttons})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_FormWrapperState createState() => _FormWrapperState();
|
||||
}
|
||||
|
||||
class _FormWrapperState extends State<FormWrapper> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Form(
|
||||
key: widget.formState,
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: widget.fields
|
||||
?.map((e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5.0),
|
||||
child: e))
|
||||
.toList() ??
|
||||
[],
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: widget.buttons ?? [],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
137
lib/components/forms/number_form_field.dart
Normal file
137
lib/components/forms/number_form_field.dart
Normal file
@ -0,0 +1,137 @@
|
||||
import 'package:diameter/components/repeat_on_hold_button.dart';
|
||||
import 'package:diameter/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NumberFormField extends StatefulWidget {
|
||||
final TextEditingController controller;
|
||||
final double min;
|
||||
final double? max;
|
||||
final double step;
|
||||
final String label;
|
||||
final String? suffix;
|
||||
final void Function(double?) onChanged;
|
||||
final bool readOnly;
|
||||
final bool showSteppers;
|
||||
final bool autoRoundToMultipleOfStep;
|
||||
final String? Function(String?)? validator;
|
||||
|
||||
const NumberFormField({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
required this.label,
|
||||
required this.onChanged,
|
||||
this.suffix,
|
||||
this.min = 0,
|
||||
this.max,
|
||||
this.step = 1,
|
||||
this.readOnly = false,
|
||||
this.showSteppers = true,
|
||||
this.autoRoundToMultipleOfStep = false,
|
||||
this.validator,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_NumberFormFieldState createState() => _NumberFormFieldState();
|
||||
}
|
||||
|
||||
class _NumberFormFieldState extends State<NumberFormField> {
|
||||
int precision = 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
precision = Utils.getFractionDigitsLength(widget.step) + 1;
|
||||
}
|
||||
|
||||
bool onIncrease() {
|
||||
double? currentValue = double.tryParse(widget.controller.text);
|
||||
|
||||
if (currentValue != null &&
|
||||
(widget.max == null || currentValue + widget.step <= widget.max!)) {
|
||||
widget.onChanged(
|
||||
Utils.addDoublesWithPrecision(currentValue, widget.step, precision));
|
||||
setState(() {});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onDecrease() {
|
||||
double? currentValue = double.tryParse(widget.controller.text);
|
||||
|
||||
if (currentValue != null && (currentValue - widget.step >= widget.min)) {
|
||||
widget.onChanged(
|
||||
Utils.addDoublesWithPrecision(currentValue, -widget.step, precision));
|
||||
setState(() {});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
widget.showSteppers
|
||||
? RepeatOnHoldButton(
|
||||
onTap: onDecrease,
|
||||
child: IconButton(
|
||||
onPressed: double.tryParse(widget.controller.text) != null &&
|
||||
(double.parse(widget.controller.text) - widget.step >=
|
||||
widget.min)
|
||||
? onDecrease
|
||||
: null,
|
||||
icon: const Icon(Icons.remove),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
readOnly: widget.readOnly,
|
||||
controller: widget.controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: widget.label,
|
||||
suffixText: widget.suffix,
|
||||
),
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
decimal: widget.step > 0 && widget.step < 1,
|
||||
signed: widget.min.isNegative),
|
||||
onChanged: (input) async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
double? value = double.tryParse(input);
|
||||
if (value != null &&
|
||||
widget.autoRoundToMultipleOfStep &&
|
||||
(value % widget.step != 0)) {
|
||||
double remainder = value % widget.step;
|
||||
value =
|
||||
Utils.addDoublesWithPrecision(value, -remainder, precision);
|
||||
if (remainder > widget.step / 2) {
|
||||
value = Utils.addDoublesWithPrecision(
|
||||
value, widget.step, precision);
|
||||
}
|
||||
}
|
||||
widget.onChanged(value);
|
||||
},
|
||||
validator: widget.validator,
|
||||
),
|
||||
),
|
||||
widget.showSteppers
|
||||
? RepeatOnHoldButton(
|
||||
onTap: onIncrease,
|
||||
child: IconButton(
|
||||
onPressed: double.tryParse(widget.controller.text) != null &&
|
||||
(widget.max == null ||
|
||||
double.parse(widget.controller.text) +
|
||||
widget.step <=
|
||||
widget.max!)
|
||||
? onIncrease
|
||||
: null,
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
40
lib/components/forms/time_of_day_form_field.dart
Normal file
40
lib/components/forms/time_of_day_form_field.dart
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TimeOfDayFormField extends StatefulWidget {
|
||||
final TimeOfDay time;
|
||||
final TextEditingController controller;
|
||||
final String label;
|
||||
final void Function(TimeOfDay?) onChanged;
|
||||
|
||||
const TimeOfDayFormField(
|
||||
{Key? key,
|
||||
required this.time,
|
||||
required this.controller,
|
||||
required this.label,
|
||||
required this.onChanged})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_TimeOfDayFormFieldState createState() => _TimeOfDayFormFieldState();
|
||||
}
|
||||
|
||||
class _TimeOfDayFormFieldState extends State<TimeOfDayFormField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
readOnly: true,
|
||||
controller: widget.controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: widget.label,
|
||||
),
|
||||
onTap: () async {
|
||||
final newTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: widget.time,
|
||||
);
|
||||
widget.onChanged(newTime);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
74
lib/components/repeat_on_hold_button.dart
Normal file
74
lib/components/repeat_on_hold_button.dart
Normal file
@ -0,0 +1,74 @@
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RepeatOnHoldButton extends StatefulWidget {
|
||||
/// Function to be called on tap and on long press.
|
||||
/// Return [false] to signify that the loop should be broken after execution.
|
||||
final bool? Function() onTap;
|
||||
|
||||
/// Specifies whether repetition speeds up when the user keeps holding the button.
|
||||
final bool increaseSpeed;
|
||||
|
||||
/// Specifies how many ms should pass before action is repeated.
|
||||
final int initialRepetitionIntervalMs;
|
||||
|
||||
/// Specifies by how much the interval between actions should be divided after [speedUpAfterTimes] times.
|
||||
final int speedUpFactor;
|
||||
|
||||
/// Specifies how many times [onTap] will be called before increasing the speed.
|
||||
final int speedUpAfterTimes;
|
||||
|
||||
final Widget child;
|
||||
|
||||
const RepeatOnHoldButton({
|
||||
Key? key,
|
||||
required this.onTap,
|
||||
this.increaseSpeed = true,
|
||||
this.initialRepetitionIntervalMs = 250,
|
||||
this.speedUpFactor = 2,
|
||||
this.speedUpAfterTimes = 5,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_RepeatOnHoldButtonState createState() => _RepeatOnHoldButtonState();
|
||||
}
|
||||
|
||||
class _RepeatOnHoldButtonState extends State<RepeatOnHoldButton> {
|
||||
bool _isHeld = false;
|
||||
|
||||
void onLongPress() async {
|
||||
setState(() {
|
||||
_isHeld = true;
|
||||
});
|
||||
int holdCycle = 0;
|
||||
int speed = widget.initialRepetitionIntervalMs;
|
||||
|
||||
while (true) {
|
||||
final result = widget.onTap() ?? true;
|
||||
if (!_isHeld || !result) {
|
||||
break;
|
||||
}
|
||||
|
||||
holdCycle++;
|
||||
if (speed > 1 && holdCycle % widget.speedUpAfterTimes == 0) {
|
||||
speed = max(1, (speed ~/ widget.speedUpFactor));
|
||||
}
|
||||
|
||||
await Future.delayed(
|
||||
Duration(
|
||||
milliseconds: speed,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onLongPress: onLongPress,
|
||||
onLongPressEnd: (_) => _isHeld = false,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ Future<void> main() async {
|
||||
Sync.isAvailable();
|
||||
SyncClient syncClient = Sync.client(
|
||||
objectBox.store,
|
||||
'wss://127.0.0.1:9999',
|
||||
'ws://192.168.1.184:9999',
|
||||
SyncCredentials.sharedSecretString(secret)
|
||||
);
|
||||
syncClient.start();
|
||||
|
@ -53,6 +53,11 @@ class Settings {
|
||||
int targetGlucoseMgPerDl;
|
||||
double targetGlucoseMmolPerL;
|
||||
|
||||
double insulinIncrements;
|
||||
double nutritionIncrements;
|
||||
double mmolPerLIncrements;
|
||||
double amountIncrements;
|
||||
|
||||
String dateFormat;
|
||||
String? longDateFormat;
|
||||
String timeFormat;
|
||||
@ -70,6 +75,10 @@ class Settings {
|
||||
this.nutritionMeasurementIndex = 0,
|
||||
this.glucoseDisplayModeIndex = 0,
|
||||
this.glucoseMeasurementIndex = 0,
|
||||
this.insulinIncrements = 0.05,
|
||||
this.nutritionIncrements = 0.01,
|
||||
this.mmolPerLIncrements = 0.1,
|
||||
this.amountIncrements = 0.05,
|
||||
this.dateFormat = 'MM/dd/yy',
|
||||
this.longDateFormat = 'MMMM dd, yyyy',
|
||||
this.timeFormat = 'HH:mm',
|
||||
@ -78,7 +87,7 @@ class Settings {
|
||||
this.showConfirmationDialogOnDelete = true,
|
||||
this.showConfirmationDialogOnStopEvent = true,
|
||||
this.targetGlucoseMgPerDl = 100,
|
||||
this.targetGlucoseMmolPerL = 5.49,
|
||||
this.targetGlucoseMmolPerL = 5.5,
|
||||
this.useDarkTheme = false,
|
||||
});
|
||||
|
||||
@ -105,6 +114,10 @@ class Settings {
|
||||
static int get targetMgPerDl => get().targetGlucoseMgPerDl;
|
||||
static double get targetMmolPerL => get().targetGlucoseMmolPerL;
|
||||
|
||||
static double get insulinSteps => get().insulinIncrements;
|
||||
static double get nutritionSteps => get().nutritionIncrements;
|
||||
static double get mmolPerLSteps => get().mmolPerLIncrements;
|
||||
|
||||
static ThemeMode get themeMode =>
|
||||
get().useDarkTheme ? ThemeMode.dark : ThemeMode.light;
|
||||
|
||||
|
@ -113,14 +113,14 @@ class _NavigationState extends State<Navigation> {
|
||||
},
|
||||
selected: widget.currentLocation == Routes.events,
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Recipes'),
|
||||
leading: const Icon(Icons.local_dining),
|
||||
onTap: () {
|
||||
selectDestination(Routes.recipes);
|
||||
},
|
||||
selected: Routes.recipeRoutes.contains(widget.currentLocation),
|
||||
),
|
||||
// ListTile(
|
||||
// title: const Text('Recipes'),
|
||||
// leading: const Icon(Icons.local_dining),
|
||||
// onTap: () {
|
||||
// selectDestination(Routes.recipes);
|
||||
// },
|
||||
// selected: Routes.recipeRoutes.contains(widget.currentLocation),
|
||||
// ),
|
||||
ListTile(
|
||||
title: const Text('Meals'),
|
||||
leading: const Icon(Icons.dinner_dining),
|
||||
|
@ -1,9 +1,11 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.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/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/accuracy.dart';
|
||||
|
||||
class AccuracyDetailScreen extends StatefulWidget {
|
||||
@ -25,8 +27,9 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
final _valueController = TextEditingController(text: '');
|
||||
final _confidenceRatingController = TextEditingController(text: '');
|
||||
final _notesController = TextEditingController(text: '');
|
||||
final _confidenceRatingController =
|
||||
TextEditingController(text: Accuracy.getAll().length.toString());
|
||||
bool _forCarbsRatio = true;
|
||||
bool _forPortionSize = true;
|
||||
|
||||
@ -39,11 +42,20 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
|
||||
_forCarbsRatio = _accuracy!.forCarbsRatio;
|
||||
_forPortionSize = _accuracy!.forPortionSize;
|
||||
_confidenceRatingController.text =
|
||||
(_accuracy!.confidenceRating ?? '').toString();
|
||||
(_accuracy!.confidenceRating ?? Accuracy.getAll().length).toString();
|
||||
_notesController.text = _accuracy!.notes ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_valueController.dispose();
|
||||
_notesController.dispose();
|
||||
_confidenceRatingController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -52,7 +64,7 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
|
||||
}
|
||||
_isNew = _accuracy == null;
|
||||
|
||||
setState(() {
|
||||
setState(() {
|
||||
if (message != null) {
|
||||
var snackBar = SnackBar(
|
||||
content: Text(message),
|
||||
@ -77,10 +89,11 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
|
||||
forPortionSize: _forPortionSize,
|
||||
notes: _notesController.text,
|
||||
);
|
||||
Accuracy.box.put(accuracy);
|
||||
Accuracy.put(accuracy);
|
||||
Accuracy.reorder(
|
||||
accuracy, int.tryParse(_confidenceRatingController.text));
|
||||
Navigator.pop(context, ['${_isNew ? 'New' : ''} Accuracy saved', accuracy]);
|
||||
Navigator.pop(
|
||||
context, ['${_isNew ? 'New' : ''} Accuracy saved', accuracy]);
|
||||
}
|
||||
setState(() {
|
||||
_isSaving = false;
|
||||
@ -93,7 +106,8 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
|
||||
(!_forCarbsRatio ||
|
||||
!_forPortionSize ||
|
||||
_valueController.text != '' ||
|
||||
int.tryParse(_confidenceRatingController.text) != null ||
|
||||
int.tryParse(_confidenceRatingController.text) !=
|
||||
Accuracy.getAll().length ||
|
||||
_notesController.text != '')) ||
|
||||
(!_isNew &&
|
||||
(_forCarbsRatio != _accuracy!.forCarbsRatio ||
|
||||
@ -102,7 +116,7 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
|
||||
int.tryParse(_confidenceRatingController.text) !=
|
||||
_accuracy!.confidenceRating ||
|
||||
(_accuracy!.notes ?? '') != _notesController.text))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
@ -159,12 +173,15 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
|
||||
});
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
NumberFormField(
|
||||
controller: _confidenceRatingController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Confidence Rating',
|
||||
),
|
||||
label: 'Confidence Rating',
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_confidenceRatingController.text =
|
||||
(value ?? 0).toInt().toString();
|
||||
});
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
controller: _notesController,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/screens/accuracy_detail.dart';
|
||||
@ -24,6 +24,12 @@ class _AccuracyListScreenState extends State<AccuracyListScreen> {
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
setState(() {
|
||||
_accuracies = Accuracy.getAll();
|
||||
@ -49,7 +55,7 @@ class _AccuracyListScreenState extends State<AccuracyListScreen> {
|
||||
|
||||
void handleDeleteAction(Accuracy accuracy) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(accuracy),
|
||||
message: 'Are you sure you want to delete this Accuracy?',
|
||||
@ -87,77 +93,77 @@ class _AccuracyListScreenState extends State<AccuracyListScreen> {
|
||||
Expanded(
|
||||
child: _accuracies.isNotEmpty
|
||||
? Scrollbar(
|
||||
controller: _scrollController,
|
||||
child: ReorderableListView.builder(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
scrollController: _scrollController,
|
||||
itemCount: _accuracies.length,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
Accuracy.reorder(_accuracies[oldIndex], newIndex);
|
||||
reload();
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
final accuracy = _accuracies[index];
|
||||
return Card(
|
||||
key: Key(accuracy.id.toString()),
|
||||
child: ListTile(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
AccuracyDetailScreen(id: accuracy.id),
|
||||
),
|
||||
).then((result) => reload(message: result?[0]));
|
||||
},
|
||||
title: Text(
|
||||
accuracy.value.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.subtitle2,
|
||||
controller: _scrollController,
|
||||
child: ReorderableListView.builder(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
scrollController: _scrollController,
|
||||
itemCount: _accuracies.length,
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
Accuracy.reorder(_accuracies[oldIndex], newIndex);
|
||||
reload();
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
final accuracy = _accuracies[index];
|
||||
return Card(
|
||||
key: Key(accuracy.id.toString()),
|
||||
child: ListTile(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
AccuracyDetailScreen(id: accuracy.id),
|
||||
),
|
||||
leading: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Icon(Icons.reorder),
|
||||
],
|
||||
).then((result) => reload(message: result?[0]));
|
||||
},
|
||||
title: Text(
|
||||
accuracy.value.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.subtitle2,
|
||||
),
|
||||
leading: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Icon(Icons.reorder),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.square_foot,
|
||||
color: accuracy.forPortionSize
|
||||
? Theme.of(context)
|
||||
.toggleableActiveColor
|
||||
: Theme.of(context).highlightColor,
|
||||
),
|
||||
onPressed: () =>
|
||||
handleToggleForPortionSizeAction(
|
||||
accuracy),
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.square_foot,
|
||||
color: accuracy.forPortionSize
|
||||
? Theme.of(context)
|
||||
.toggleableActiveColor
|
||||
: Theme.of(context).highlightColor,
|
||||
),
|
||||
onPressed: () =>
|
||||
handleToggleForPortionSizeAction(
|
||||
accuracy),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.pie_chart,
|
||||
color: accuracy.forCarbsRatio
|
||||
? Theme.of(context)
|
||||
.toggleableActiveColor
|
||||
: Theme.of(context).highlightColor,
|
||||
),
|
||||
onPressed: () =>
|
||||
handleToggleForCarbsRatioAction(
|
||||
accuracy),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () =>
|
||||
handleDeleteAction(accuracy),
|
||||
)
|
||||
],
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.pie_chart,
|
||||
color: accuracy.forCarbsRatio
|
||||
? Theme.of(context)
|
||||
.toggleableActiveColor
|
||||
: Theme.of(context).highlightColor,
|
||||
),
|
||||
onPressed: () =>
|
||||
handleToggleForCarbsRatioAction(
|
||||
accuracy),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () =>
|
||||
handleDeleteAction(accuracy),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
)
|
||||
: const Center(
|
||||
child: Text('You have not created any Accuracies yet!'),
|
||||
),
|
||||
|
@ -1,10 +1,13 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/forms/number_form_field.dart';
|
||||
import 'package:diameter/components/forms/time_of_day_form_field.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/utils/date_time_utils.dart';
|
||||
import 'package:diameter/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/basal.dart';
|
||||
import 'package:diameter/models/basal_profile.dart';
|
||||
|
||||
@ -42,7 +45,8 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
|
||||
|
||||
final _startTimeController = TextEditingController(text: '');
|
||||
final _endTimeController = TextEditingController(text: '');
|
||||
final _unitsController = TextEditingController(text: '');
|
||||
final _unitsController =
|
||||
TextEditingController(text: 0.toStringAsPrecision(3));
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -59,13 +63,22 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
|
||||
if (_basal != null) {
|
||||
_startTime = TimeOfDay.fromDateTime(_basal!.startTime);
|
||||
_endTime = TimeOfDay.fromDateTime(_basal!.endTime);
|
||||
_unitsController.text = _basal!.units.toString();
|
||||
_unitsController.text = _basal!.units.toStringAsPrecision(3);
|
||||
}
|
||||
|
||||
_startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime);
|
||||
_endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_startTimeController.dispose();
|
||||
_endTimeController.dispose();
|
||||
_unitsController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -207,13 +220,13 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
|
||||
_startTime.minute !=
|
||||
(widget.suggestedStartTime?.minute ?? 0) ||
|
||||
_endTime.minute != (widget.suggestedEndTime?.minute ?? 0) ||
|
||||
double.tryParse(_unitsController.text) != null)) ||
|
||||
double.tryParse(_unitsController.text) != 0)) ||
|
||||
(!_isNew &&
|
||||
(TimeOfDay.fromDateTime(_basal!.startTime) != _startTime ||
|
||||
TimeOfDay.fromDateTime(_basal!.endTime) != _endTime ||
|
||||
(double.tryParse(_unitsController.text) ?? 0) !=
|
||||
double.tryParse(_unitsController.text) !=
|
||||
_basal!.units)))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
@ -266,21 +279,19 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
TextFormField(
|
||||
controller: _unitsController,
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(decimal: true),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Units',
|
||||
suffixText: 'U',
|
||||
),
|
||||
validator: (value) {
|
||||
if (value!.trim().isEmpty) {
|
||||
return 'Empty amount of units';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
NumberFormField(
|
||||
controller: _unitsController,
|
||||
label: 'Units',
|
||||
suffix: 'U',
|
||||
autoRoundToMultipleOfStep: true,
|
||||
step: Settings.insulinSteps,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
_unitsController.text =
|
||||
Utils.toStringMatchingTemplateFractionPrecision(
|
||||
value, Settings.insulinSteps);
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/utils/date_time_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -25,6 +25,12 @@ class BasalListScreen extends StatefulWidget {
|
||||
class _BasalListScreenState extends State<BasalListScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
widget.reload();
|
||||
|
||||
@ -60,7 +66,7 @@ class _BasalListScreenState extends State<BasalListScreen> {
|
||||
|
||||
void handleDeleteAction(Basal basal) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(basal),
|
||||
message: 'Are you sure you want to delete this Basal Rate?',
|
||||
@ -106,70 +112,76 @@ class _BasalListScreenState extends State<BasalListScreen> {
|
||||
.isNotEmpty) {
|
||||
return 'This rate\'s time period overlaps with another one';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.basalRates.isNotEmpty ? Scrollbar(
|
||||
controller: _scrollController,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
controller: _scrollController,
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.basalRates.length,
|
||||
itemBuilder: (context, index) {
|
||||
final basal = widget.basalRates[index];
|
||||
final error = validateTimePeriod(index);
|
||||
return Card(
|
||||
child: Column(
|
||||
children: [
|
||||
error != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.warning, color: Theme.of(context).errorColor),
|
||||
Text(
|
||||
error, style: TextStyle(color: Theme.of(context).errorColor)
|
||||
),
|
||||
],
|
||||
),
|
||||
) : Container(),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
handleEditAction(basal);
|
||||
},
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
return widget.basalRates.isNotEmpty
|
||||
? Scrollbar(
|
||||
controller: _scrollController,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
controller: _scrollController,
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.basalRates.length,
|
||||
itemBuilder: (context, index) {
|
||||
final basal = widget.basalRates[index];
|
||||
final error = validateTimePeriod(index);
|
||||
return Card(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${DateTimeUtils.displayTime(basal.startTime)} - ${DateTimeUtils.displayTime(basal.endTime)}')),
|
||||
const Spacer(),
|
||||
Expanded(child: Text('${basal.units} U')),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.delete,
|
||||
color: Colors.blue,
|
||||
error != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.warning,
|
||||
color: Theme.of(context).errorColor),
|
||||
Text(error,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).errorColor)),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
handleEditAction(basal);
|
||||
},
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${DateTimeUtils.displayTime(basal.startTime)} - ${DateTimeUtils.displayTime(basal.endTime)}')),
|
||||
const Spacer(),
|
||||
Expanded(child: Text('${basal.units} U')),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.delete,
|
||||
color: Colors.blue,
|
||||
),
|
||||
onPressed: () => handleDeleteAction(basal),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () => handleDeleteAction(basal),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: const Center(
|
||||
child: Text('You have not created any Basal Rates yet!'),
|
||||
);
|
||||
},
|
||||
),
|
||||
) : const Center(
|
||||
child: Text('You have not created any Basal Rates yet!'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/forms/boolean_form_field.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/basal.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/screens/basal/basal_detail.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/basal_profile.dart';
|
||||
import 'package:diameter/screens/basal/basal_list.dart';
|
||||
|
||||
@ -92,6 +93,14 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
|
||||
bottomNav = detailBottomRow;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_nameController.dispose();
|
||||
_notesController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -270,7 +279,7 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
|
||||
(_basalProfile!.active != _active ||
|
||||
_basalProfile!.name != _nameController.text ||
|
||||
(_basalProfile!.notes ?? '') != _notesController.text))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/dropdown.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
|
||||
import 'package:diameter/models/basal.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
@ -17,13 +17,25 @@ class BasalProfileListScreen extends StatefulWidget {
|
||||
|
||||
class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
|
||||
late List<BasalProfile> _basalProfiles;
|
||||
Widget banner = Container();
|
||||
|
||||
final BasalProfile? _activeProfile = BasalProfile.getActive(DateTime.now());
|
||||
|
||||
void refresh({String? message}) {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
setState(() {
|
||||
_basalProfiles = BasalProfile.getAll();
|
||||
});
|
||||
@ -75,14 +87,35 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
void handleDuplicateAction(BasalProfile basalProfile) async {
|
||||
final copy = BasalProfile(
|
||||
active: false,
|
||||
name: 'Copy of ${basalProfile.name}',
|
||||
);
|
||||
BasalProfile.put(copy);
|
||||
|
||||
final rates = Basal.getAllForProfile(basalProfile.id);
|
||||
for (Basal rate in rates) {
|
||||
final basal = Basal(
|
||||
endTime: rate.endTime,
|
||||
startTime: rate.startTime,
|
||||
units: rate.units,
|
||||
);
|
||||
basal.basalProfile.target = copy;
|
||||
Basal.put(basal);
|
||||
}
|
||||
|
||||
reload(message: 'Added copy of ${basalProfile.name}');
|
||||
}
|
||||
|
||||
void onDelete(BasalProfile basalProfile) {
|
||||
BasalProfile.remove(basalProfile.id);
|
||||
refresh(message: 'Basal Profile deleted');
|
||||
reload(message: 'Basal Profile deleted');
|
||||
}
|
||||
|
||||
void handleDeleteAction(BasalProfile basalProfile) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(basalProfile),
|
||||
message: 'Are you sure you want to delete this Basal Profile?',
|
||||
@ -97,7 +130,7 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
|
||||
BasalProfile.setAllInactive;
|
||||
basalProfile.active = true;
|
||||
BasalProfile.put(basalProfile);
|
||||
refresh(
|
||||
reload(
|
||||
message: '${basalProfile.name} has been set as your active Profile');
|
||||
}
|
||||
}
|
||||
@ -130,7 +163,7 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
|
||||
builder: (context) =>
|
||||
BasalProfileDetailScreen(id: basalProfile?.id ?? 0, active: active),
|
||||
),
|
||||
).then((result) => refresh(message: result?[0]));
|
||||
).then((result) => reload(message: result?[0]));
|
||||
}
|
||||
|
||||
void onNew(bool active) {
|
||||
@ -141,19 +174,13 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
|
||||
showDetailScreen(basalProfile: basalProfile);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
refresh();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Basal Profiles'),
|
||||
actions: <Widget>[
|
||||
IconButton(onPressed: refresh, icon: const Icon(Icons.refresh))
|
||||
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
|
||||
],
|
||||
),
|
||||
drawer:
|
||||
@ -165,8 +192,8 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
|
||||
Expanded(
|
||||
child: _basalProfiles.isNotEmpty
|
||||
? Scrollbar(
|
||||
controller: _scrollController,
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
controller: _scrollController,
|
||||
itemCount: _basalProfiles.length,
|
||||
@ -186,22 +213,23 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
|
||||
basalProfile.id == _activeProfile?.id,
|
||||
onTap: () => onEdit(basalProfile),
|
||||
title: Text(
|
||||
basalProfile.name.toUpperCase() + activeProfileText,
|
||||
basalProfile.name.toUpperCase() +
|
||||
activeProfileText,
|
||||
style: Theme.of(context).textTheme.subtitle2,
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
basalProfile.notes ?? ''
|
||||
),
|
||||
Text(basalProfile.notes ?? ''),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: dailyTotal > 0
|
||||
? [
|
||||
Text(dailyTotal.toStringAsPrecision(3)),
|
||||
const Text('U/day', textScaleFactor: 0.75),
|
||||
Text(dailyTotal
|
||||
.toStringAsPrecision(3)),
|
||||
const Text('U/day',
|
||||
textScaleFactor: 0.75),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
@ -212,12 +240,21 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.copy,
|
||||
color: Colors.blue,
|
||||
),
|
||||
onPressed: () =>
|
||||
handleDuplicateAction(basalProfile),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.delete,
|
||||
color: Colors.blue,
|
||||
),
|
||||
onPressed: () => handleDeleteAction(basalProfile),
|
||||
onPressed: () =>
|
||||
handleDeleteAction(basalProfile),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -225,7 +262,7 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
: const Center(
|
||||
child: Text('You have not created any Basal Profiles yet!'),
|
||||
),
|
||||
|
@ -1,11 +1,13 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/forms/number_form_field.dart';
|
||||
import 'package:diameter/components/forms/time_of_day_form_field.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/utils/date_time_utils.dart';
|
||||
import 'package:diameter/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/bolus.dart';
|
||||
import 'package:diameter/models/bolus_profile.dart';
|
||||
|
||||
@ -43,10 +45,10 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
|
||||
|
||||
final _startTimeController = TextEditingController(text: '');
|
||||
final _endTimeController = TextEditingController(text: '');
|
||||
final _unitsController = TextEditingController(text: '');
|
||||
final _carbsController = TextEditingController(text: '');
|
||||
final _mgPerDlController = TextEditingController(text: '');
|
||||
final _mmolPerLController = TextEditingController(text: '');
|
||||
final _unitsController = TextEditingController(text: Utils.toStringMatchingTemplateFractionPrecision(0, Settings.insulinSteps));
|
||||
final _carbsController = TextEditingController(text: Utils.toStringMatchingTemplateFractionPrecision(0, Settings.nutritionSteps));
|
||||
final _mgPerDlController = TextEditingController(text: '0');
|
||||
final _mmolPerLController = TextEditingController(text: Utils.toStringMatchingTemplateFractionPrecision(0, Settings.mmolPerLSteps));
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -74,6 +76,18 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
|
||||
_endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_startTimeController.dispose();
|
||||
_endTimeController.dispose();
|
||||
_unitsController.dispose();
|
||||
_carbsController.dispose();
|
||||
_mgPerDlController.dispose();
|
||||
_mmolPerLController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -196,7 +210,8 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
|
||||
).then((result) {
|
||||
Navigator.pop(
|
||||
context,
|
||||
['New Bolus Rate${result[1] != null ? 's' : ''} saved', bolus] + [result[1]],
|
||||
['New Bolus Rate${result[1] != null ? 's' : ''} saved', bolus] +
|
||||
[result[1]],
|
||||
);
|
||||
});
|
||||
} else {
|
||||
@ -235,7 +250,7 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
|
||||
_bolus!.mgPerDl ||
|
||||
(double.tryParse(_mmolPerLController.text) ?? 0) !=
|
||||
_bolus!.mmolPerL)))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
@ -245,30 +260,26 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
void convertBetweenMgPerDlAndMmolPerL({GlucoseMeasurement? calculateFrom}) {
|
||||
int? mgPerDl;
|
||||
double? mmolPerL;
|
||||
|
||||
if (calculateFrom != GlucoseMeasurement.mmolPerL &&
|
||||
_mgPerDlController.text != '') {
|
||||
mgPerDl = int.tryParse(_mgPerDlController.text);
|
||||
}
|
||||
if (calculateFrom != GlucoseMeasurement.mgPerDl &&
|
||||
_mmolPerLController.text != '') {
|
||||
mmolPerL = double.tryParse(_mmolPerLController.text);
|
||||
}
|
||||
|
||||
if (mgPerDl != null && mmolPerL == null) {
|
||||
setState(() {
|
||||
void convertBetweenMgPerDlAndMmolPerL(double? value) async {
|
||||
if (value != null) {
|
||||
if (Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl &&
|
||||
_mgPerDlController.text != '') {
|
||||
_mgPerDlController.text = value.toInt().toString();
|
||||
setState(() {
|
||||
_mmolPerLController.text =
|
||||
Utils.convertMgPerDlToMmolPerL(value.toInt()).toString();
|
||||
});
|
||||
}
|
||||
if (Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL &&
|
||||
_mmolPerLController.text != '') {
|
||||
_mmolPerLController.text =
|
||||
Utils.convertMgPerDlToMmolPerL(mgPerDl!).toString();
|
||||
});
|
||||
}
|
||||
if (mmolPerL != null && mgPerDl == null) {
|
||||
setState(() {
|
||||
_mgPerDlController.text =
|
||||
Utils.convertMmolPerLToMgPerDl(mmolPerL!).toString();
|
||||
});
|
||||
Utils.toStringMatchingTemplateFractionPrecision(
|
||||
value, Settings.mmolPerLSteps);
|
||||
setState(() {
|
||||
_mgPerDlController.text =
|
||||
Utils.convertMmolPerLToMgPerDl(value.toDouble()).toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,34 +326,32 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Units',
|
||||
suffixText: 'U',
|
||||
),
|
||||
NumberFormField(
|
||||
controller: _unitsController,
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(decimal: true),
|
||||
validator: (value) {
|
||||
if (value!.trim().isEmpty) {
|
||||
return 'Empty amount of units';
|
||||
label: 'Units',
|
||||
suffix: 'U',
|
||||
autoRoundToMultipleOfStep: true,
|
||||
step: Settings.insulinSteps,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
_unitsController.text =
|
||||
Utils.toStringMatchingTemplateFractionPrecision(
|
||||
value, Settings.insulinSteps);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'per carbs',
|
||||
suffixText: Settings.nutritionMeasurementSuffix,
|
||||
),
|
||||
NumberFormField(
|
||||
controller: _carbsController,
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(decimal: true),
|
||||
validator: (value) {
|
||||
if (value!.trim().isEmpty) {
|
||||
return 'How many carbs does the rate make up for?';
|
||||
label: 'per carbs',
|
||||
suffix: Settings.nutritionMeasurementSuffix,
|
||||
autoRoundToMultipleOfStep: true,
|
||||
step: Settings.nutritionSteps,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
_carbsController.text =
|
||||
Utils.toStringMatchingTemplateFractionPrecision(
|
||||
value, Settings.nutritionSteps);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
Row(
|
||||
@ -354,42 +363,18 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
|
||||
Settings.glucoseDisplayMode ==
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
? Expanded(
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'per mg/dl',
|
||||
suffixText: 'mg/dl',
|
||||
),
|
||||
flex: Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl ? 2 : 1,
|
||||
child: NumberFormField(
|
||||
label: 'per mg/dl',
|
||||
suffix: 'mg/dl',
|
||||
readOnly: Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mmolPerL,
|
||||
showSteppers: Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl,
|
||||
controller: _mgPerDlController,
|
||||
onChanged: (_) async {
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 1));
|
||||
convertBetweenMgPerDlAndMmolPerL(
|
||||
calculateFrom:
|
||||
GlucoseMeasurement.mgPerDl);
|
||||
},
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(),
|
||||
validator: (value) {
|
||||
if (value!.trim().isEmpty &&
|
||||
_mmolPerLController.text.trim().isEmpty) {
|
||||
return 'How many mg/dl does the rate make up for?';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: convertBetweenMgPerDlAndMmolPerL,
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
Settings.glucoseDisplayMode == GlucoseDisplayMode.both ||
|
||||
Settings.glucoseDisplayMode ==
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
? IconButton(
|
||||
onPressed: () => convertBetweenMgPerDlAndMmolPerL(
|
||||
calculateFrom: GlucoseMeasurement.mmolPerL),
|
||||
icon: const Icon(Icons.calculate),
|
||||
)
|
||||
: Container(),
|
||||
Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mmolPerL ||
|
||||
[
|
||||
@ -397,44 +382,19 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
].contains(Settings.glucoseDisplayMode)
|
||||
? Expanded(
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'per mmol/l',
|
||||
suffixText: 'mmol/l',
|
||||
),
|
||||
flex: Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL ? 2 : 1,
|
||||
child: NumberFormField(
|
||||
label: 'per mmol/l',
|
||||
suffix: 'mmol/l',
|
||||
readOnly: Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mgPerDl,
|
||||
showSteppers: Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL,
|
||||
controller: _mmolPerLController,
|
||||
onChanged: (_) async {
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 1));
|
||||
convertBetweenMgPerDlAndMmolPerL(
|
||||
calculateFrom:
|
||||
GlucoseMeasurement.mmolPerL);
|
||||
},
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(
|
||||
decimal: true),
|
||||
validator: (value) {
|
||||
if (value!.trim().isEmpty &&
|
||||
_mgPerDlController.text.trim().isEmpty) {
|
||||
return 'How many mmol/l does rhe rate make up for?';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
step: Settings.mmolPerLSteps,
|
||||
onChanged: convertBetweenMgPerDlAndMmolPerL,
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
[
|
||||
GlucoseDisplayMode.both,
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
].contains(Settings.glucoseDisplayMode)
|
||||
? IconButton(
|
||||
onPressed: () => convertBetweenMgPerDlAndMmolPerL(
|
||||
calculateFrom: GlucoseMeasurement.mgPerDl),
|
||||
icon: const Icon(Icons.calculate),
|
||||
)
|
||||
: Container(),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/utils/date_time_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -12,7 +12,10 @@ class BolusListScreen extends StatefulWidget {
|
||||
final Function() reload;
|
||||
|
||||
const BolusListScreen(
|
||||
{Key? key, required this.bolusProfile, this.bolusRates = const [], required this.reload})
|
||||
{Key? key,
|
||||
required this.bolusProfile,
|
||||
this.bolusRates = const [],
|
||||
required this.reload})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -22,6 +25,12 @@ class BolusListScreen extends StatefulWidget {
|
||||
class _BolusListScreenState extends State<BolusListScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
widget.reload();
|
||||
|
||||
@ -57,7 +66,7 @@ class _BolusListScreenState extends State<BolusListScreen> {
|
||||
|
||||
void handleDeleteAction(Bolus bolus) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(bolus),
|
||||
message: 'Are you sure you want to delete this Bolus Rate?',
|
||||
@ -102,100 +111,122 @@ class _BolusListScreenState extends State<BolusListScreen> {
|
||||
.isNotEmpty) {
|
||||
return 'This rate\'s time period overlaps with another one';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.bolusRates.isNotEmpty ? Scrollbar(
|
||||
controller: _scrollController,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
controller: _scrollController,
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.bolusRates.length,
|
||||
itemBuilder: (context, index) {
|
||||
final bolus = widget.bolusRates[index];
|
||||
final error = validateTimePeriod(index);
|
||||
return Card(
|
||||
child: Column(
|
||||
children: [
|
||||
error != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.warning, color: Theme.of(context).errorColor),
|
||||
Text(
|
||||
error, style: TextStyle(color: Theme.of(context).errorColor)
|
||||
),
|
||||
],
|
||||
),
|
||||
) : Container(),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
handleEditAction(bolus);
|
||||
},
|
||||
isThreeLine: true,
|
||||
title: Text('${DateTimeUtils.displayTime(bolus.startTime)} - ${DateTimeUtils.displayTime(bolus.endTime)}'),
|
||||
subtitle: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return widget.bolusRates.isNotEmpty
|
||||
? Scrollbar(
|
||||
controller: _scrollController,
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
controller: _scrollController,
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.bolusRates.length,
|
||||
itemBuilder: (context, index) {
|
||||
final bolus = widget.bolusRates[index];
|
||||
final error = validateTimePeriod(index);
|
||||
return Card(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: (bolus.units > 0 && bolus.carbs > 0)
|
||||
? [
|
||||
Text((bolus.carbs / bolus.units).toStringAsPrecision(2)),
|
||||
Text('${Settings.nutritionMeasurementSuffix} carbs per U',
|
||||
textAlign: TextAlign.center, textScaleFactor: 0.75),
|
||||
]
|
||||
: [],
|
||||
error != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.warning,
|
||||
color: Theme.of(context).errorColor),
|
||||
Text(error,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).errorColor)),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
handleEditAction(bolus);
|
||||
},
|
||||
isThreeLine: true,
|
||||
title: Text(
|
||||
'${DateTimeUtils.displayTime(bolus.startTime)} - ${DateTimeUtils.displayTime(bolus.endTime)}'),
|
||||
subtitle: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: (bolus.units > 0 && bolus.carbs > 0)
|
||||
? [
|
||||
Text((bolus.carbs / bolus.units)
|
||||
.toStringAsPrecision(2)),
|
||||
Text(
|
||||
'${Settings.nutritionMeasurementSuffix} carbs per U',
|
||||
textAlign: TextAlign.center,
|
||||
textScaleFactor: 0.75),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: (bolus.units > 0 && bolus.carbs > 0)
|
||||
? [
|
||||
Text((bolus.units / bolus.carbs * 12)
|
||||
.toStringAsPrecision(2)),
|
||||
const Text('U per bread unit',
|
||||
textAlign: TextAlign.center,
|
||||
textScaleFactor: 0.75),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: (bolus.units > 0 &&
|
||||
(bolus.mgPerDl ?? bolus.mmolPerL ?? 0) >
|
||||
0)
|
||||
? [
|
||||
Text((((Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement
|
||||
.mgPerDl
|
||||
? bolus.mgPerDl
|
||||
: bolus.mmolPerL ?? 0)! /
|
||||
bolus.units))
|
||||
.toString()),
|
||||
Text(
|
||||
'${Settings.glucoseMeasurementSuffix} per unit',
|
||||
textAlign: TextAlign.center,
|
||||
textScaleFactor: 0.75),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: (bolus.units > 0 && bolus.carbs > 0)
|
||||
? [
|
||||
Text((bolus.units / bolus.carbs * 12).toStringAsPrecision(2)),
|
||||
const Text('U per bread unit',
|
||||
textAlign: TextAlign.center, textScaleFactor: 0.75),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: (bolus.units > 0 && (bolus.mgPerDl ?? bolus.mmolPerL ?? 0) > 0)
|
||||
? [
|
||||
Text((((Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDl : bolus.mmolPerL ?? 0)! / bolus.units)).toString()),
|
||||
Text('${Settings.glucoseMeasurementSuffix} per unit',
|
||||
textAlign: TextAlign.center, textScaleFactor: 0.75),
|
||||
]
|
||||
: [],
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.delete,
|
||||
color: Colors.blue,
|
||||
),
|
||||
onPressed: () => handleDeleteAction(bolus),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.delete,
|
||||
color: Colors.blue,
|
||||
),
|
||||
onPressed: () => handleDeleteAction(bolus),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: const Center(
|
||||
child: Text('You have not created any Bolus Rates yet!'),
|
||||
);
|
||||
},
|
||||
),
|
||||
) : const Center(
|
||||
child: Text('You have not created any Bolus Rates yet!'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/forms/boolean_form_field.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/bolus.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/screens/bolus/bolus_detail.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/bolus_profile.dart';
|
||||
import 'package:diameter/screens/bolus/bolus_list.dart';
|
||||
|
||||
@ -90,6 +91,14 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
|
||||
bottomNav = detailBottomRow;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_nameController.dispose();
|
||||
_notesController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -269,7 +278,7 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
|
||||
(_bolusProfile!.active != _active ||
|
||||
_bolusProfile!.name != _nameController.text ||
|
||||
(_bolusProfile!.notes ?? '') != _notesController.text))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/dropdown.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
|
||||
import 'package:diameter/models/bolus.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -22,6 +23,18 @@ class _BolusProfileListScreenState extends State<BolusProfileListScreen> {
|
||||
|
||||
final BolusProfile? _activeProfile = BolusProfile.getActive(DateTime.now());
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
setState(() {
|
||||
_bolusProfiles = BolusProfile.getAll();
|
||||
@ -77,6 +90,30 @@ class _BolusProfileListScreenState extends State<BolusProfileListScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
void handleDuplicateAction(BolusProfile bolusProfile) async {
|
||||
final copy = BolusProfile(
|
||||
active: false,
|
||||
name: 'Copy of ${bolusProfile.name}',
|
||||
);
|
||||
BolusProfile.put(copy);
|
||||
|
||||
final rates = Bolus.getAllForProfile(bolusProfile.id);
|
||||
for (Bolus rate in rates) {
|
||||
final bolus = Bolus(
|
||||
endTime: rate.endTime,
|
||||
startTime: rate.startTime,
|
||||
units: rate.units,
|
||||
carbs: rate.carbs,
|
||||
mgPerDl: rate.mgPerDl,
|
||||
mmolPerL: rate.mmolPerL,
|
||||
);
|
||||
bolus.bolusProfile.target = copy;
|
||||
Bolus.put(bolus);
|
||||
}
|
||||
|
||||
reload(message: 'Added copy of ${bolusProfile.name}');
|
||||
}
|
||||
|
||||
void onDelete(BolusProfile bolusProfile) {
|
||||
BolusProfile.remove(bolusProfile.id);
|
||||
reload(message: 'Bolus Profile deleted');
|
||||
@ -84,7 +121,7 @@ class _BolusProfileListScreenState extends State<BolusProfileListScreen> {
|
||||
|
||||
void handleDeleteAction(BolusProfile bolusProfile) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(bolusProfile),
|
||||
message: 'Are you sure you want to delete this Bolus Profile?',
|
||||
@ -143,12 +180,6 @@ class _BolusProfileListScreenState extends State<BolusProfileListScreen> {
|
||||
showDetailScreen(bolusProfile: bolusProfile);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -196,6 +227,14 @@ class _BolusProfileListScreenState extends State<BolusProfileListScreen> {
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.copy,
|
||||
color: Colors.blue,
|
||||
),
|
||||
onPressed: () =>
|
||||
handleDuplicateAction(bolusProfile),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.delete,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/glucose_target.dart';
|
||||
import 'package:diameter/models/log_bolus.dart';
|
||||
import 'package:diameter/models/log_entry.dart';
|
||||
@ -29,6 +29,12 @@ class _LogScreenState extends State<LogScreen> {
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
setState(() {
|
||||
_logEntryDailyMap = LogEntry.getDailyEntryMap();
|
||||
@ -53,7 +59,7 @@ class _LogScreenState extends State<LogScreen> {
|
||||
|
||||
void handleDeleteAction(LogEntry logEntry) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(logEntry),
|
||||
message: 'Are you sure you want to delete this Log Entry?',
|
||||
|
@ -1,9 +1,11 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/dropdown.dart';
|
||||
import 'package:diameter/components/forms.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/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/bolus.dart';
|
||||
import 'package:diameter/models/log_bolus.dart';
|
||||
import 'package:diameter/models/log_entry.dart';
|
||||
@ -136,6 +138,25 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
updateDelayedRatio();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_unitsController.dispose();
|
||||
_carbsController.dispose();
|
||||
_mgPerDlCurrentController.dispose();
|
||||
_mgPerDlTargetController.dispose();
|
||||
_mgPerDlCorrectionController.dispose();
|
||||
_mmolPerLCurrentController.dispose();
|
||||
_mmolPerLTargetController.dispose();
|
||||
_mmolPerLCorrectionController.dispose();
|
||||
_delayController.dispose();
|
||||
_notesController.dispose();
|
||||
_delayedUnitsController.dispose();
|
||||
_immediateUnitsController.dispose();
|
||||
_mealController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -179,20 +200,73 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
void updateDelayedRatio() {
|
||||
if (_unitsController.text != '') {
|
||||
setState(() {
|
||||
_delayedUnitsController.text =
|
||||
((double.tryParse(_unitsController.text) ?? 0) *
|
||||
_delayPercentage /
|
||||
100)
|
||||
.toString();
|
||||
_immediateUnitsController.text =
|
||||
((double.tryParse(_unitsController.text) ?? 0) *
|
||||
(100 - _delayPercentage) /
|
||||
100)
|
||||
.toString();
|
||||
});
|
||||
void updateDelayedRatio(
|
||||
{double? totalUnitsUpdate,
|
||||
double? delayedUnitsUpdate,
|
||||
double? immediateUnitsUpdate,
|
||||
double? percentageUpdate}) {
|
||||
int precision = Utils.getFractionDigitsLength(Settings.insulinSteps);
|
||||
double? totalUnits =
|
||||
totalUnitsUpdate ?? double.tryParse(_unitsController.text);
|
||||
double? delayedUnits;
|
||||
double? immediateUnits;
|
||||
|
||||
if (totalUnits == null) {
|
||||
delayedUnits =
|
||||
delayedUnitsUpdate ?? double.tryParse(_delayedUnitsController.text);
|
||||
immediateUnits = immediateUnitsUpdate ??
|
||||
double.tryParse(_immediateUnitsController.text);
|
||||
|
||||
if (percentageUpdate != null) {
|
||||
if (delayedUnits != null) {
|
||||
totalUnits = delayedUnits / percentageUpdate * 100;
|
||||
} else if (immediateUnits != null) {
|
||||
totalUnits = immediateUnits / percentageUpdate * 100;
|
||||
}
|
||||
} else if (delayedUnits != null && immediateUnits != null) {
|
||||
totalUnits = Utils.addDoublesWithPrecision(
|
||||
delayedUnits, immediateUnits, precision);
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_unitsController.text = (totalUnits ?? 0).toString();
|
||||
});
|
||||
|
||||
if (totalUnits != null) {
|
||||
double percentage = percentageUpdate ?? _delayPercentage;
|
||||
|
||||
if (totalUnitsUpdate != null || percentageUpdate != null) {
|
||||
delayedUnits = totalUnits * percentage / 100;
|
||||
} else if (delayedUnitsUpdate != null) {
|
||||
delayedUnits = delayedUnitsUpdate;
|
||||
} else if (immediateUnitsUpdate != null) {
|
||||
delayedUnits = totalUnits - immediateUnitsUpdate;
|
||||
}
|
||||
|
||||
if (delayedUnits != null) {
|
||||
double remainder = delayedUnits % Settings.insulinSteps;
|
||||
int precision = Utils.getFractionDigitsLength(Settings.insulinSteps);
|
||||
|
||||
if (remainder != 0) {
|
||||
delayedUnits = Utils.addDoublesWithPrecision(
|
||||
delayedUnits, -remainder, precision);
|
||||
if (remainder > Settings.insulinSteps / 2) {
|
||||
delayedUnits = Utils.addDoublesWithPrecision(
|
||||
delayedUnits, Settings.insulinSteps, precision);
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_delayedUnitsController.text = delayedUnits.toString();
|
||||
_immediateUnitsController.text = Utils.addDoublesWithPrecision(
|
||||
totalUnits!, -delayedUnits!, precision)
|
||||
.toString();
|
||||
if (totalUnits != 0) {
|
||||
_delayPercentage = delayedUnits * 100 / totalUnits;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,28 +281,30 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
}
|
||||
|
||||
void calculateBolus() {
|
||||
setState(() {
|
||||
if (_rate != null && !_setManually) {
|
||||
_unitsController.text = ((double.tryParse(_carbsController.text) ?? 0) /
|
||||
(_rate!.carbs / _rate!.units))
|
||||
.toString();
|
||||
if (_unitsController.text != '') {
|
||||
_delayedUnitsController.text =
|
||||
((double.tryParse(_unitsController.text) ?? 0) *
|
||||
_delayPercentage /
|
||||
100)
|
||||
.toString();
|
||||
_immediateUnitsController.text =
|
||||
((double.tryParse(_unitsController.text) ?? 0) *
|
||||
(100 - _delayPercentage) /
|
||||
100)
|
||||
.toString();
|
||||
if (_rate != null && !_setManually) {
|
||||
double units = (double.tryParse(_carbsController.text) ?? 0) /
|
||||
(_rate!.carbs / _rate!.units);
|
||||
double remainder = units % Settings.insulinSteps;
|
||||
int precision = Utils.getFractionDigitsLength(Settings.insulinSteps);
|
||||
|
||||
if (remainder != 0) {
|
||||
units = Utils.addDoublesWithPrecision(units, -remainder, precision);
|
||||
if (remainder > Settings.insulinSteps / 2) {
|
||||
units = Utils.addDoublesWithPrecision(
|
||||
units, Settings.insulinSteps, precision);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setState(() {
|
||||
_unitsController.text = units.toString();
|
||||
});
|
||||
|
||||
updateDelayedRatio(
|
||||
totalUnitsUpdate: double.tryParse(_unitsController.text));
|
||||
}
|
||||
}
|
||||
|
||||
void onChangeGlucose({GlucoseMeasurement? calculateFrom}) {
|
||||
void onChangeGlucose() {
|
||||
int? mgPerDlCurrent;
|
||||
int? mgPerDlTarget;
|
||||
int? mgPerDlCorrection;
|
||||
@ -237,14 +313,14 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
double? mmolPerLTarget;
|
||||
double? mmolPerLCorrection;
|
||||
|
||||
if (calculateFrom != GlucoseMeasurement.mmolPerL &&
|
||||
if (Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl &&
|
||||
_mgPerDlCurrentController.text != '' &&
|
||||
_mgPerDlTargetController.text != '') {
|
||||
mgPerDlCurrent = int.tryParse(_mgPerDlCurrentController.text);
|
||||
mgPerDlTarget = int.tryParse(_mgPerDlTargetController.text);
|
||||
mgPerDlCorrection = max((mgPerDlCurrent ?? 0) - (mgPerDlTarget ?? 0), 0);
|
||||
}
|
||||
if (calculateFrom != GlucoseMeasurement.mgPerDl &&
|
||||
if (Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL &&
|
||||
_mmolPerLCurrentController.text != '') {
|
||||
mmolPerLCurrent = double.tryParse(_mmolPerLCurrentController.text);
|
||||
mmolPerLTarget = double.tryParse(_mmolPerLTargetController.text);
|
||||
@ -264,9 +340,9 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
_mmolPerLCorrectionController.text =
|
||||
Utils.convertMgPerDlToMmolPerL(mgPerDlCorrection ?? 0).toString();
|
||||
if (_rate != null && !_setManually) {
|
||||
_unitsController.text = ((mgPerDlCorrection ?? 0) /
|
||||
((_rate!.mgPerDl ?? 0) / _rate!.units))
|
||||
.toString();
|
||||
updateDelayedRatio(
|
||||
totalUnitsUpdate: (mgPerDlCorrection ?? 0) /
|
||||
((_rate!.mgPerDl ?? 0) / _rate!.units));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -282,9 +358,9 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
_mgPerDlCorrectionController.text =
|
||||
Utils.convertMmolPerLToMgPerDl(mmolPerLCorrection ?? 0).toString();
|
||||
if (_rate != null && !_setManually) {
|
||||
_unitsController.text = ((mmolPerLCorrection ?? 0) /
|
||||
((_rate!.mmolPerL ?? 0) / _rate!.units))
|
||||
.toString();
|
||||
updateDelayedRatio(
|
||||
totalUnitsUpdate: (mmolPerLCorrection ?? 0) /
|
||||
((_rate!.mmolPerL ?? 0) / _rate!.units));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -414,7 +490,7 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
int.tryParse(_delayController.text) != _logBolus!.delay ||
|
||||
_setManually != _logBolus!.setManually ||
|
||||
_notesController.text != (_logBolus!.notes ?? ''))))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
@ -444,20 +520,18 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Bolus Units',
|
||||
suffixText: ' U',
|
||||
),
|
||||
child: NumberFormField(
|
||||
label: 'Bolus Units',
|
||||
suffix: ' U',
|
||||
controller: _unitsController,
|
||||
onChanged: (_) {
|
||||
step: Settings.insulinSteps,
|
||||
autoRoundToMultipleOfStep: true,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_setManually = true;
|
||||
});
|
||||
updateDelayedRatio();
|
||||
updateDelayedRatio(totalUnitsUpdate: value);
|
||||
},
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
@ -486,6 +560,7 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
_bolusType = BolusType.glucose;
|
||||
onChangeGlucose();
|
||||
});
|
||||
}),
|
||||
),
|
||||
@ -497,6 +572,7 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_bolusType = BolusType.meal;
|
||||
calculateBolus();
|
||||
});
|
||||
}),
|
||||
),
|
||||
@ -517,23 +593,13 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(right: 5.0),
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Current',
|
||||
suffixText: 'mg/dl',
|
||||
),
|
||||
child: NumberFormField(
|
||||
label: 'Current',
|
||||
suffix: 'mg/dl',
|
||||
controller:
|
||||
_mgPerDlCurrentController,
|
||||
onChanged: (_) async {
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 1));
|
||||
onChangeGlucose(
|
||||
calculateFrom:
|
||||
GlucoseMeasurement
|
||||
.mgPerDl);
|
||||
},
|
||||
keyboardType: const TextInputType
|
||||
.numberWithOptions(),
|
||||
onChanged: (_) => onChangeGlucose(),
|
||||
showSteppers: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -541,23 +607,13 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 5.0),
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Target',
|
||||
suffixText: 'mg/dl',
|
||||
),
|
||||
child: NumberFormField(
|
||||
label: 'Target',
|
||||
suffix: 'mg/dl',
|
||||
controller:
|
||||
_mgPerDlTargetController,
|
||||
onChanged: (_) async {
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 1));
|
||||
onChangeGlucose(
|
||||
calculateFrom:
|
||||
GlucoseMeasurement
|
||||
.mgPerDl);
|
||||
},
|
||||
keyboardType: const TextInputType
|
||||
.numberWithOptions(),
|
||||
onChanged: (_) => onChangeGlucose(),
|
||||
showSteppers: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -576,106 +632,73 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
[
|
||||
GlucoseDisplayMode.both,
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
].contains(Settings.glucoseDisplayMode)
|
||||
? IconButton(
|
||||
onPressed: () => onChangeGlucose(
|
||||
calculateFrom:
|
||||
GlucoseMeasurement
|
||||
.mmolPerL),
|
||||
icon: const Icon(Icons.calculate),
|
||||
)
|
||||
: Container(),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
Row(
|
||||
children: Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mmolPerL ||
|
||||
[
|
||||
GlucoseDisplayMode.both,
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
].contains(Settings.glucoseDisplayMode)
|
||||
? [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(right: 5.0),
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Current',
|
||||
suffixText: 'mmol/l',
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: [
|
||||
GlucoseDisplayMode.both,
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
].contains(Settings.glucoseDisplayMode)
|
||||
? 10.0
|
||||
: 0.0),
|
||||
child: Row(
|
||||
children: Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mmolPerL ||
|
||||
[
|
||||
GlucoseDisplayMode.both,
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
].contains(Settings.glucoseDisplayMode)
|
||||
? [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 5.0),
|
||||
child: NumberFormField(
|
||||
label: 'Current',
|
||||
suffix: 'mmol/l',
|
||||
controller:
|
||||
_mmolPerLCurrentController,
|
||||
onChanged: (_) =>
|
||||
onChangeGlucose(),
|
||||
showSteppers: false,
|
||||
),
|
||||
controller:
|
||||
_mmolPerLCurrentController,
|
||||
onChanged: (_) async {
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 1));
|
||||
onChangeGlucose(
|
||||
calculateFrom:
|
||||
GlucoseMeasurement
|
||||
.mmolPerL);
|
||||
},
|
||||
keyboardType: const TextInputType
|
||||
.numberWithOptions(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 5.0),
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Target',
|
||||
suffixText: 'mmol/l',
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 5.0),
|
||||
child: NumberFormField(
|
||||
label: 'Target',
|
||||
suffix: 'mmol/l',
|
||||
controller:
|
||||
_mmolPerLTargetController,
|
||||
onChanged: (_) =>
|
||||
onChangeGlucose(),
|
||||
showSteppers: false,
|
||||
),
|
||||
controller:
|
||||
_mmolPerLTargetController,
|
||||
onChanged: (_) async {
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 1));
|
||||
onChangeGlucose(
|
||||
calculateFrom:
|
||||
GlucoseMeasurement
|
||||
.mmolPerL);
|
||||
},
|
||||
keyboardType: const TextInputType
|
||||
.numberWithOptions(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 5.0),
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Correction',
|
||||
suffixText: 'mmol/l',
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 5.0),
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Correction',
|
||||
suffixText: 'mmol/l',
|
||||
),
|
||||
controller:
|
||||
_mmolPerLCorrectionController,
|
||||
readOnly: true,
|
||||
),
|
||||
controller:
|
||||
_mmolPerLCorrectionController,
|
||||
readOnly: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
[
|
||||
GlucoseDisplayMode.both,
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
].contains(Settings.glucoseDisplayMode)
|
||||
? IconButton(
|
||||
onPressed: () => onChangeGlucose(
|
||||
calculateFrom:
|
||||
GlucoseMeasurement
|
||||
.mgPerDl),
|
||||
icon: const Icon(Icons.calculate),
|
||||
)
|
||||
: Container(),
|
||||
]
|
||||
: [],
|
||||
]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
]
|
||||
: [
|
||||
@ -712,17 +735,15 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Carbs',
|
||||
suffixText:
|
||||
Settings.nutritionMeasurementSuffix,
|
||||
),
|
||||
child: NumberFormField(
|
||||
label: 'Carbs',
|
||||
suffix: Settings.nutritionMeasurementSuffix,
|
||||
controller: _carbsController,
|
||||
onChanged: (_) => calculateBolus(),
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(
|
||||
decimal: true),
|
||||
step: Settings.nutritionSteps,
|
||||
onChanged: (value) {
|
||||
_carbsController.text = (value ?? 0).toString();
|
||||
calculateBolus();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -749,10 +770,7 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
max: 100,
|
||||
onChanged: _delayController.text != ''
|
||||
? (value) {
|
||||
setState(() {
|
||||
_delayPercentage = value;
|
||||
});
|
||||
updateDelayedRatio();
|
||||
updateDelayedRatio(percentageUpdate: value);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
@ -766,34 +784,30 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 5.0),
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Immediate Bolus',
|
||||
suffixText: ' U',
|
||||
),
|
||||
child: NumberFormField(
|
||||
label: 'Immediate Bolus',
|
||||
suffix: ' U',
|
||||
controller: _immediateUnitsController,
|
||||
max: double.tryParse(_unitsController.text),
|
||||
step: Settings.insulinSteps,
|
||||
readOnly: true,
|
||||
enabled:
|
||||
(int.tryParse(_delayController.text) ??
|
||||
0) !=
|
||||
0,
|
||||
onChanged: (value) => updateDelayedRatio(
|
||||
immediateUnitsUpdate: value),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 5.0),
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Delayed Bolus',
|
||||
suffixText: ' U',
|
||||
),
|
||||
child: NumberFormField(
|
||||
label: 'Delayed Bolus',
|
||||
suffix: ' U',
|
||||
controller: _delayedUnitsController,
|
||||
max: double.tryParse(_unitsController.text),
|
||||
step: Settings.insulinSteps,
|
||||
readOnly: true,
|
||||
enabled:
|
||||
(int.tryParse(_delayController.text) ??
|
||||
0) !=
|
||||
0,
|
||||
onChanged: (value) => updateDelayedRatio(
|
||||
delayedUnitsUpdate: value),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/log_bolus.dart';
|
||||
import 'package:diameter/models/log_entry.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
@ -25,6 +25,12 @@ class LogBolusListScreen extends StatefulWidget {
|
||||
class _LogBolusListScreenState extends State<LogBolusListScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
widget.reload();
|
||||
|
||||
@ -60,7 +66,7 @@ class _LogBolusListScreenState extends State<LogBolusListScreen> {
|
||||
|
||||
void handleDeleteAction(LogBolus logBolus) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(logBolus),
|
||||
message: 'Are you sure you want to delete this Bolus?',
|
||||
|
@ -1,6 +1,9 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/components/forms/date_time_form_field.dart';
|
||||
import 'package:diameter/components/forms/number_form_field.dart';
|
||||
import 'package:diameter/components/forms/time_of_day_form_field.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/log_bolus.dart';
|
||||
import 'package:diameter/models/log_entry.dart';
|
||||
import 'package:diameter/models/log_meal.dart';
|
||||
@ -40,8 +43,10 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
|
||||
|
||||
final _timeController = TextEditingController(text: '');
|
||||
final _dateController = TextEditingController(text: '');
|
||||
final _mgPerDlController = TextEditingController(text: '');
|
||||
final _mmolPerLController = TextEditingController(text: '');
|
||||
final _mgPerDlController =
|
||||
TextEditingController(text: Settings.targetMgPerDl.toString());
|
||||
final _mmolPerLController =
|
||||
TextEditingController(text: Settings.targetMmolPerL.toString());
|
||||
final _notesController = TextEditingController(text: '');
|
||||
|
||||
late FloatingActionButton addMealButton;
|
||||
@ -109,6 +114,17 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
|
||||
updateTime();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_timeController.dispose();
|
||||
_dateController.dispose();
|
||||
_mgPerDlController.dispose();
|
||||
_mmolPerLController.dispose();
|
||||
_notesController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -137,25 +153,26 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
|
||||
_dateController.text = DateTimeUtils.displayDate(_time);
|
||||
}
|
||||
|
||||
void convertBetweenMgPerDlAndMmolPerL() {
|
||||
int? mgPerDl;
|
||||
double? mmolPerL;
|
||||
|
||||
if (Settings.glucoseMeasurement != GlucoseMeasurement.mmolPerL &&
|
||||
_mgPerDlController.text != '') {
|
||||
mgPerDl = int.tryParse(_mgPerDlController.text);
|
||||
setState(() {
|
||||
void convertBetweenMgPerDlAndMmolPerL(double? value) async {
|
||||
if (value != null) {
|
||||
if (Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl &&
|
||||
_mgPerDlController.text != '') {
|
||||
_mgPerDlController.text = value.toInt().toString();
|
||||
setState(() {
|
||||
_mmolPerLController.text =
|
||||
Utils.convertMgPerDlToMmolPerL(value.toInt()).toString();
|
||||
});
|
||||
}
|
||||
if (Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL &&
|
||||
_mmolPerLController.text != '') {
|
||||
_mmolPerLController.text =
|
||||
Utils.convertMgPerDlToMmolPerL(mgPerDl ?? 0).toString();
|
||||
});
|
||||
}
|
||||
if (Settings.glucoseMeasurement != GlucoseMeasurement.mgPerDl &&
|
||||
_mmolPerLController.text != '') {
|
||||
mmolPerL = double.tryParse(_mmolPerLController.text);
|
||||
setState(() {
|
||||
_mgPerDlController.text =
|
||||
Utils.convertMmolPerLToMgPerDl(mmolPerL ?? 0).toString();
|
||||
});
|
||||
Utils.toStringMatchingTemplateFractionPrecision(
|
||||
value, Settings.mmolPerLSteps);
|
||||
setState(() {
|
||||
_mgPerDlController.text =
|
||||
Utils.convertMmolPerLToMgPerDl(value.toDouble()).toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,7 +224,7 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
|
||||
double.tryParse(_mmolPerLController.text) !=
|
||||
_logEntry!.mmolPerL ||
|
||||
_notesController.text != (_logEntry!.notes ?? ''))))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
@ -337,76 +354,52 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
|
||||
children: [
|
||||
Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mgPerDl ||
|
||||
[
|
||||
GlucoseDisplayMode.both,
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
].contains(Settings.glucoseDisplayMode)
|
||||
? Expanded(
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'mg/dl',
|
||||
suffixText: 'mg/dl',
|
||||
),
|
||||
readOnly: Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mmolPerL,
|
||||
controller: _mgPerDlController,
|
||||
onChanged: (_) async {
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 1));
|
||||
convertBetweenMgPerDlAndMmolPerL();
|
||||
},
|
||||
keyboardType: const TextInputType
|
||||
.numberWithOptions(),
|
||||
validator: (value) {
|
||||
if (value!.trim().isEmpty &&
|
||||
_mmolPerLController.text
|
||||
.trim()
|
||||
.isEmpty) {
|
||||
return 'How high is your blood sugar?';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
[
|
||||
GlucoseDisplayMode.both,
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
].contains(Settings.glucoseDisplayMode)
|
||||
? const SizedBox(width: 10.0)
|
||||
: Container(),
|
||||
Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mmolPerL ||
|
||||
Settings.glucoseDisplayMode ==
|
||||
GlucoseDisplayMode.both ||
|
||||
Settings.glucoseDisplayMode ==
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
? Expanded(
|
||||
child: TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'mmol/l',
|
||||
suffixText: 'mmol/l',
|
||||
),
|
||||
flex: Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mgPerDl
|
||||
? 2
|
||||
: 1,
|
||||
child: NumberFormField(
|
||||
label: 'per mg/dl',
|
||||
suffix: 'mg/dl',
|
||||
readOnly: Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mmolPerL,
|
||||
showSteppers:
|
||||
Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mgPerDl,
|
||||
controller: _mgPerDlController,
|
||||
onChanged:
|
||||
convertBetweenMgPerDlAndMmolPerL,
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mmolPerL ||
|
||||
[
|
||||
GlucoseDisplayMode.both,
|
||||
GlucoseDisplayMode.bothForDetail
|
||||
].contains(Settings.glucoseDisplayMode)
|
||||
? Expanded(
|
||||
flex: Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mmolPerL
|
||||
? 2
|
||||
: 1,
|
||||
child: NumberFormField(
|
||||
label: 'per mmol/l',
|
||||
suffix: 'mmol/l',
|
||||
readOnly: Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mgPerDl,
|
||||
showSteppers:
|
||||
Settings.glucoseMeasurement ==
|
||||
GlucoseMeasurement.mmolPerL,
|
||||
controller: _mmolPerLController,
|
||||
onChanged: (_) async {
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 1));
|
||||
convertBetweenMgPerDlAndMmolPerL();
|
||||
},
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(
|
||||
decimal: true),
|
||||
validator: (value) {
|
||||
if (value!.trim().isEmpty &&
|
||||
_mgPerDlController.text
|
||||
.trim()
|
||||
.isEmpty) {
|
||||
return 'How high is your blood sugar?';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
step: Settings.mmolPerLSteps,
|
||||
onChanged:
|
||||
convertBetweenMgPerDlAndMmolPerL,
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
|
@ -1,7 +1,8 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/dropdown.dart';
|
||||
import 'package:diameter/components/forms.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/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/accuracy.dart';
|
||||
import 'package:diameter/models/log_meal.dart';
|
||||
import 'package:diameter/models/meal.dart';
|
||||
@ -37,13 +38,10 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
bool _isSaving = false;
|
||||
bool _isExpanded = false;
|
||||
|
||||
double _amount = 1;
|
||||
|
||||
final GlobalKey<FormState> _logMealForm = GlobalKey<FormState>();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
final _valueController = TextEditingController(text: '');
|
||||
final _amountController = TextEditingController(text: '');
|
||||
final _carbsRatioController = TextEditingController(text: '');
|
||||
final _portionSizeController = TextEditingController(text: '');
|
||||
final _totalCarbsController = TextEditingController(text: '');
|
||||
@ -62,6 +60,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
final _mealPortionTypeController = TextEditingController(text: '');
|
||||
final _portionSizeAccuracyController = TextEditingController(text: '');
|
||||
final _carbsRatioAccuracyController = TextEditingController(text: '');
|
||||
final _amountController = TextEditingController(text: '1');
|
||||
|
||||
List<Meal> _meals = [];
|
||||
List<MealCategory> _mealCategories = [];
|
||||
@ -84,12 +83,11 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
|
||||
if (widget.id != 0) {
|
||||
_valueController.text = _logMeal!.value;
|
||||
_amountController.text = _logMeal!.amount.toString();
|
||||
_carbsRatioController.text = (_logMeal!.carbsRatio ?? '').toString();
|
||||
_portionSizeController.text = (_logMeal!.portionSize ?? '').toString();
|
||||
_totalCarbsController.text = (_logMeal!.totalCarbs ?? '').toString();
|
||||
_amountController.text = (_logMeal!.amount).toString();
|
||||
_notesController.text = _logMeal!.notes ?? '';
|
||||
|
||||
_meal = _logMeal!.meal.target;
|
||||
_mealController.text = (_meal ?? '').toString();
|
||||
_mealSource = _logMeal!.mealSource.target;
|
||||
@ -105,10 +103,24 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
_carbsRatioAccuracyController.text =
|
||||
(_carbsRatioAccuracy ?? '').toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (_amountController.text == '') {
|
||||
_amountController.text = '1';
|
||||
}
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_valueController.dispose();
|
||||
_carbsRatioController.dispose();
|
||||
_portionSizeController.dispose();
|
||||
_totalCarbsController.dispose();
|
||||
_notesController.dispose();
|
||||
_mealController.dispose();
|
||||
_mealSourceController.dispose();
|
||||
_mealCategoryController.dispose();
|
||||
_mealPortionTypeController.dispose();
|
||||
_portionSizeAccuracyController.dispose();
|
||||
_carbsRatioAccuracyController.dispose();
|
||||
_amountController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
@ -177,7 +189,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
_carbsRatioController.text = (meal?.carbsRatio ?? '').toString();
|
||||
_amountController.text = '1';
|
||||
_portionSizeController.text = (meal?.portionSize ?? '').toString();
|
||||
_totalCarbsController.text = (meal?.carbsPerPortion ?? '').toString();
|
||||
_totalCarbsController.text = (meal?.carbsPerPortion ?? '').toString();
|
||||
});
|
||||
updateMealSource(meal?.mealSource.target);
|
||||
updateMealCategory(meal?.mealCategory.target);
|
||||
@ -197,6 +209,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
carbsRatio: double.tryParse(_carbsRatioController.text),
|
||||
portionSize: double.tryParse(_portionSizeController.text),
|
||||
totalCarbs: double.tryParse(_totalCarbsController.text),
|
||||
amount: double.parse(_amountController.text),
|
||||
);
|
||||
logMeal.logEntry.targetId = widget.logEntryId;
|
||||
logMeal.meal.target = _meal;
|
||||
@ -222,6 +235,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
_mealSource != null ||
|
||||
_mealCategory != null ||
|
||||
_mealPortionType != null ||
|
||||
double.tryParse(_amountController.text) != 1 ||
|
||||
double.tryParse(_carbsRatioController.text) != null ||
|
||||
double.tryParse(_portionSizeController.text) != null ||
|
||||
double.tryParse(_totalCarbsController.text) != null ||
|
||||
@ -234,6 +248,8 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
_mealSource != _logMeal!.mealSource.target ||
|
||||
_mealCategory != _logMeal!.mealCategory.target ||
|
||||
_mealPortionType != _logMeal!.mealPortionType.target ||
|
||||
double.tryParse(_amountController.text) !=
|
||||
_logMeal!.amount ||
|
||||
double.tryParse(_carbsRatioController.text) !=
|
||||
_logMeal!.carbsRatio ||
|
||||
double.tryParse(_portionSizeController.text) !=
|
||||
@ -245,7 +261,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
_portionSizeAccuracy !=
|
||||
_logMeal!.portionSizeAccuracy.target ||
|
||||
_notesController.text != (_logMeal!.notes ?? ''))))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
@ -256,15 +272,16 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
}
|
||||
|
||||
void updateAmount(double? amount) {
|
||||
double? previousAmount;
|
||||
double previousAmount;
|
||||
double newAmount;
|
||||
double? portionSize;
|
||||
double? carbsRatio;
|
||||
|
||||
previousAmount = _amount;
|
||||
previousAmount = double.tryParse(_amountController.text) ?? 1;
|
||||
newAmount = amount?.toDouble() ?? 1;
|
||||
|
||||
setState(() {
|
||||
_amountController.text = (amount ?? '').toString();
|
||||
_amount = amount ?? 1;
|
||||
_amountController.text = newAmount.toString();
|
||||
});
|
||||
|
||||
if (_carbsRatioController.text != '') {
|
||||
@ -274,9 +291,9 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
portionSize = double.tryParse(_portionSizeController.text);
|
||||
}
|
||||
|
||||
if (amount != null && portionSize != null) {
|
||||
if (portionSize != null) {
|
||||
setState(() {
|
||||
portionSize = portionSize! / (previousAmount ?? 1) * amount;
|
||||
portionSize = portionSize! / previousAmount * newAmount;
|
||||
_portionSizeController.text = portionSize.toString();
|
||||
});
|
||||
if (carbsRatio != null) {
|
||||
@ -289,14 +306,11 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
}
|
||||
|
||||
void calculateThirdMeasurementOfPortionCarbsRelation() {
|
||||
int? amount;
|
||||
double? amount = double.tryParse(_amountController.text) ?? 1;
|
||||
double? carbsRatio;
|
||||
double? portionSize;
|
||||
double? carbsPerPortion;
|
||||
|
||||
if (_amountController.text != '') {
|
||||
amount = int.tryParse(_amountController.text);
|
||||
}
|
||||
if (_carbsRatioController.text != '') {
|
||||
carbsRatio = double.tryParse(_carbsRatioController.text);
|
||||
}
|
||||
@ -310,15 +324,14 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
if (carbsRatio != null && portionSize != null && carbsPerPortion == null) {
|
||||
setState(() {
|
||||
_totalCarbsController.text =
|
||||
Utils.calculateCarbs(carbsRatio!, portionSize! * (amount ?? 1))
|
||||
.toString();
|
||||
Utils.calculateCarbs(carbsRatio!, portionSize! * amount).toString();
|
||||
});
|
||||
}
|
||||
if (carbsRatio == null && portionSize != null && carbsPerPortion != null) {
|
||||
setState(() {
|
||||
_carbsRatioController.text = Utils.calculateCarbsRatio(
|
||||
carbsPerPortion!, portionSize! * (amount ?? 1))
|
||||
.toString();
|
||||
_carbsRatioController.text =
|
||||
Utils.calculateCarbsRatio(carbsPerPortion!, portionSize! * amount)
|
||||
.toString();
|
||||
});
|
||||
}
|
||||
if (carbsRatio != null && portionSize == null && carbsPerPortion != null) {
|
||||
@ -388,12 +401,44 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
NumberFormField(
|
||||
controller: _amountController,
|
||||
label: 'Amount',
|
||||
suffix: _mealPortionType?.value,
|
||||
min: 0,
|
||||
onChanged: updateAmount,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: NumberFormField(
|
||||
controller: _amountController,
|
||||
label: 'Amount',
|
||||
suffix: _mealPortionType?.value,
|
||||
onChanged: updateAmount,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => updateAmount(0.5),
|
||||
child: Column(
|
||||
children: const [
|
||||
Text('1', style: TextStyle(decoration: TextDecoration.underline, decorationThickness: 2),),
|
||||
Text('2'),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => updateAmount(0.33),
|
||||
child: Column(
|
||||
children: const [
|
||||
Text('1', style: TextStyle(decoration: TextDecoration.underline, decorationThickness: 2),),
|
||||
Text('3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => updateAmount(0.67),
|
||||
child: Column(
|
||||
children: const [
|
||||
Text('2', style: TextStyle(decoration: TextDecoration.underline, decorationThickness: 2),),
|
||||
Text('3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/log_entry.dart';
|
||||
import 'package:diameter/models/log_meal.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
@ -21,6 +21,12 @@ class LogMealListScreen extends StatefulWidget {
|
||||
class _LogMealListScreenState extends State<LogMealListScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
widget.reload();
|
||||
|
||||
@ -56,7 +62,7 @@ class _LogMealListScreenState extends State<LogMealListScreen> {
|
||||
|
||||
void handleDeleteAction(LogMeal meal) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(meal),
|
||||
message: 'Are you sure you want to delete this Meal?',
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/dropdown.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/components/forms/boolean_form_field.dart';
|
||||
import 'package:diameter/components/forms/date_time_form_field.dart';
|
||||
import 'package:diameter/components/forms/time_of_day_form_field.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/basal_profile.dart';
|
||||
import 'package:diameter/models/bolus_profile.dart';
|
||||
import 'package:diameter/models/log_event.dart';
|
||||
@ -99,6 +102,21 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
|
||||
updateEndTime();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_timeController.dispose();
|
||||
_endTimeController.dispose();
|
||||
_dateController.dispose();
|
||||
_endDateController.dispose();
|
||||
_reminderDurationController.dispose();
|
||||
_notesController.dispose();
|
||||
_eventTypeController.dispose();
|
||||
_bolusProfileController.dispose();
|
||||
_basalProfileController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -242,7 +260,7 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
|
||||
(_notesController.text != (_logEvent!.notes ?? '') ||
|
||||
_eventType != _logEvent!.eventType.target ||
|
||||
_hasEndTime != _logEvent!.hasEndTime)))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
@ -382,14 +400,17 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
TextFormField(
|
||||
controller: _reminderDurationController,
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Default Reminder Duration',
|
||||
suffixText: ' min',
|
||||
enabled: _hasEndTime,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: TextFormField(
|
||||
controller: _reminderDurationController,
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Default Reminder Duration',
|
||||
suffixText: ' min',
|
||||
enabled: _hasEndTime,
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
@ -426,39 +447,42 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoCompleteDropdownButton<
|
||||
BasalProfile>(
|
||||
controller: _basalProfileController,
|
||||
selectedItem: _basalProfile,
|
||||
label: 'Basal Profile',
|
||||
items: _basalProfiles,
|
||||
onChanged: updateBasalProfile,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutoCompleteDropdownButton<
|
||||
BasalProfile>(
|
||||
controller: _basalProfileController,
|
||||
selectedItem: _basalProfile,
|
||||
label: 'Basal Profile',
|
||||
items: _basalProfiles,
|
||||
onChanged: updateBasalProfile,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => _basalProfile ==
|
||||
null
|
||||
? const BasalProfileDetailScreen()
|
||||
: BasalProfileDetailScreen(
|
||||
id: _basalProfile!.id),
|
||||
),
|
||||
).then((result) {
|
||||
updateBasalProfile(result?[1]);
|
||||
reload(message: result?[0]);
|
||||
});
|
||||
},
|
||||
icon: Icon(_basalProfile == null
|
||||
? Icons.add
|
||||
: Icons.edit),
|
||||
),
|
||||
],
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => _basalProfile ==
|
||||
null
|
||||
? const BasalProfileDetailScreen()
|
||||
: BasalProfileDetailScreen(
|
||||
id: _basalProfile!.id),
|
||||
),
|
||||
).then((result) {
|
||||
updateBasalProfile(result?[1]);
|
||||
reload(message: result?[0]);
|
||||
});
|
||||
},
|
||||
icon: Icon(_basalProfile == null
|
||||
? Icons.add
|
||||
: Icons.edit),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
]
|
||||
: []),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/log_event.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/screens/log/log_event/log_event_detail.dart';
|
||||
@ -25,6 +25,12 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
setState(() {
|
||||
_activeEvents = LogEvent.getAllActiveForTime(DateTime.now());
|
||||
@ -73,7 +79,7 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
|
||||
|
||||
void handleDeleteAction(LogEvent logEvent) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(logEvent),
|
||||
message: 'Are you sure you want to delete this Event?',
|
||||
@ -91,7 +97,7 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
|
||||
|
||||
void handleStopAction(LogEvent event) async {
|
||||
if (Settings.get().showConfirmationDialogOnStopEvent) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onStop(event),
|
||||
message: 'Are you sure you want to end this Event?',
|
||||
|
@ -1,7 +1,9 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/dropdown.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/components/forms/boolean_form_field.dart';
|
||||
import 'package:diameter/components/forms/duration_form_field.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/basal_profile.dart';
|
||||
import 'package:diameter/models/bolus_profile.dart';
|
||||
import 'package:diameter/models/log_event_type.dart';
|
||||
@ -32,10 +34,10 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
final _valueController = TextEditingController(text: '');
|
||||
final _defaultReminderDurationController = TextEditingController(text: '');
|
||||
final _notesController = TextEditingController(text: '');
|
||||
|
||||
bool _hasEndTime = false;
|
||||
int _defaultReminderDuration = 0;
|
||||
BolusProfile? _bolusProfile;
|
||||
BasalProfile? _basalProfile;
|
||||
final _bolusProfileController = TextEditingController(text: '');
|
||||
@ -52,8 +54,8 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
|
||||
|
||||
if (_logEventType != null) {
|
||||
_valueController.text = _logEventType!.value;
|
||||
_defaultReminderDurationController.text =
|
||||
(_logEventType!.defaultReminderDuration ?? '').toString();
|
||||
_defaultReminderDuration =
|
||||
_logEventType!.defaultReminderDuration ?? 0;
|
||||
_hasEndTime = _logEventType!.hasEndTime;
|
||||
_notesController.text = _logEventType!.notes ?? '';
|
||||
_basalProfile = _logEventType!.basalProfile.target;
|
||||
@ -63,6 +65,16 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_valueController.dispose();
|
||||
_notesController.dispose();
|
||||
_bolusProfileController.dispose();
|
||||
_basalProfileController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -107,8 +119,7 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
|
||||
id: widget.id,
|
||||
value: _valueController.text,
|
||||
notes: _notesController.text,
|
||||
defaultReminderDuration:
|
||||
int.tryParse(_defaultReminderDurationController.text),
|
||||
defaultReminderDuration: _defaultReminderDuration,
|
||||
hasEndTime: _hasEndTime,
|
||||
);
|
||||
eventType.basalProfile.target = _basalProfile;
|
||||
@ -127,17 +138,16 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
|
||||
if (Settings.get().showConfirmationDialogOnCancel &&
|
||||
((isNew &&
|
||||
(_valueController.text != '' ||
|
||||
int.tryParse(_defaultReminderDurationController.text) !=
|
||||
null ||
|
||||
_defaultReminderDuration != 0 ||
|
||||
_notesController.text != '' ||
|
||||
_hasEndTime)) ||
|
||||
(!isNew &&
|
||||
(_valueController.text != _logEventType!.value ||
|
||||
int.tryParse(_defaultReminderDurationController.text) !=
|
||||
_defaultReminderDuration !=
|
||||
_logEventType!.defaultReminderDuration ||
|
||||
_notesController.text != (_logEventType!.notes ?? '') ||
|
||||
_hasEndTime != _logEventType!.hasEndTime)))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: isNew,
|
||||
onSave: handleSaveAction,
|
||||
@ -189,16 +199,12 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
|
||||
? [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
child: TextFormField(
|
||||
controller: _defaultReminderDurationController,
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Default Reminder Duration',
|
||||
suffixText: ' min',
|
||||
enabled: _hasEndTime,
|
||||
child: DurationFormField(
|
||||
minutes: _defaultReminderDuration,
|
||||
label: 'Default Reminder Duration',
|
||||
onChanged: (value) => _defaultReminderDuration = value ?? 0,
|
||||
showSteppers: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
|
@ -22,6 +22,12 @@ class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> {
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
setState(() {
|
||||
_logEventTypes = LogEventType.getAll();
|
||||
@ -66,8 +72,8 @@ class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
EventTypeDetailScreen(id: logEventType.id),
|
||||
builder: (context) => EventTypeDetailScreen(
|
||||
id: logEventType.id),
|
||||
),
|
||||
).then((result) => reload(message: result?[0]));
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -38,6 +38,14 @@ class _MealCategoryDetailScreenState extends State<MealCategoryDetailScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_valueController.dispose();
|
||||
_notesController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -80,7 +88,7 @@ class _MealCategoryDetailScreenState extends State<MealCategoryDetailScreen> {
|
||||
(!_isNew &&
|
||||
(_mealCategory!.value != _valueController.text ||
|
||||
(_mealCategory!.notes ?? '') != _notesController.text))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/screens/meal/meal_category_detail.dart';
|
||||
@ -25,6 +25,12 @@ class _MealCategoryListScreenState extends State<MealCategoryListScreen> {
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
setState(() {
|
||||
_mealCategories = MealCategory.getAll();
|
||||
@ -49,7 +55,7 @@ class _MealCategoryListScreenState extends State<MealCategoryListScreen> {
|
||||
|
||||
void handleDeleteAction(MealCategory mealCategory) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(mealCategory),
|
||||
message: 'Are you sure you want to delete this Meal Category?',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/dropdown.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/accuracy.dart';
|
||||
import 'package:diameter/models/meal.dart';
|
||||
import 'package:diameter/models/meal_category.dart';
|
||||
@ -101,6 +101,23 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_valueController.dispose();
|
||||
_carbsRatioController.dispose();
|
||||
_portionSizeController.dispose();
|
||||
_carbsPerPortionController.dispose();
|
||||
_delayedBolusDurationController.dispose();
|
||||
_notesController.dispose();
|
||||
_mealSourceController.dispose();
|
||||
_mealCategoryController.dispose();
|
||||
_mealPortionTypeController.dispose();
|
||||
_portionSizeAccuracyController.dispose();
|
||||
_carbsRatioAccuracyController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -215,7 +232,7 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
||||
_meal!.delayedBolusDuration ||
|
||||
_delayedBolusPercentage != _meal!.delayedBolusPercentage ||
|
||||
_notesController.text != (_meal!.notes ?? ''))))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
@ -488,11 +505,12 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
'ADDITIONAL FIELDS',
|
||||
style: Theme.of(context).textTheme.subtitle2,
|
||||
Expanded(
|
||||
child: Text(
|
||||
'ADDITIONAL FIELDS',
|
||||
style: Theme.of(context).textTheme.subtitle2,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Icon(_isExpanded
|
||||
? Icons.expand_less
|
||||
: Icons.expand_more),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/meal.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
@ -25,6 +25,12 @@ class _MealListScreenState extends State<MealListScreen> {
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
setState(() {
|
||||
_meals = Meal.getAll();
|
||||
@ -49,7 +55,7 @@ class _MealListScreenState extends State<MealListScreen> {
|
||||
|
||||
void handleDeleteAction(Meal meal) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(meal),
|
||||
message: 'Are you sure you want to delete this Meal?',
|
||||
@ -97,42 +103,51 @@ class _MealListScreenState extends State<MealListScreen> {
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: Row(
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
Row(
|
||||
children: [
|
||||
Text(meal.mealSource.target?.value ?? ''),
|
||||
Text(meal.notes ?? ''),
|
||||
Expanded(
|
||||
child: Text(meal.mealSource.target?.value ?? ''),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: ((meal.carbsPerPortion ?? 0) > 0)
|
||||
? [
|
||||
Text(meal.carbsPerPortion!.toStringAsPrecision(3)),
|
||||
Text(
|
||||
'${Settings.nutritionMeasurementSuffix} carbs',
|
||||
textScaleFactor: 0.75),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: (meal.mealPortionType.hasValue)
|
||||
? [
|
||||
Text(meal.portionSize?.toStringAsPrecision(3) ?? ''),
|
||||
Text(
|
||||
'${Settings.nutritionMeasurementSuffix}$portionType',
|
||||
textAlign: TextAlign.center,
|
||||
textScaleFactor: 0.75
|
||||
),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: ((meal.carbsPerPortion ?? 0) > 0)
|
||||
? [
|
||||
Text(meal.carbsPerPortion!.toStringAsPrecision(3)),
|
||||
Text(
|
||||
'${Settings.nutritionMeasurementSuffix} carbs',
|
||||
textScaleFactor: 0.75),
|
||||
]
|
||||
: [],
|
||||
meal.notes != null && meal.notes!.trim() != '' ? Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(meal.notes ?? '')),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: (meal.mealPortionType.hasValue)
|
||||
? [
|
||||
Text(meal.portionSize!.toStringAsPrecision(3)),
|
||||
Text(
|
||||
'${Settings.nutritionMeasurementSuffix}$portionType',
|
||||
textAlign: TextAlign.center,
|
||||
textScaleFactor: 0.75
|
||||
),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
),
|
||||
) : Container(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -40,6 +40,14 @@ class _MealPortionTypeDetailScreenState
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_valueController.dispose();
|
||||
_notesController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -82,7 +90,7 @@ class _MealPortionTypeDetailScreenState
|
||||
(_valueController.text != _mealPortionType!.value ||
|
||||
_notesController.text !=
|
||||
(_mealPortionType!.notes ?? ''))))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
|
||||
@ -26,6 +26,12 @@ class _MealPortionTypeListScreenState extends State<MealPortionTypeListScreen> {
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
setState(() {
|
||||
_mealPortionTypes = MealPortionType.getAll();
|
||||
@ -50,7 +56,7 @@ class _MealPortionTypeListScreenState extends State<MealPortionTypeListScreen> {
|
||||
|
||||
void handleDeleteAction(MealPortionType mealPortionType) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(mealPortionType),
|
||||
message: 'Are you sure you want to delete this Meal Portion Type?',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/dropdown.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/accuracy.dart';
|
||||
import 'package:diameter/models/meal_category.dart';
|
||||
import 'package:diameter/models/meal_portion_type.dart';
|
||||
@ -77,6 +77,18 @@ class _MealSourceDetailScreenState extends State<MealSourceDetailScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_valueController.dispose();
|
||||
_notesController.dispose();
|
||||
_defaultCarbsRatioAccuracyController.dispose();
|
||||
_defaultPortionSizeAccuracyController.dispose();
|
||||
_defaultMealCategoryController.dispose();
|
||||
_defaultMealPortionTypeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
if (widget.id != 0) {
|
||||
setState(() {
|
||||
@ -132,7 +144,7 @@ class _MealSourceDetailScreenState extends State<MealSourceDetailScreen> {
|
||||
_defaultMealPortionType !=
|
||||
_mealSource!.defaultMealPortionType.target ||
|
||||
_notesController.text != (_mealSource!.notes ?? ''))))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/meal_source.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
import 'package:diameter/navigation.dart';
|
||||
@ -25,6 +25,12 @@ class _MealSourceListScreenState extends State<MealSourceListScreen> {
|
||||
reload();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void reload({String? message}) {
|
||||
setState(() {
|
||||
_mealSources = MealSource.getAll();
|
||||
@ -49,7 +55,7 @@ class _MealSourceListScreenState extends State<MealSourceListScreen> {
|
||||
|
||||
void handleDeleteAction(MealSource mealSource) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(mealSource),
|
||||
message: 'Are you sure you want to delete this Meal Source?',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:diameter/components/detail.dart';
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/dropdown.dart';
|
||||
import 'package:diameter/components/forms.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
|
||||
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||
import 'package:diameter/models/ingredient.dart';
|
||||
import 'package:diameter/models/meal.dart';
|
||||
import 'package:diameter/models/recipe.dart';
|
||||
@ -31,11 +31,11 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
final _nameController = TextEditingController(text: '');
|
||||
final _servingsController = TextEditingController(text: '');
|
||||
final _notesController = TextEditingController(text: '');
|
||||
|
||||
double _servings = 1;
|
||||
|
||||
final List<TextEditingController> _ingredientControllers = [];
|
||||
final List<TextEditingController> _ingredientAmountControllers = [];
|
||||
|
||||
List<Meal> _meals = [];
|
||||
|
||||
@ -49,15 +49,13 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
|
||||
|
||||
if (_recipe != null) {
|
||||
_nameController.text = _recipe!.name;
|
||||
_servingsController.text = (_recipe!.servings ?? '').toString();
|
||||
_servings = _recipe!.servings ?? 1;
|
||||
_notesController.text = _recipe!.notes ?? '';
|
||||
|
||||
if (_ingredients.isNotEmpty) {
|
||||
for (Ingredient ingredient in _ingredients) {
|
||||
_ingredientControllers.add(
|
||||
TextEditingController(text: ingredient.ingredient.target?.value));
|
||||
_ingredientAmountControllers
|
||||
.add(TextEditingController(text: ingredient.amount.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -91,7 +89,6 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
|
||||
newIngredient.recipe.target = _recipe;
|
||||
_ingredients.add(newIngredient);
|
||||
_ingredientControllers.add(TextEditingController(text: ''));
|
||||
_ingredientAmountControllers.add(TextEditingController(text: ''));
|
||||
});
|
||||
}
|
||||
|
||||
@ -103,7 +100,7 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
|
||||
Recipe recipe = Recipe(
|
||||
id: widget.id,
|
||||
name: _nameController.text,
|
||||
servings: double.tryParse(_servingsController.text),
|
||||
servings: _servings,
|
||||
notes: _notesController.text,
|
||||
);
|
||||
Recipe.put(recipe);
|
||||
@ -145,14 +142,13 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
|
||||
if (Settings.get().showConfirmationDialogOnCancel &&
|
||||
((_isNew &&
|
||||
(_nameController.text != '' ||
|
||||
_servingsController.text != '' ||
|
||||
_servings != 1 ||
|
||||
_notesController.text != '')) ||
|
||||
(!_isNew &&
|
||||
(_nameController.text != _recipe!.name ||
|
||||
_servingsController.text !=
|
||||
(_recipe!.servings ?? '').toString() ||
|
||||
_servings != _recipe!.servings ||
|
||||
_notesController.text != (_recipe!.notes ?? ''))))) {
|
||||
Dialogs.showCancelConfirmationDialog(
|
||||
DialogUtils.showCancelConfirmationDialog(
|
||||
context: context,
|
||||
isNew: _isNew,
|
||||
onSave: handleSaveAction,
|
||||
@ -190,19 +186,19 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
|
||||
return null;
|
||||
},
|
||||
),
|
||||
NumberFormField(
|
||||
controller: _servingsController,
|
||||
label: 'Servings',
|
||||
suffix: ' portions',
|
||||
min: 0,
|
||||
onChanged: (value) {
|
||||
if ((value ?? 0) >= 0) {
|
||||
setState(() {
|
||||
_servingsController.text = (value ?? 0).toString();
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
// NumberFormField(
|
||||
// value: _servings,
|
||||
// label: 'Servings',
|
||||
// suffix: ' portions',
|
||||
// min: 0,
|
||||
// onChanged: (value) {
|
||||
// if (value != null && value >= 0) {
|
||||
// setState(() {
|
||||
// _servings = value.toDouble();
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
TextFormField(
|
||||
keyboardType: TextInputType.multiline,
|
||||
controller: _notesController,
|
||||
@ -285,25 +281,23 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: NumberFormField(
|
||||
controller:
|
||||
_ingredientAmountControllers[index],
|
||||
label: 'Amount',
|
||||
suffix: Settings.nutritionMeasurementSuffix,
|
||||
min: 0,
|
||||
onChanged: (value) {
|
||||
if ((value ?? 0) >= 0) {
|
||||
setState(() {
|
||||
_ingredients[index].amount = value ?? 0;
|
||||
_ingredientAmountControllers[index]
|
||||
.text = (value ?? 0).toString();
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(top: 10.0),
|
||||
// child: NumberFormField(
|
||||
// controller:
|
||||
// _ingredients[index].amount,
|
||||
// label: 'Amount',
|
||||
// suffix: Settings.nutritionMeasurementSuffix,
|
||||
// min: 0,
|
||||
// onChanged: (value) {
|
||||
// if (value != null && value >= 0) {
|
||||
// setState(() {
|
||||
// _ingredients[index].amount = value.toDouble();
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/utils/dialog_utils.dart';
|
||||
import 'package:diameter/models/ingredient.dart';
|
||||
import 'package:diameter/models/recipe.dart';
|
||||
import 'package:diameter/models/settings.dart';
|
||||
@ -50,7 +50,7 @@ class _RecipeListScreenState extends State<RecipeListScreen> {
|
||||
|
||||
void handleDeleteAction(Recipe recipe) async {
|
||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: () => onDelete(recipe),
|
||||
message: 'Are you sure you want to delete this Recipe?',
|
||||
|
@ -1,9 +1,12 @@
|
||||
import 'package:diameter/components/dialogs.dart';
|
||||
import 'package:diameter/components/dropdown.dart';
|
||||
import 'package:diameter/components/forms.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';
|
||||
@ -17,29 +20,33 @@ class SettingsScreen extends StatefulWidget {
|
||||
class _SettingsScreenState extends State<SettingsScreen> {
|
||||
late Settings _settings;
|
||||
|
||||
final TextEditingController _nutritionMeasurementLabelController = TextEditingController(text: '');
|
||||
final TextEditingController _glucoseMeasurementLabelController = TextEditingController(text: '');
|
||||
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 String _dateFormat;
|
||||
// late String? _longDateFormat;
|
||||
// late String _timeFormat;
|
||||
// late String? _longTimeFormat;
|
||||
|
||||
late bool _showConfirmationDialogOnCancel;
|
||||
late bool _showConfirmationDialogOnDelete;
|
||||
late bool _showConfirmationDialogOnStopEvent;
|
||||
|
||||
// late int _lowGlucoseMgPerDl;
|
||||
// late int _moderateGlucoseMgPerDl;
|
||||
// late int _highGlucoseMgPerDl;
|
||||
// late double _lowGlucoseMmolPerL;
|
||||
// late double _moderateGlucoseMmolPerL;
|
||||
// late double _highGlucoseMmolPerDl;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -48,27 +55,50 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
nutritionMeasurementLabels[_settings.nutritionMeasurementIndex];
|
||||
_glucoseMeasurementLabelController.text =
|
||||
glucoseMeasurementLabels[_settings.glucoseMeasurementIndex];
|
||||
_onlyDisplayActiveGlucoseMeasurement = _settings.glucoseDisplayModeIndex == GlucoseDisplayMode.activeOnly.index;
|
||||
_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;
|
||||
_settings.glucoseDisplayModeIndex == GlucoseDisplayMode.both.index ||
|
||||
_settings.glucoseDisplayModeIndex ==
|
||||
GlucoseDisplayMode.bothForDetail.index;
|
||||
_displayBothGlucoseMeasurementsInListView =
|
||||
_settings.glucoseDisplayModeIndex == GlucoseDisplayMode.both.index ||
|
||||
_settings.glucoseDisplayModeIndex == GlucoseDisplayMode.bothForList.index;
|
||||
// _dateFormat = _settings.dateFormat;
|
||||
// _longDateFormat = _settings.longDateFormat;
|
||||
// _timeFormat = _settings.timeFormat;
|
||||
// _longTimeFormat = _settings.longTimeFormat;
|
||||
_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;
|
||||
// _lowGlucoseMgPerDl = _settings.lowGlucoseMgPerDl;
|
||||
// _moderateGlucoseMgPerDl = _settings.moderateGlucoseMgPerDl;
|
||||
// _highGlucoseMgPerDl = _settings.highGlucoseMgPerDl;
|
||||
// _lowGlucoseMmolPerL = _settings.lowGlucoseMmolPerL;
|
||||
// _moderateGlucoseMmolPerL = _settings.moderateGlucoseMmolPerL;
|
||||
// _highGlucoseMmolPerDl = _settings.highGlucoseMmolPerDl;
|
||||
}
|
||||
|
||||
@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}) {
|
||||
@ -92,42 +122,35 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
void saveSettings() {
|
||||
Settings.put(Settings(
|
||||
id: _settings.id,
|
||||
nutritionMeasurementIndex:
|
||||
nutritionMeasurementLabels.indexOf(_nutritionMeasurementLabelController.text),
|
||||
glucoseMeasurementIndex:
|
||||
glucoseMeasurementLabels.indexOf(_glucoseMeasurementLabelController.text),
|
||||
nutritionMeasurementIndex: nutritionMeasurementLabels
|
||||
.indexOf(_nutritionMeasurementLabelController.text),
|
||||
glucoseMeasurementIndex: glucoseMeasurementLabels
|
||||
.indexOf(_glucoseMeasurementLabelController.text),
|
||||
glucoseDisplayModeIndex: _onlyDisplayActiveGlucoseMeasurement
|
||||
? GlucoseDisplayMode.activeOnly.index
|
||||
: _displayBothGlucoseMeasurementsInDetailView && _displayBothGlucoseMeasurementsInListView
|
||||
? GlucoseDisplayMode.both.index
|
||||
? GlucoseDisplayMode.activeOnly.index
|
||||
: _displayBothGlucoseMeasurementsInDetailView &&
|
||||
_displayBothGlucoseMeasurementsInListView
|
||||
? GlucoseDisplayMode.both.index
|
||||
: _displayBothGlucoseMeasurementsInDetailView
|
||||
? GlucoseDisplayMode.bothForDetail.index
|
||||
? GlucoseDisplayMode.bothForDetail.index
|
||||
: GlucoseDisplayMode.bothForList.index,
|
||||
// dateFormat: _dateFormat,
|
||||
// longDateFormat: _longDateFormat,
|
||||
// timeFormat: _timeFormat,
|
||||
// longTimeFormat: _longTimeFormat,
|
||||
// showConfirmationDialogOnCancel: _showConfirmationDialogOnCancel,
|
||||
// showConfirmationDialogOnDelete: _showConfirmationDialogOnDelete,
|
||||
// showConfirmationDialogOnStopEvent: _showConfirmationDialogOnStopEvent,
|
||||
// lowGlucoseMgPerDl: _dateFormat: _dateFormat,
|
||||
// longDateFormat: _longDateFormat,
|
||||
// timeFormat: _timeFormat,
|
||||
// longTimeFormat: _longTimeFormat,
|
||||
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,
|
||||
// lowGlucoseMgPerDl: _lowGlucoseMgPerDl,
|
||||
// moderateGlucoseMgPerDl: _moderateGlucoseMgPerDl,
|
||||
// highGlucoseMgPerDl: _highGlucoseMgPerDl,
|
||||
// lowGlucoseMmolPerL: _lowGlucoseMmolPerL,
|
||||
// moderateGlucoseMmolPerL: _moderateGlucoseMmolPerL,
|
||||
// highGlucoseMmolPerDl: _highGlucoseMmolPerDl,lowGlucoseMgPerDl,
|
||||
// moderateGlucoseMgPerDl: _moderateGlucoseMgPerDl,
|
||||
// highGlucoseMgPerDl: _highGlucoseMgPerDl,
|
||||
// lowGlucoseMmolPerL: _lowGlucoseMmolPerL,
|
||||
// moderateGlucoseMmolPerL: _moderateGlucoseMmolPerL,
|
||||
// highGlucoseMmolPerDl: _highGlucoseMmolPerDl,
|
||||
));
|
||||
reload(message: 'Settings updated');
|
||||
}
|
||||
@ -138,7 +161,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
}
|
||||
|
||||
void handleResetAction() async {
|
||||
Dialogs.showConfirmationDialog(
|
||||
DialogUtils.showConfirmationDialog(
|
||||
context: context,
|
||||
onConfirm: onReset,
|
||||
message: 'Are you sure you want to reset all settings?',
|
||||
@ -153,111 +176,399 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
),
|
||||
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: Row(
|
||||
children: [
|
||||
Text(
|
||||
'MEASUREMENTS',
|
||||
style: Theme.of(context).textTheme.subtitle2,
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
AutoCompleteDropdownButton<String>(
|
||||
controller: _nutritionMeasurementLabelController,
|
||||
selectedItem: _nutritionMeasurementLabelController.text,
|
||||
label: 'Preferred Nutrition Measurement',
|
||||
items: nutritionMeasurementLabels,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_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) {
|
||||
setState(() {
|
||||
_glucoseMeasurementLabelController.text = value ?? '';
|
||||
});
|
||||
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: Row(
|
||||
children: [
|
||||
Text(
|
||||
'CONFIRMATION PROMPTS',
|
||||
style: Theme.of(context).textTheme.subtitle2,
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
_measurementsIsExpanded = !_measurementsIsExpanded;
|
||||
}),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'MEASUREMENTS',
|
||||
style: Theme.of(context).textTheme.subtitle2,
|
||||
),
|
||||
),
|
||||
),
|
||||
BooleanFormField(
|
||||
value: _showConfirmationDialogOnCancel,
|
||||
label: 'on cancelling edit or creation of a record if changes have already been made',
|
||||
onChanged: (value) {
|
||||
_showConfirmationDialogOnCancel = value;
|
||||
saveSettings();
|
||||
},
|
||||
Icon(_measurementsIsExpanded
|
||||
? Icons.expand_less
|
||||
: Icons.expand_more),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
BooleanFormField(
|
||||
value: _showConfirmationDialogOnDelete,
|
||||
label: 'on deleting a record',
|
||||
onChanged: (value) {
|
||||
_showConfirmationDialogOnDelete = value;
|
||||
saveSettings();
|
||||
},
|
||||
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();
|
||||
},
|
||||
),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
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(() {
|
||||
_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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -10,7 +10,8 @@ class DateTimeUtils {
|
||||
return fallback;
|
||||
}
|
||||
DateTime localDate = date.toLocal();
|
||||
final DateFormat formatter = DateFormat('${Settings.get().dateFormat} ${Settings.get().timeFormat}');
|
||||
final DateFormat formatter =
|
||||
DateFormat('${Settings.get().dateFormat} ${Settings.get().timeFormat}');
|
||||
return formatter.format(localDate);
|
||||
}
|
||||
|
||||
@ -19,7 +20,8 @@ class DateTimeUtils {
|
||||
return fallback;
|
||||
}
|
||||
DateTime localDate = date.toLocal();
|
||||
final DateFormat formatter = DateFormat(Settings.get().longDateFormat ?? Settings.get().dateFormat);
|
||||
final DateFormat formatter =
|
||||
DateFormat(Settings.get().longDateFormat ?? Settings.get().dateFormat);
|
||||
return formatter.format(localDate);
|
||||
}
|
||||
|
||||
@ -29,8 +31,9 @@ class DateTimeUtils {
|
||||
return fallback;
|
||||
}
|
||||
DateTime localDate = date.toLocal();
|
||||
final DateFormat formatter = DateFormat(
|
||||
longFormat == true ? Settings.get().longTimeFormat ?? Settings.get().timeFormat : Settings.get().timeFormat);
|
||||
final DateFormat formatter = DateFormat(longFormat == true
|
||||
? Settings.get().longTimeFormat ?? Settings.get().timeFormat
|
||||
: Settings.get().timeFormat);
|
||||
return formatter.format(localDate);
|
||||
}
|
||||
|
||||
@ -39,8 +42,9 @@ class DateTimeUtils {
|
||||
if (time == null) {
|
||||
return fallback;
|
||||
}
|
||||
final DateFormat formatter = DateFormat(
|
||||
longFormat == true ? Settings.get().longTimeFormat ?? Settings.get().timeFormat : Settings.get().timeFormat);
|
||||
final DateFormat formatter = DateFormat(longFormat == true
|
||||
? Settings.get().longTimeFormat ?? Settings.get().timeFormat
|
||||
: Settings.get().timeFormat);
|
||||
return formatter.format(convertTimeOfDayToDateTime(time));
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Dialogs {
|
||||
class DialogUtils {
|
||||
static void showCancelConfirmationDialog(
|
||||
{required BuildContext context,
|
||||
required bool isNew,
|
@ -1,11 +1,28 @@
|
||||
import 'dart:math';
|
||||
|
||||
class Utils {
|
||||
static double roundToDecimalPlaces(double value, int places) {
|
||||
double mod = pow(10.0, places).toDouble();
|
||||
static double roundToDecimalPlaces(double value, int precision) {
|
||||
double mod = pow(10.0, precision).toDouble();
|
||||
return ((value * mod).round().toDouble() / mod);
|
||||
}
|
||||
|
||||
static double addDoublesWithPrecision(double a, double b, int precision) {
|
||||
double mod = pow(10.0, precision).toDouble();
|
||||
double difference = (a * mod) + (b * mod);
|
||||
return difference.round() / mod;
|
||||
}
|
||||
|
||||
static int getFractionDigitsLength(double value) {
|
||||
final fractionDigits = value.toString().split('.');
|
||||
return fractionDigits[1].length;
|
||||
}
|
||||
|
||||
static String toStringMatchingTemplateFractionPrecision(
|
||||
double value, double template) {
|
||||
final precision = getFractionDigitsLength(template);
|
||||
return value.toStringAsFixed(precision);
|
||||
}
|
||||
|
||||
static double convertMgPerDlToMmolPerL(int mgPerDl) {
|
||||
return Utils.roundToDecimalPlaces(mgPerDl * 0.0555, 2);
|
||||
}
|
||||
@ -14,18 +31,21 @@ class Utils {
|
||||
return (mmolPerL * 18.018).round();
|
||||
}
|
||||
|
||||
static double calculateCarbs(
|
||||
double carbsRatio, double portionSize) {
|
||||
static double calculateCarbs(double carbsRatio, double portionSize) {
|
||||
return Utils.roundToDecimalPlaces(carbsRatio * portionSize / 100, 2);
|
||||
}
|
||||
|
||||
static double calculateCarbsRatio(
|
||||
double carbsPerPortion, double portionSize) {
|
||||
return portionSize > 0 ? Utils.roundToDecimalPlaces(carbsPerPortion * 100 / portionSize, 2) : 0;
|
||||
return portionSize > 0
|
||||
? Utils.roundToDecimalPlaces(carbsPerPortion * 100 / portionSize, 2)
|
||||
: 0;
|
||||
}
|
||||
|
||||
static double calculatePortionSize(
|
||||
double carbsRatio, double carbsPerPortion) {
|
||||
return carbsRatio > 0 ? Utils.roundToDecimalPlaces(carbsPerPortion * 100 / carbsRatio, 2) : 0;
|
||||
return carbsRatio > 0
|
||||
? Utils.roundToDecimalPlaces(carbsPerPortion * 100 / carbsRatio, 2)
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
|
BIN
objectbox/data.mdb
Normal file
BIN
objectbox/data.mdb
Normal file
Binary file not shown.
BIN
objectbox/lock.mdb
Normal file
BIN
objectbox/lock.mdb
Normal file
Binary file not shown.
273
pubspec.lock
273
pubspec.lock
@ -141,48 +141,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
connectivity_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: connectivity_plus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
connectivity_plus_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: connectivity_plus_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
connectivity_plus_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: connectivity_plus_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
connectivity_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: connectivity_plus_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
connectivity_plus_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: connectivity_plus_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0+1"
|
||||
connectivity_plus_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: connectivity_plus_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -211,20 +169,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.6"
|
||||
dio:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.4"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -277,11 +221,6 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -303,13 +242,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.13.4"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -324,13 +256,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
idb_shim:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: idb_shim
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -380,6 +305,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.11"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -394,13 +326,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
mime_type:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime_type
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -408,13 +333,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
nm:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nm
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
objectbox:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -443,62 +361,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
package_info_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
package_info_plus_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
package_info_plus_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
package_info_plus_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
package_info_plus_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
parse_server_sdk:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: parse_server_sdk
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
parse_server_sdk_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: parse_server_sdk_flutter
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -555,13 +417,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.4.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -611,76 +466,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
sembast:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sembast
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
sembast_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sembast_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1+1"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.9"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.9"
|
||||
shared_preferences_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
shared_preferences_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -714,20 +499,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
sqflite:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1+1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -756,13 +527,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -776,7 +540,7 @@ packages:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.3"
|
||||
version: "0.4.8"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -791,13 +555,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -833,20 +590,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.3.1"
|
||||
xxtea:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xxtea
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -9,14 +9,11 @@ environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
parse_server_sdk_flutter: ^3.1.0
|
||||
flutter:
|
||||
sdk: flutter
|
||||
sqflite: ^2.0.0+4
|
||||
path_provider: ^2.0.5
|
||||
cupertino_icons: ^1.0.2
|
||||
flex_color_scheme: ^3.0.1
|
||||
shared_preferences: ^2.0.8
|
||||
intl: ^0.17.0
|
||||
objectbox: ^1.2.0
|
||||
objectbox_sync_flutter_libs: any
|
||||
|
BIN
sync-server
Executable file
BIN
sync-server
Executable file
Binary file not shown.
12
sync-server-config.js
Normal file
12
sync-server-config.js
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"dbDirectory": "objectbox",
|
||||
"dbMaxSize": "100G",
|
||||
"modelFile": "lib/objectbox-model.json",
|
||||
"bind": "ws://192.168.1.184:9999",
|
||||
"browserBind": "http://127.0.0.1:9980",
|
||||
"browserThreads": 4,
|
||||
"certificatePath": "",
|
||||
"auth": {
|
||||
"sharedSecret": "m4Gwehzgv18jZ5gCVUBZl5li3Z0FX2Yb"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user