sync server support, additional settings, number field component, usability improvements

This commit is contained in:
spinel 2022-01-22 23:37:02 +01:00
parent 6b4f588d5d
commit 09c5230caf
55 changed files with 2351 additions and 1627 deletions

102
TODO
View File

@ -1,53 +1,95 @@
MAIN TASKS: MAIN TASKS:
General/Framework: Components/Framework:
☐ create app icon ☐ update number fields to use corresponding components
☐ show indicator and make all fields readonly if user somehow gets to a deleted record detail view ☐ meal detail (carbs ratio, portion size, carbs per portion)
☐ clean up controllers (dispose method of each stateful widget) ☐ log meal detail (amount, carbs ratio, portion size, carbs per portion)
☐ account for deleted/disabled elements in dropdowns ☐ add "set manually" switch (like in log bolus detail) wherever parameters can be calculated from others
☐ check through all detail forms and set required fields/according messages ☐ 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) ☐ 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 ☐ change placement of delete and floating button because its very easy to accidentally hit delete
Recipe: ☐ implement deletion by swiping left on item instead?
✔ recipe list screen @done(21-12-11 22:01) ☐ check for changes before navigating as well (not just on cancel)
✔ 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)
FUTURE TASKS: Log Overview:
General/Framework: ☐ only show current day
☐ setup objectbox sync server ☐ add calendar field on top to navigate
☐ add explanations to each section ☐ use currently selected day when adding a log entry
☐ evaluate if some fields should be readonly instead of completely hidden Event Types:
☐ alternate languages ☐ add pagination
☐ log hba1c
Reports: Reports:
☐ evaluate what type of reports there should be ☐ 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 ☐ meal tweaking
☐ bolus tweaking ☐ bolus tweaking
☐ basal test
☐ daily graph (showing glucose curve, events, boli and meals) ☐ daily graph (showing glucose curve, events, boli and meals)
Log Overview: Log Overview:
☐ add pagination
☐ add filters ☐ add filters
Log Entry: Log Entry:
☐ check if there is still an active bolus when suggesting glucose bolus ☐ check if there is still an active bolus when suggesting glucose bolus
Event Types: Event Types:
☐ add pagination ☐ add colors as indicators for log entries (and later graphs in reports)
☐ implement reminders as push notifications ☐ implement reminders as push notifications
Settings: Settings:
☐ add option to hide extra customization options (ie. changing pre calculated values)? ☐ add option to hide extra customization options (ie. changing pre calculated values)?
☐ option to switch theme ☐ 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: 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 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) ✔ 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) ✔ give option to specify quantity @done(21-12-11 01:28) @project(MAIN TASKS.Log Entry)

View File

@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 30 compileSdkVersion 31
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.50' ext.kotlin_version = '1.6.10'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()

View File

@ -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),
),
],
);
}
}

View 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,
);
});
}
}

View 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);
},
);
}
}

View 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(),
],
);
}
}

View 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 ?? [],
),
),
],
),
),
);
}
}

View 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(),
],
);
}
}

View 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);
},
);
}
}

View 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,
);
}
}

View File

@ -37,7 +37,7 @@ Future<void> main() async {
Sync.isAvailable(); Sync.isAvailable();
SyncClient syncClient = Sync.client( SyncClient syncClient = Sync.client(
objectBox.store, objectBox.store,
'wss://127.0.0.1:9999', 'ws://192.168.1.184:9999',
SyncCredentials.sharedSecretString(secret) SyncCredentials.sharedSecretString(secret)
); );
syncClient.start(); syncClient.start();

View File

@ -53,6 +53,11 @@ class Settings {
int targetGlucoseMgPerDl; int targetGlucoseMgPerDl;
double targetGlucoseMmolPerL; double targetGlucoseMmolPerL;
double insulinIncrements;
double nutritionIncrements;
double mmolPerLIncrements;
double amountIncrements;
String dateFormat; String dateFormat;
String? longDateFormat; String? longDateFormat;
String timeFormat; String timeFormat;
@ -70,6 +75,10 @@ class Settings {
this.nutritionMeasurementIndex = 0, this.nutritionMeasurementIndex = 0,
this.glucoseDisplayModeIndex = 0, this.glucoseDisplayModeIndex = 0,
this.glucoseMeasurementIndex = 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.dateFormat = 'MM/dd/yy',
this.longDateFormat = 'MMMM dd, yyyy', this.longDateFormat = 'MMMM dd, yyyy',
this.timeFormat = 'HH:mm', this.timeFormat = 'HH:mm',
@ -78,7 +87,7 @@ class Settings {
this.showConfirmationDialogOnDelete = true, this.showConfirmationDialogOnDelete = true,
this.showConfirmationDialogOnStopEvent = true, this.showConfirmationDialogOnStopEvent = true,
this.targetGlucoseMgPerDl = 100, this.targetGlucoseMgPerDl = 100,
this.targetGlucoseMmolPerL = 5.49, this.targetGlucoseMmolPerL = 5.5,
this.useDarkTheme = false, this.useDarkTheme = false,
}); });
@ -105,6 +114,10 @@ class Settings {
static int get targetMgPerDl => get().targetGlucoseMgPerDl; static int get targetMgPerDl => get().targetGlucoseMgPerDl;
static double get targetMmolPerL => get().targetGlucoseMmolPerL; 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 => static ThemeMode get themeMode =>
get().useDarkTheme ? ThemeMode.dark : ThemeMode.light; get().useDarkTheme ? ThemeMode.dark : ThemeMode.light;

View File

@ -113,14 +113,14 @@ class _NavigationState extends State<Navigation> {
}, },
selected: widget.currentLocation == Routes.events, selected: widget.currentLocation == Routes.events,
), ),
ListTile( // ListTile(
title: const Text('Recipes'), // title: const Text('Recipes'),
leading: const Icon(Icons.local_dining), // leading: const Icon(Icons.local_dining),
onTap: () { // onTap: () {
selectDestination(Routes.recipes); // selectDestination(Routes.recipes);
}, // },
selected: Routes.recipeRoutes.contains(widget.currentLocation), // selected: Routes.recipeRoutes.contains(widget.currentLocation),
), // ),
ListTile( ListTile(
title: const Text('Meals'), title: const Text('Meals'),
leading: const Icon(Icons.dinner_dining), leading: const Icon(Icons.dinner_dining),

View File

@ -1,9 +1,11 @@
import 'package:diameter/components/detail.dart'; 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/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:flutter/material.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'; import 'package:diameter/models/accuracy.dart';
class AccuracyDetailScreen extends StatefulWidget { class AccuracyDetailScreen extends StatefulWidget {
@ -25,8 +27,9 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final _valueController = TextEditingController(text: ''); final _valueController = TextEditingController(text: '');
final _confidenceRatingController = TextEditingController(text: '');
final _notesController = TextEditingController(text: ''); final _notesController = TextEditingController(text: '');
final _confidenceRatingController =
TextEditingController(text: Accuracy.getAll().length.toString());
bool _forCarbsRatio = true; bool _forCarbsRatio = true;
bool _forPortionSize = true; bool _forPortionSize = true;
@ -39,11 +42,20 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
_forCarbsRatio = _accuracy!.forCarbsRatio; _forCarbsRatio = _accuracy!.forCarbsRatio;
_forPortionSize = _accuracy!.forPortionSize; _forPortionSize = _accuracy!.forPortionSize;
_confidenceRatingController.text = _confidenceRatingController.text =
(_accuracy!.confidenceRating ?? '').toString(); (_accuracy!.confidenceRating ?? Accuracy.getAll().length).toString();
_notesController.text = _accuracy!.notes ?? ''; _notesController.text = _accuracy!.notes ?? '';
} }
} }
@override
void dispose() {
_scrollController.dispose();
_valueController.dispose();
_notesController.dispose();
_confidenceRatingController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -52,7 +64,7 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
} }
_isNew = _accuracy == null; _isNew = _accuracy == null;
setState(() { setState(() {
if (message != null) { if (message != null) {
var snackBar = SnackBar( var snackBar = SnackBar(
content: Text(message), content: Text(message),
@ -77,10 +89,11 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
forPortionSize: _forPortionSize, forPortionSize: _forPortionSize,
notes: _notesController.text, notes: _notesController.text,
); );
Accuracy.box.put(accuracy); Accuracy.put(accuracy);
Accuracy.reorder( Accuracy.reorder(
accuracy, int.tryParse(_confidenceRatingController.text)); accuracy, int.tryParse(_confidenceRatingController.text));
Navigator.pop(context, ['${_isNew ? 'New' : ''} Accuracy saved', accuracy]); Navigator.pop(
context, ['${_isNew ? 'New' : ''} Accuracy saved', accuracy]);
} }
setState(() { setState(() {
_isSaving = false; _isSaving = false;
@ -93,7 +106,8 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
(!_forCarbsRatio || (!_forCarbsRatio ||
!_forPortionSize || !_forPortionSize ||
_valueController.text != '' || _valueController.text != '' ||
int.tryParse(_confidenceRatingController.text) != null || int.tryParse(_confidenceRatingController.text) !=
Accuracy.getAll().length ||
_notesController.text != '')) || _notesController.text != '')) ||
(!_isNew && (!_isNew &&
(_forCarbsRatio != _accuracy!.forCarbsRatio || (_forCarbsRatio != _accuracy!.forCarbsRatio ||
@ -102,7 +116,7 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
int.tryParse(_confidenceRatingController.text) != int.tryParse(_confidenceRatingController.text) !=
_accuracy!.confidenceRating || _accuracy!.confidenceRating ||
(_accuracy!.notes ?? '') != _notesController.text))) { (_accuracy!.notes ?? '') != _notesController.text))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,
@ -159,12 +173,15 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
}); });
}, },
), ),
TextFormField( NumberFormField(
controller: _confidenceRatingController, controller: _confidenceRatingController,
keyboardType: TextInputType.number, label: 'Confidence Rating',
decoration: const InputDecoration( onChanged: (value) {
labelText: 'Confidence Rating', setState(() {
), _confidenceRatingController.text =
(value ?? 0).toInt().toString();
});
},
), ),
TextFormField( TextFormField(
controller: _notesController, controller: _notesController,

View File

@ -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/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/accuracy_detail.dart'; import 'package:diameter/screens/accuracy_detail.dart';
@ -24,6 +24,12 @@ class _AccuracyListScreenState extends State<AccuracyListScreen> {
reload(); reload();
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
setState(() { setState(() {
_accuracies = Accuracy.getAll(); _accuracies = Accuracy.getAll();
@ -49,7 +55,7 @@ class _AccuracyListScreenState extends State<AccuracyListScreen> {
void handleDeleteAction(Accuracy accuracy) async { void handleDeleteAction(Accuracy accuracy) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(accuracy), onConfirm: () => onDelete(accuracy),
message: 'Are you sure you want to delete this Accuracy?', message: 'Are you sure you want to delete this Accuracy?',
@ -87,77 +93,77 @@ class _AccuracyListScreenState extends State<AccuracyListScreen> {
Expanded( Expanded(
child: _accuracies.isNotEmpty child: _accuracies.isNotEmpty
? Scrollbar( ? Scrollbar(
controller: _scrollController, controller: _scrollController,
child: ReorderableListView.builder( child: ReorderableListView.builder(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
scrollController: _scrollController, scrollController: _scrollController,
itemCount: _accuracies.length, itemCount: _accuracies.length,
onReorder: (oldIndex, newIndex) { onReorder: (oldIndex, newIndex) {
Accuracy.reorder(_accuracies[oldIndex], newIndex); Accuracy.reorder(_accuracies[oldIndex], newIndex);
reload(); reload();
}, },
itemBuilder: (context, index) { itemBuilder: (context, index) {
final accuracy = _accuracies[index]; final accuracy = _accuracies[index];
return Card( return Card(
key: Key(accuracy.id.toString()), key: Key(accuracy.id.toString()),
child: ListTile( child: ListTile(
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
AccuracyDetailScreen(id: accuracy.id), AccuracyDetailScreen(id: accuracy.id),
),
).then((result) => reload(message: result?[0]));
},
title: Text(
accuracy.value.toUpperCase(),
style: Theme.of(context).textTheme.subtitle2,
), ),
leading: Row( ).then((result) => reload(message: result?[0]));
mainAxisSize: MainAxisSize.min, },
children: const [ title: Text(
Icon(Icons.reorder), 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( IconButton(
mainAxisSize: MainAxisSize.min, icon: Icon(
children: [ Icons.pie_chart,
IconButton( color: accuracy.forCarbsRatio
icon: Icon( ? Theme.of(context)
Icons.square_foot, .toggleableActiveColor
color: accuracy.forPortionSize : Theme.of(context).highlightColor,
? Theme.of(context) ),
.toggleableActiveColor onPressed: () =>
: Theme.of(context).highlightColor, handleToggleForCarbsRatioAction(
), accuracy),
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: const Icon(Icons.delete),
}), onPressed: () =>
) handleDeleteAction(accuracy),
)
],
),
),
);
}),
)
: const Center( : const Center(
child: Text('You have not created any Accuracies yet!'), child: Text('You have not created any Accuracies yet!'),
), ),

View File

@ -1,10 +1,13 @@
import 'package:diameter/components/detail.dart'; 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/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/utils/date_time_utils.dart'; import 'package:diameter/utils/date_time_utils.dart';
import 'package:diameter/utils/utils.dart';
import 'package:flutter/material.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.dart';
import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/models/basal_profile.dart';
@ -42,7 +45,8 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
final _startTimeController = TextEditingController(text: ''); final _startTimeController = TextEditingController(text: '');
final _endTimeController = TextEditingController(text: ''); final _endTimeController = TextEditingController(text: '');
final _unitsController = TextEditingController(text: ''); final _unitsController =
TextEditingController(text: 0.toStringAsPrecision(3));
@override @override
void initState() { void initState() {
@ -59,13 +63,22 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
if (_basal != null) { if (_basal != null) {
_startTime = TimeOfDay.fromDateTime(_basal!.startTime); _startTime = TimeOfDay.fromDateTime(_basal!.startTime);
_endTime = TimeOfDay.fromDateTime(_basal!.endTime); _endTime = TimeOfDay.fromDateTime(_basal!.endTime);
_unitsController.text = _basal!.units.toString(); _unitsController.text = _basal!.units.toStringAsPrecision(3);
} }
_startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime); _startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime);
_endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime); _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime);
} }
@override
void dispose() {
_scrollController.dispose();
_startTimeController.dispose();
_endTimeController.dispose();
_unitsController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -207,13 +220,13 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
_startTime.minute != _startTime.minute !=
(widget.suggestedStartTime?.minute ?? 0) || (widget.suggestedStartTime?.minute ?? 0) ||
_endTime.minute != (widget.suggestedEndTime?.minute ?? 0) || _endTime.minute != (widget.suggestedEndTime?.minute ?? 0) ||
double.tryParse(_unitsController.text) != null)) || double.tryParse(_unitsController.text) != 0)) ||
(!_isNew && (!_isNew &&
(TimeOfDay.fromDateTime(_basal!.startTime) != _startTime || (TimeOfDay.fromDateTime(_basal!.startTime) != _startTime ||
TimeOfDay.fromDateTime(_basal!.endTime) != _endTime || TimeOfDay.fromDateTime(_basal!.endTime) != _endTime ||
(double.tryParse(_unitsController.text) ?? 0) != double.tryParse(_unitsController.text) !=
_basal!.units)))) { _basal!.units)))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,
@ -266,21 +279,19 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
), ),
], ],
), ),
TextFormField( NumberFormField(
controller: _unitsController, controller: _unitsController,
keyboardType: label: 'Units',
const TextInputType.numberWithOptions(decimal: true), suffix: 'U',
decoration: const InputDecoration( autoRoundToMultipleOfStep: true,
labelText: 'Units', step: Settings.insulinSteps,
suffixText: 'U', onChanged: (value) {
), if (value != null) {
validator: (value) { _unitsController.text =
if (value!.trim().isEmpty) { Utils.toStringMatchingTemplateFractionPrecision(
return 'Empty amount of units'; value, Settings.insulinSteps);
} }
return null; }),
},
),
], ],
), ),
], ],

View File

@ -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/models/settings.dart';
import 'package:diameter/utils/date_time_utils.dart'; import 'package:diameter/utils/date_time_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -25,6 +25,12 @@ class BasalListScreen extends StatefulWidget {
class _BasalListScreenState extends State<BasalListScreen> { class _BasalListScreenState extends State<BasalListScreen> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
widget.reload(); widget.reload();
@ -60,7 +66,7 @@ class _BasalListScreenState extends State<BasalListScreen> {
void handleDeleteAction(Basal basal) async { void handleDeleteAction(Basal basal) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(basal), onConfirm: () => onDelete(basal),
message: 'Are you sure you want to delete this Basal Rate?', message: 'Are you sure you want to delete this Basal Rate?',
@ -106,70 +112,76 @@ class _BasalListScreenState extends State<BasalListScreen> {
.isNotEmpty) { .isNotEmpty) {
return 'This rate\'s time period overlaps with another one'; return 'This rate\'s time period overlaps with another one';
} }
return null;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return widget.basalRates.isNotEmpty ? Scrollbar( return widget.basalRates.isNotEmpty
controller: _scrollController, ? Scrollbar(
child: ListView.builder( controller: _scrollController,
padding: const EdgeInsets.all(10.0), child: ListView.builder(
controller: _scrollController, padding: const EdgeInsets.all(10.0),
shrinkWrap: true, controller: _scrollController,
itemCount: widget.basalRates.length, shrinkWrap: true,
itemBuilder: (context, index) { itemCount: widget.basalRates.length,
final basal = widget.basalRates[index]; itemBuilder: (context, index) {
final error = validateTimePeriod(index); final basal = widget.basalRates[index];
return Card( final error = validateTimePeriod(index);
child: Column( return Card(
children: [ child: Column(
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: [ children: [
Expanded( error != null
child: Text( ? Padding(
'${DateTimeUtils.displayTime(basal.startTime)} - ${DateTimeUtils.displayTime(basal.endTime)}')), padding: const EdgeInsets.all(5.0),
const Spacer(), child: Row(
Expanded(child: Text('${basal.units} U')), mainAxisAlignment: MainAxisAlignment.center,
], children: [
), Icon(Icons.warning,
trailing: Row( color: Theme.of(context).errorColor),
mainAxisSize: MainAxisSize.min, Text(error,
children: [ style: TextStyle(
IconButton( color: Theme.of(context).errorColor)),
icon: const Icon( ],
Icons.delete, ),
color: Colors.blue, )
: 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!'),
);
} }
} }

View File

@ -1,11 +1,12 @@
import 'package:diameter/components/detail.dart'; 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/basal.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/basal/basal_detail.dart'; import 'package:diameter/screens/basal/basal_detail.dart';
import 'package:flutter/material.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/models/basal_profile.dart';
import 'package:diameter/screens/basal/basal_list.dart'; import 'package:diameter/screens/basal/basal_list.dart';
@ -92,6 +93,14 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
bottomNav = detailBottomRow; bottomNav = detailBottomRow;
} }
@override
void dispose() {
_scrollController.dispose();
_nameController.dispose();
_notesController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -270,7 +279,7 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
(_basalProfile!.active != _active || (_basalProfile!.active != _active ||
_basalProfile!.name != _nameController.text || _basalProfile!.name != _nameController.text ||
(_basalProfile!.notes ?? '') != _notesController.text))) { (_basalProfile!.notes ?? '') != _notesController.text))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,

View File

@ -1,5 +1,5 @@
import 'package:diameter/components/dialogs.dart'; import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
import 'package:diameter/models/basal.dart'; import 'package:diameter/models/basal.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
@ -17,13 +17,25 @@ class BasalProfileListScreen extends StatefulWidget {
class _BasalProfileListScreenState extends State<BasalProfileListScreen> { class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
late List<BasalProfile> _basalProfiles; late List<BasalProfile> _basalProfiles;
Widget banner = Container(); Widget banner = Container();
final BasalProfile? _activeProfile = BasalProfile.getActive(DateTime.now()); 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(() { setState(() {
_basalProfiles = BasalProfile.getAll(); _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) { void onDelete(BasalProfile basalProfile) {
BasalProfile.remove(basalProfile.id); BasalProfile.remove(basalProfile.id);
refresh(message: 'Basal Profile deleted'); reload(message: 'Basal Profile deleted');
} }
void handleDeleteAction(BasalProfile basalProfile) async { void handleDeleteAction(BasalProfile basalProfile) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(basalProfile), onConfirm: () => onDelete(basalProfile),
message: 'Are you sure you want to delete this Basal Profile?', message: 'Are you sure you want to delete this Basal Profile?',
@ -97,7 +130,7 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
BasalProfile.setAllInactive; BasalProfile.setAllInactive;
basalProfile.active = true; basalProfile.active = true;
BasalProfile.put(basalProfile); BasalProfile.put(basalProfile);
refresh( reload(
message: '${basalProfile.name} has been set as your active Profile'); message: '${basalProfile.name} has been set as your active Profile');
} }
} }
@ -130,7 +163,7 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
builder: (context) => builder: (context) =>
BasalProfileDetailScreen(id: basalProfile?.id ?? 0, active: active), BasalProfileDetailScreen(id: basalProfile?.id ?? 0, active: active),
), ),
).then((result) => refresh(message: result?[0])); ).then((result) => reload(message: result?[0]));
} }
void onNew(bool active) { void onNew(bool active) {
@ -141,19 +174,13 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
showDetailScreen(basalProfile: basalProfile); showDetailScreen(basalProfile: basalProfile);
} }
@override
void initState() {
super.initState();
refresh();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Basal Profiles'), title: const Text('Basal Profiles'),
actions: <Widget>[ actions: <Widget>[
IconButton(onPressed: refresh, icon: const Icon(Icons.refresh)) IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
], ],
), ),
drawer: drawer:
@ -165,8 +192,8 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
Expanded( Expanded(
child: _basalProfiles.isNotEmpty child: _basalProfiles.isNotEmpty
? Scrollbar( ? Scrollbar(
controller: _scrollController, controller: _scrollController,
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
controller: _scrollController, controller: _scrollController,
itemCount: _basalProfiles.length, itemCount: _basalProfiles.length,
@ -186,22 +213,23 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
basalProfile.id == _activeProfile?.id, basalProfile.id == _activeProfile?.id,
onTap: () => onEdit(basalProfile), onTap: () => onEdit(basalProfile),
title: Text( title: Text(
basalProfile.name.toUpperCase() + activeProfileText, basalProfile.name.toUpperCase() +
activeProfileText,
style: Theme.of(context).textTheme.subtitle2, style: Theme.of(context).textTheme.subtitle2,
), ),
subtitle: Padding( subtitle: Padding(
padding: const EdgeInsets.only(top: 10.0), padding: const EdgeInsets.only(top: 10.0),
child: Row( child: Row(
children: [ children: [
Text( Text(basalProfile.notes ?? ''),
basalProfile.notes ?? ''
),
Expanded( Expanded(
child: Column( child: Column(
children: dailyTotal > 0 children: dailyTotal > 0
? [ ? [
Text(dailyTotal.toStringAsPrecision(3)), Text(dailyTotal
const Text('U/day', textScaleFactor: 0.75), .toStringAsPrecision(3)),
const Text('U/day',
textScaleFactor: 0.75),
] ]
: [], : [],
), ),
@ -212,12 +240,21 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
IconButton(
icon: const Icon(
Icons.copy,
color: Colors.blue,
),
onPressed: () =>
handleDuplicateAction(basalProfile),
),
IconButton( IconButton(
icon: const Icon( icon: const Icon(
Icons.delete, Icons.delete,
color: Colors.blue, color: Colors.blue,
), ),
onPressed: () => handleDeleteAction(basalProfile), onPressed: () =>
handleDeleteAction(basalProfile),
), ),
], ],
), ),
@ -225,7 +262,7 @@ class _BasalProfileListScreenState extends State<BasalProfileListScreen> {
); );
}, },
), ),
) )
: const Center( : const Center(
child: Text('You have not created any Basal Profiles yet!'), child: Text('You have not created any Basal Profiles yet!'),
), ),

View File

@ -1,11 +1,13 @@
import 'package:diameter/components/detail.dart'; 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/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/utils/date_time_utils.dart'; import 'package:diameter/utils/date_time_utils.dart';
import 'package:diameter/utils/utils.dart'; import 'package:diameter/utils/utils.dart';
import 'package:flutter/material.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.dart';
import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/models/bolus_profile.dart';
@ -43,10 +45,10 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
final _startTimeController = TextEditingController(text: ''); final _startTimeController = TextEditingController(text: '');
final _endTimeController = TextEditingController(text: ''); final _endTimeController = TextEditingController(text: '');
final _unitsController = TextEditingController(text: ''); final _unitsController = TextEditingController(text: Utils.toStringMatchingTemplateFractionPrecision(0, Settings.insulinSteps));
final _carbsController = TextEditingController(text: ''); final _carbsController = TextEditingController(text: Utils.toStringMatchingTemplateFractionPrecision(0, Settings.nutritionSteps));
final _mgPerDlController = TextEditingController(text: ''); final _mgPerDlController = TextEditingController(text: '0');
final _mmolPerLController = TextEditingController(text: ''); final _mmolPerLController = TextEditingController(text: Utils.toStringMatchingTemplateFractionPrecision(0, Settings.mmolPerLSteps));
@override @override
void initState() { void initState() {
@ -74,6 +76,18 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
_endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime); _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}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -196,7 +210,8 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
).then((result) { ).then((result) {
Navigator.pop( Navigator.pop(
context, context,
['New Bolus Rate${result[1] != null ? 's' : ''} saved', bolus] + [result[1]], ['New Bolus Rate${result[1] != null ? 's' : ''} saved', bolus] +
[result[1]],
); );
}); });
} else { } else {
@ -235,7 +250,7 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
_bolus!.mgPerDl || _bolus!.mgPerDl ||
(double.tryParse(_mmolPerLController.text) ?? 0) != (double.tryParse(_mmolPerLController.text) ?? 0) !=
_bolus!.mmolPerL)))) { _bolus!.mmolPerL)))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,
@ -245,30 +260,26 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
} }
} }
void convertBetweenMgPerDlAndMmolPerL({GlucoseMeasurement? calculateFrom}) { void convertBetweenMgPerDlAndMmolPerL(double? value) async {
int? mgPerDl; if (value != null) {
double? mmolPerL; if (Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl &&
_mgPerDlController.text != '') {
if (calculateFrom != GlucoseMeasurement.mmolPerL && _mgPerDlController.text = value.toInt().toString();
_mgPerDlController.text != '') { setState(() {
mgPerDl = int.tryParse(_mgPerDlController.text); _mmolPerLController.text =
} Utils.convertMgPerDlToMmolPerL(value.toInt()).toString();
if (calculateFrom != GlucoseMeasurement.mgPerDl && });
_mmolPerLController.text != '') { }
mmolPerL = double.tryParse(_mmolPerLController.text); if (Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL &&
} _mmolPerLController.text != '') {
if (mgPerDl != null && mmolPerL == null) {
setState(() {
_mmolPerLController.text = _mmolPerLController.text =
Utils.convertMgPerDlToMmolPerL(mgPerDl!).toString(); Utils.toStringMatchingTemplateFractionPrecision(
}); value, Settings.mmolPerLSteps);
} setState(() {
if (mmolPerL != null && mgPerDl == null) { _mgPerDlController.text =
setState(() { Utils.convertMmolPerLToMgPerDl(value.toDouble()).toString();
_mgPerDlController.text = });
Utils.convertMmolPerLToMgPerDl(mmolPerL!).toString(); }
});
} }
} }
@ -315,34 +326,32 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
), ),
], ],
), ),
TextFormField( NumberFormField(
decoration: const InputDecoration(
labelText: 'Units',
suffixText: 'U',
),
controller: _unitsController, controller: _unitsController,
keyboardType: label: 'Units',
const TextInputType.numberWithOptions(decimal: true), suffix: 'U',
validator: (value) { autoRoundToMultipleOfStep: true,
if (value!.trim().isEmpty) { step: Settings.insulinSteps,
return 'Empty amount of units'; onChanged: (value) {
if (value != null) {
_unitsController.text =
Utils.toStringMatchingTemplateFractionPrecision(
value, Settings.insulinSteps);
} }
return null;
}, },
), ),
TextFormField( NumberFormField(
decoration: InputDecoration(
labelText: 'per carbs',
suffixText: Settings.nutritionMeasurementSuffix,
),
controller: _carbsController, controller: _carbsController,
keyboardType: label: 'per carbs',
const TextInputType.numberWithOptions(decimal: true), suffix: Settings.nutritionMeasurementSuffix,
validator: (value) { autoRoundToMultipleOfStep: true,
if (value!.trim().isEmpty) { step: Settings.nutritionSteps,
return 'How many carbs does the rate make up for?'; onChanged: (value) {
if (value != null) {
_carbsController.text =
Utils.toStringMatchingTemplateFractionPrecision(
value, Settings.nutritionSteps);
} }
return null;
}, },
), ),
Row( Row(
@ -354,42 +363,18 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
Settings.glucoseDisplayMode == Settings.glucoseDisplayMode ==
GlucoseDisplayMode.bothForDetail GlucoseDisplayMode.bothForDetail
? Expanded( ? Expanded(
child: TextFormField( flex: Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl ? 2 : 1,
decoration: const InputDecoration( child: NumberFormField(
labelText: 'per mg/dl', label: 'per mg/dl',
suffixText: 'mg/dl', suffix: 'mg/dl',
),
readOnly: Settings.glucoseMeasurement == readOnly: Settings.glucoseMeasurement ==
GlucoseMeasurement.mmolPerL, GlucoseMeasurement.mmolPerL,
showSteppers: Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl,
controller: _mgPerDlController, controller: _mgPerDlController,
onChanged: (_) async { onChanged: convertBetweenMgPerDlAndMmolPerL,
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;
},
), ),
) )
: Container(), : Container(),
Settings.glucoseDisplayMode == GlucoseDisplayMode.both ||
Settings.glucoseDisplayMode ==
GlucoseDisplayMode.bothForDetail
? IconButton(
onPressed: () => convertBetweenMgPerDlAndMmolPerL(
calculateFrom: GlucoseMeasurement.mmolPerL),
icon: const Icon(Icons.calculate),
)
: Container(),
Settings.glucoseMeasurement == Settings.glucoseMeasurement ==
GlucoseMeasurement.mmolPerL || GlucoseMeasurement.mmolPerL ||
[ [
@ -397,44 +382,19 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
GlucoseDisplayMode.bothForDetail GlucoseDisplayMode.bothForDetail
].contains(Settings.glucoseDisplayMode) ].contains(Settings.glucoseDisplayMode)
? Expanded( ? Expanded(
child: TextFormField( flex: Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL ? 2 : 1,
decoration: const InputDecoration( child: NumberFormField(
labelText: 'per mmol/l', label: 'per mmol/l',
suffixText: 'mmol/l', suffix: 'mmol/l',
),
readOnly: Settings.glucoseMeasurement == readOnly: Settings.glucoseMeasurement ==
GlucoseMeasurement.mgPerDl, GlucoseMeasurement.mgPerDl,
showSteppers: Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL,
controller: _mmolPerLController, controller: _mmolPerLController,
onChanged: (_) async { step: Settings.mmolPerLSteps,
await Future.delayed( onChanged: convertBetweenMgPerDlAndMmolPerL,
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;
},
), ),
) )
: Container(), : Container(),
[
GlucoseDisplayMode.both,
GlucoseDisplayMode.bothForDetail
].contains(Settings.glucoseDisplayMode)
? IconButton(
onPressed: () => convertBetweenMgPerDlAndMmolPerL(
calculateFrom: GlucoseMeasurement.mgPerDl),
icon: const Icon(Icons.calculate),
)
: Container(),
], ],
), ),
], ],

View File

@ -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/models/settings.dart';
import 'package:diameter/utils/date_time_utils.dart'; import 'package:diameter/utils/date_time_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -12,7 +12,10 @@ class BolusListScreen extends StatefulWidget {
final Function() reload; final Function() reload;
const BolusListScreen( 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); : super(key: key);
@override @override
@ -22,6 +25,12 @@ class BolusListScreen extends StatefulWidget {
class _BolusListScreenState extends State<BolusListScreen> { class _BolusListScreenState extends State<BolusListScreen> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
widget.reload(); widget.reload();
@ -57,7 +66,7 @@ class _BolusListScreenState extends State<BolusListScreen> {
void handleDeleteAction(Bolus bolus) async { void handleDeleteAction(Bolus bolus) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(bolus), onConfirm: () => onDelete(bolus),
message: 'Are you sure you want to delete this Bolus Rate?', message: 'Are you sure you want to delete this Bolus Rate?',
@ -102,100 +111,122 @@ class _BolusListScreenState extends State<BolusListScreen> {
.isNotEmpty) { .isNotEmpty) {
return 'This rate\'s time period overlaps with another one'; return 'This rate\'s time period overlaps with another one';
} }
return null;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return widget.bolusRates.isNotEmpty ? Scrollbar( return widget.bolusRates.isNotEmpty
controller: _scrollController, ? Scrollbar(
child: ListView.builder( controller: _scrollController,
padding: const EdgeInsets.all(10.0), child: ListView.builder(
controller: _scrollController, padding: const EdgeInsets.all(10.0),
shrinkWrap: true, controller: _scrollController,
itemCount: widget.bolusRates.length, shrinkWrap: true,
itemBuilder: (context, index) { itemCount: widget.bolusRates.length,
final bolus = widget.bolusRates[index]; itemBuilder: (context, index) {
final error = validateTimePeriod(index); final bolus = widget.bolusRates[index];
return Card( final error = validateTimePeriod(index);
child: Column( return Card(
children: [ child: Column(
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: [ children: [
Expanded( error != null
child: Column( ? Padding(
children: (bolus.units > 0 && bolus.carbs > 0) padding: const EdgeInsets.all(5.0),
? [ child: Row(
Text((bolus.carbs / bolus.units).toStringAsPrecision(2)), mainAxisAlignment: MainAxisAlignment.center,
Text('${Settings.nutritionMeasurementSuffix} carbs per U', children: [
textAlign: TextAlign.center, textScaleFactor: 0.75), 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),
]
: [],
),
),
],
), ),
), trailing: Row(
Expanded( mainAxisSize: MainAxisSize.min,
child: Column( children: [
children: (bolus.units > 0 && bolus.carbs > 0) IconButton(
? [ icon: const Icon(
Text((bolus.units / bolus.carbs * 12).toStringAsPrecision(2)), Icons.delete,
const Text('U per bread unit', color: Colors.blue,
textAlign: TextAlign.center, textScaleFactor: 0.75), ),
] onPressed: () => handleDeleteAction(bolus),
: [], ),
), ],
),
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),
),
],
),
),
],
), ),
)
: const Center(
child: Text('You have not created any Bolus Rates yet!'),
); );
},
),
) : const Center(
child: Text('You have not created any Bolus Rates yet!'),
);
} }
} }

View File

@ -1,11 +1,12 @@
import 'package:diameter/components/detail.dart'; 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/bolus.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/bolus/bolus_detail.dart'; import 'package:diameter/screens/bolus/bolus_detail.dart';
import 'package:flutter/material.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/models/bolus_profile.dart';
import 'package:diameter/screens/bolus/bolus_list.dart'; import 'package:diameter/screens/bolus/bolus_list.dart';
@ -90,6 +91,14 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
bottomNav = detailBottomRow; bottomNav = detailBottomRow;
} }
@override
void dispose() {
_scrollController.dispose();
_nameController.dispose();
_notesController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -269,7 +278,7 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
(_bolusProfile!.active != _active || (_bolusProfile!.active != _active ||
_bolusProfile!.name != _nameController.text || _bolusProfile!.name != _nameController.text ||
(_bolusProfile!.notes ?? '') != _notesController.text))) { (_bolusProfile!.notes ?? '') != _notesController.text))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,

View File

@ -1,5 +1,6 @@
import 'package:diameter/components/dialogs.dart'; import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/components/dropdown.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/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -22,6 +23,18 @@ class _BolusProfileListScreenState extends State<BolusProfileListScreen> {
final BolusProfile? _activeProfile = BolusProfile.getActive(DateTime.now()); final BolusProfile? _activeProfile = BolusProfile.getActive(DateTime.now());
@override
void initState() {
super.initState();
reload();
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
setState(() { setState(() {
_bolusProfiles = BolusProfile.getAll(); _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) { void onDelete(BolusProfile bolusProfile) {
BolusProfile.remove(bolusProfile.id); BolusProfile.remove(bolusProfile.id);
reload(message: 'Bolus Profile deleted'); reload(message: 'Bolus Profile deleted');
@ -84,7 +121,7 @@ class _BolusProfileListScreenState extends State<BolusProfileListScreen> {
void handleDeleteAction(BolusProfile bolusProfile) async { void handleDeleteAction(BolusProfile bolusProfile) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(bolusProfile), onConfirm: () => onDelete(bolusProfile),
message: 'Are you sure you want to delete this Bolus Profile?', message: 'Are you sure you want to delete this Bolus Profile?',
@ -143,12 +180,6 @@ class _BolusProfileListScreenState extends State<BolusProfileListScreen> {
showDetailScreen(bolusProfile: bolusProfile); showDetailScreen(bolusProfile: bolusProfile);
} }
@override
void initState() {
super.initState();
reload();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -196,6 +227,14 @@ class _BolusProfileListScreenState extends State<BolusProfileListScreen> {
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
IconButton(
icon: const Icon(
Icons.copy,
color: Colors.blue,
),
onPressed: () =>
handleDuplicateAction(bolusProfile),
),
IconButton( IconButton(
icon: const Icon( icon: const Icon(
Icons.delete, Icons.delete,

View File

@ -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/glucose_target.dart';
import 'package:diameter/models/log_bolus.dart'; import 'package:diameter/models/log_bolus.dart';
import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_entry.dart';
@ -29,6 +29,12 @@ class _LogScreenState extends State<LogScreen> {
reload(); reload();
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
setState(() { setState(() {
_logEntryDailyMap = LogEntry.getDailyEntryMap(); _logEntryDailyMap = LogEntry.getDailyEntryMap();
@ -53,7 +59,7 @@ class _LogScreenState extends State<LogScreen> {
void handleDeleteAction(LogEntry logEntry) async { void handleDeleteAction(LogEntry logEntry) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(logEntry), onConfirm: () => onDelete(logEntry),
message: 'Are you sure you want to delete this Log Entry?', message: 'Are you sure you want to delete this Log Entry?',

View File

@ -1,9 +1,11 @@
import 'dart:math'; import 'dart:math';
import 'package:diameter/components/detail.dart'; 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/dropdown.dart'; import 'package:diameter/components/forms/number_form_field.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/bolus.dart'; import 'package:diameter/models/bolus.dart';
import 'package:diameter/models/log_bolus.dart'; import 'package:diameter/models/log_bolus.dart';
import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_entry.dart';
@ -136,6 +138,25 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
updateDelayedRatio(); 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}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -179,20 +200,73 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
} }
} }
void updateDelayedRatio() { void updateDelayedRatio(
if (_unitsController.text != '') { {double? totalUnitsUpdate,
setState(() { double? delayedUnitsUpdate,
_delayedUnitsController.text = double? immediateUnitsUpdate,
((double.tryParse(_unitsController.text) ?? 0) * double? percentageUpdate}) {
_delayPercentage / int precision = Utils.getFractionDigitsLength(Settings.insulinSteps);
100) double? totalUnits =
.toString(); totalUnitsUpdate ?? double.tryParse(_unitsController.text);
_immediateUnitsController.text = double? delayedUnits;
((double.tryParse(_unitsController.text) ?? 0) * double? immediateUnits;
(100 - _delayPercentage) /
100) if (totalUnits == null) {
.toString(); 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() { void calculateBolus() {
setState(() { if (_rate != null && !_setManually) {
if (_rate != null && !_setManually) { double units = (double.tryParse(_carbsController.text) ?? 0) /
_unitsController.text = ((double.tryParse(_carbsController.text) ?? 0) / (_rate!.carbs / _rate!.units);
(_rate!.carbs / _rate!.units)) double remainder = units % Settings.insulinSteps;
.toString(); int precision = Utils.getFractionDigitsLength(Settings.insulinSteps);
if (_unitsController.text != '') {
_delayedUnitsController.text = if (remainder != 0) {
((double.tryParse(_unitsController.text) ?? 0) * units = Utils.addDoublesWithPrecision(units, -remainder, precision);
_delayPercentage / if (remainder > Settings.insulinSteps / 2) {
100) units = Utils.addDoublesWithPrecision(
.toString(); units, Settings.insulinSteps, precision);
_immediateUnitsController.text =
((double.tryParse(_unitsController.text) ?? 0) *
(100 - _delayPercentage) /
100)
.toString();
} }
} }
});
setState(() {
_unitsController.text = units.toString();
});
updateDelayedRatio(
totalUnitsUpdate: double.tryParse(_unitsController.text));
}
} }
void onChangeGlucose({GlucoseMeasurement? calculateFrom}) { void onChangeGlucose() {
int? mgPerDlCurrent; int? mgPerDlCurrent;
int? mgPerDlTarget; int? mgPerDlTarget;
int? mgPerDlCorrection; int? mgPerDlCorrection;
@ -237,14 +313,14 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
double? mmolPerLTarget; double? mmolPerLTarget;
double? mmolPerLCorrection; double? mmolPerLCorrection;
if (calculateFrom != GlucoseMeasurement.mmolPerL && if (Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl &&
_mgPerDlCurrentController.text != '' && _mgPerDlCurrentController.text != '' &&
_mgPerDlTargetController.text != '') { _mgPerDlTargetController.text != '') {
mgPerDlCurrent = int.tryParse(_mgPerDlCurrentController.text); mgPerDlCurrent = int.tryParse(_mgPerDlCurrentController.text);
mgPerDlTarget = int.tryParse(_mgPerDlTargetController.text); mgPerDlTarget = int.tryParse(_mgPerDlTargetController.text);
mgPerDlCorrection = max((mgPerDlCurrent ?? 0) - (mgPerDlTarget ?? 0), 0); mgPerDlCorrection = max((mgPerDlCurrent ?? 0) - (mgPerDlTarget ?? 0), 0);
} }
if (calculateFrom != GlucoseMeasurement.mgPerDl && if (Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL &&
_mmolPerLCurrentController.text != '') { _mmolPerLCurrentController.text != '') {
mmolPerLCurrent = double.tryParse(_mmolPerLCurrentController.text); mmolPerLCurrent = double.tryParse(_mmolPerLCurrentController.text);
mmolPerLTarget = double.tryParse(_mmolPerLTargetController.text); mmolPerLTarget = double.tryParse(_mmolPerLTargetController.text);
@ -264,9 +340,9 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
_mmolPerLCorrectionController.text = _mmolPerLCorrectionController.text =
Utils.convertMgPerDlToMmolPerL(mgPerDlCorrection ?? 0).toString(); Utils.convertMgPerDlToMmolPerL(mgPerDlCorrection ?? 0).toString();
if (_rate != null && !_setManually) { if (_rate != null && !_setManually) {
_unitsController.text = ((mgPerDlCorrection ?? 0) / updateDelayedRatio(
((_rate!.mgPerDl ?? 0) / _rate!.units)) totalUnitsUpdate: (mgPerDlCorrection ?? 0) /
.toString(); ((_rate!.mgPerDl ?? 0) / _rate!.units));
} }
}); });
} }
@ -282,9 +358,9 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
_mgPerDlCorrectionController.text = _mgPerDlCorrectionController.text =
Utils.convertMmolPerLToMgPerDl(mmolPerLCorrection ?? 0).toString(); Utils.convertMmolPerLToMgPerDl(mmolPerLCorrection ?? 0).toString();
if (_rate != null && !_setManually) { if (_rate != null && !_setManually) {
_unitsController.text = ((mmolPerLCorrection ?? 0) / updateDelayedRatio(
((_rate!.mmolPerL ?? 0) / _rate!.units)) totalUnitsUpdate: (mmolPerLCorrection ?? 0) /
.toString(); ((_rate!.mmolPerL ?? 0) / _rate!.units));
} }
}); });
} }
@ -414,7 +490,7 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
int.tryParse(_delayController.text) != _logBolus!.delay || int.tryParse(_delayController.text) != _logBolus!.delay ||
_setManually != _logBolus!.setManually || _setManually != _logBolus!.setManually ||
_notesController.text != (_logBolus!.notes ?? ''))))) { _notesController.text != (_logBolus!.notes ?? ''))))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,
@ -444,20 +520,18 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
Row( Row(
children: [ children: [
Expanded( Expanded(
child: TextFormField( child: NumberFormField(
decoration: const InputDecoration( label: 'Bolus Units',
labelText: 'Bolus Units', suffix: ' U',
suffixText: ' U',
),
controller: _unitsController, controller: _unitsController,
onChanged: (_) { step: Settings.insulinSteps,
autoRoundToMultipleOfStep: true,
onChanged: (value) {
setState(() { setState(() {
_setManually = true; _setManually = true;
}); });
updateDelayedRatio(); updateDelayedRatio(totalUnitsUpdate: value);
}, },
keyboardType: const TextInputType.numberWithOptions(
decimal: true),
), ),
), ),
Expanded( Expanded(
@ -486,6 +560,7 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
onChanged: (_) { onChanged: (_) {
setState(() { setState(() {
_bolusType = BolusType.glucose; _bolusType = BolusType.glucose;
onChangeGlucose();
}); });
}), }),
), ),
@ -497,6 +572,7 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
_bolusType = BolusType.meal; _bolusType = BolusType.meal;
calculateBolus();
}); });
}), }),
), ),
@ -517,23 +593,13 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
child: Padding( child: Padding(
padding: padding:
const EdgeInsets.only(right: 5.0), const EdgeInsets.only(right: 5.0),
child: TextFormField( child: NumberFormField(
decoration: const InputDecoration( label: 'Current',
labelText: 'Current', suffix: 'mg/dl',
suffixText: 'mg/dl',
),
controller: controller:
_mgPerDlCurrentController, _mgPerDlCurrentController,
onChanged: (_) async { onChanged: (_) => onChangeGlucose(),
await Future.delayed( showSteppers: false,
const Duration(seconds: 1));
onChangeGlucose(
calculateFrom:
GlucoseMeasurement
.mgPerDl);
},
keyboardType: const TextInputType
.numberWithOptions(),
), ),
), ),
), ),
@ -541,23 +607,13 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 5.0), horizontal: 5.0),
child: TextFormField( child: NumberFormField(
decoration: const InputDecoration( label: 'Target',
labelText: 'Target', suffix: 'mg/dl',
suffixText: 'mg/dl',
),
controller: controller:
_mgPerDlTargetController, _mgPerDlTargetController,
onChanged: (_) async { onChanged: (_) => onChangeGlucose(),
await Future.delayed( showSteppers: false,
const Duration(seconds: 1));
onChangeGlucose(
calculateFrom:
GlucoseMeasurement
.mgPerDl);
},
keyboardType: const TextInputType
.numberWithOptions(),
), ),
), ),
), ),
@ -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( Padding(
children: Settings.glucoseMeasurement == padding: EdgeInsets.only(
GlucoseMeasurement.mmolPerL || top: [
[ GlucoseDisplayMode.both,
GlucoseDisplayMode.both, GlucoseDisplayMode.bothForDetail
GlucoseDisplayMode.bothForDetail ].contains(Settings.glucoseDisplayMode)
].contains(Settings.glucoseDisplayMode) ? 10.0
? [ : 0.0),
Expanded( child: Row(
child: Padding( children: Settings.glucoseMeasurement ==
padding: GlucoseMeasurement.mmolPerL ||
const EdgeInsets.only(right: 5.0), [
child: TextFormField( GlucoseDisplayMode.both,
decoration: const InputDecoration( GlucoseDisplayMode.bothForDetail
labelText: 'Current', ].contains(Settings.glucoseDisplayMode)
suffixText: 'mmol/l', ? [
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(
Expanded( child: Padding(
child: Padding( padding: const EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric( horizontal: 5.0),
horizontal: 5.0), child: NumberFormField(
child: TextFormField( label: 'Target',
decoration: const InputDecoration( suffix: 'mmol/l',
labelText: 'Target', controller:
suffixText: 'mmol/l', _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(
Expanded( child: Padding(
child: Padding( padding: const EdgeInsets.only(
padding: left: 5.0),
const EdgeInsets.only(left: 5.0), child: TextFormField(
child: TextFormField( decoration: const InputDecoration(
decoration: const InputDecoration( labelText: 'Correction',
labelText: 'Correction', suffixText: 'mmol/l',
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(
padding: const EdgeInsets.only(top: 10.0), padding: const EdgeInsets.only(top: 10.0),
child: TextFormField( child: NumberFormField(
decoration: InputDecoration( label: 'Carbs',
labelText: 'Carbs', suffix: Settings.nutritionMeasurementSuffix,
suffixText:
Settings.nutritionMeasurementSuffix,
),
controller: _carbsController, controller: _carbsController,
onChanged: (_) => calculateBolus(), step: Settings.nutritionSteps,
keyboardType: onChanged: (value) {
const TextInputType.numberWithOptions( _carbsController.text = (value ?? 0).toString();
decimal: true), calculateBolus();
},
), ),
), ),
], ],
@ -749,10 +770,7 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
max: 100, max: 100,
onChanged: _delayController.text != '' onChanged: _delayController.text != ''
? (value) { ? (value) {
setState(() { updateDelayedRatio(percentageUpdate: value);
_delayPercentage = value;
});
updateDelayedRatio();
} }
: null, : null,
), ),
@ -766,34 +784,30 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(right: 5.0), padding: const EdgeInsets.only(right: 5.0),
child: TextFormField( child: NumberFormField(
decoration: const InputDecoration( label: 'Immediate Bolus',
labelText: 'Immediate Bolus', suffix: ' U',
suffixText: ' U',
),
controller: _immediateUnitsController, controller: _immediateUnitsController,
max: double.tryParse(_unitsController.text),
step: Settings.insulinSteps,
readOnly: true, readOnly: true,
enabled: onChanged: (value) => updateDelayedRatio(
(int.tryParse(_delayController.text) ?? immediateUnitsUpdate: value),
0) !=
0,
), ),
), ),
), ),
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 5.0), padding: const EdgeInsets.only(left: 5.0),
child: TextFormField( child: NumberFormField(
decoration: const InputDecoration( label: 'Delayed Bolus',
labelText: 'Delayed Bolus', suffix: ' U',
suffixText: ' U',
),
controller: _delayedUnitsController, controller: _delayedUnitsController,
max: double.tryParse(_unitsController.text),
step: Settings.insulinSteps,
readOnly: true, readOnly: true,
enabled: onChanged: (value) => updateDelayedRatio(
(int.tryParse(_delayController.text) ?? delayedUnitsUpdate: value),
0) !=
0,
), ),
), ),
), ),

View File

@ -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_bolus.dart';
import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
@ -25,6 +25,12 @@ class LogBolusListScreen extends StatefulWidget {
class _LogBolusListScreenState extends State<LogBolusListScreen> { class _LogBolusListScreenState extends State<LogBolusListScreen> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
widget.reload(); widget.reload();
@ -60,7 +66,7 @@ class _LogBolusListScreenState extends State<LogBolusListScreen> {
void handleDeleteAction(LogBolus logBolus) async { void handleDeleteAction(LogBolus logBolus) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(logBolus), onConfirm: () => onDelete(logBolus),
message: 'Are you sure you want to delete this Bolus?', message: 'Are you sure you want to delete this Bolus?',

View File

@ -1,6 +1,9 @@
import 'package:diameter/components/detail.dart'; import 'package:diameter/components/detail.dart';
import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/forms/date_time_form_field.dart';
import 'package:diameter/components/forms.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_bolus.dart';
import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_meal.dart'; import 'package:diameter/models/log_meal.dart';
@ -40,8 +43,10 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
final _timeController = TextEditingController(text: ''); final _timeController = TextEditingController(text: '');
final _dateController = TextEditingController(text: ''); final _dateController = TextEditingController(text: '');
final _mgPerDlController = TextEditingController(text: ''); final _mgPerDlController =
final _mmolPerLController = TextEditingController(text: ''); TextEditingController(text: Settings.targetMgPerDl.toString());
final _mmolPerLController =
TextEditingController(text: Settings.targetMmolPerL.toString());
final _notesController = TextEditingController(text: ''); final _notesController = TextEditingController(text: '');
late FloatingActionButton addMealButton; late FloatingActionButton addMealButton;
@ -109,6 +114,17 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
updateTime(); updateTime();
} }
@override
void dispose() {
_scrollController.dispose();
_timeController.dispose();
_dateController.dispose();
_mgPerDlController.dispose();
_mmolPerLController.dispose();
_notesController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -137,25 +153,26 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
_dateController.text = DateTimeUtils.displayDate(_time); _dateController.text = DateTimeUtils.displayDate(_time);
} }
void convertBetweenMgPerDlAndMmolPerL() { void convertBetweenMgPerDlAndMmolPerL(double? value) async {
int? mgPerDl; if (value != null) {
double? mmolPerL; if (Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl &&
_mgPerDlController.text != '') {
if (Settings.glucoseMeasurement != GlucoseMeasurement.mmolPerL && _mgPerDlController.text = value.toInt().toString();
_mgPerDlController.text != '') { setState(() {
mgPerDl = int.tryParse(_mgPerDlController.text); _mmolPerLController.text =
setState(() { Utils.convertMgPerDlToMmolPerL(value.toInt()).toString();
});
}
if (Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL &&
_mmolPerLController.text != '') {
_mmolPerLController.text = _mmolPerLController.text =
Utils.convertMgPerDlToMmolPerL(mgPerDl ?? 0).toString(); Utils.toStringMatchingTemplateFractionPrecision(
}); value, Settings.mmolPerLSteps);
} setState(() {
if (Settings.glucoseMeasurement != GlucoseMeasurement.mgPerDl && _mgPerDlController.text =
_mmolPerLController.text != '') { Utils.convertMmolPerLToMgPerDl(value.toDouble()).toString();
mmolPerL = double.tryParse(_mmolPerLController.text); });
setState(() { }
_mgPerDlController.text =
Utils.convertMmolPerLToMgPerDl(mmolPerL ?? 0).toString();
});
} }
} }
@ -207,7 +224,7 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
double.tryParse(_mmolPerLController.text) != double.tryParse(_mmolPerLController.text) !=
_logEntry!.mmolPerL || _logEntry!.mmolPerL ||
_notesController.text != (_logEntry!.notes ?? ''))))) { _notesController.text != (_logEntry!.notes ?? ''))))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,
@ -337,76 +354,52 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
children: [ children: [
Settings.glucoseMeasurement == Settings.glucoseMeasurement ==
GlucoseMeasurement.mgPerDl || 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 == Settings.glucoseDisplayMode ==
GlucoseDisplayMode.both || GlucoseDisplayMode.both ||
Settings.glucoseDisplayMode == Settings.glucoseDisplayMode ==
GlucoseDisplayMode.bothForDetail GlucoseDisplayMode.bothForDetail
? Expanded( ? Expanded(
child: TextFormField( flex: Settings.glucoseMeasurement ==
decoration: const InputDecoration( GlucoseMeasurement.mgPerDl
labelText: 'mmol/l', ? 2
suffixText: 'mmol/l', : 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 == readOnly: Settings.glucoseMeasurement ==
GlucoseMeasurement.mgPerDl, GlucoseMeasurement.mgPerDl,
showSteppers:
Settings.glucoseMeasurement ==
GlucoseMeasurement.mmolPerL,
controller: _mmolPerLController, controller: _mmolPerLController,
onChanged: (_) async { step: Settings.mmolPerLSteps,
await Future.delayed( onChanged:
const Duration(seconds: 1)); convertBetweenMgPerDlAndMmolPerL,
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;
},
), ),
) )
: Container(), : Container(),

View File

@ -1,7 +1,8 @@
import 'package:diameter/components/detail.dart'; 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/dropdown.dart'; import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/components/forms.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/accuracy.dart';
import 'package:diameter/models/log_meal.dart'; import 'package:diameter/models/log_meal.dart';
import 'package:diameter/models/meal.dart'; import 'package:diameter/models/meal.dart';
@ -37,13 +38,10 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
bool _isSaving = false; bool _isSaving = false;
bool _isExpanded = false; bool _isExpanded = false;
double _amount = 1;
final GlobalKey<FormState> _logMealForm = GlobalKey<FormState>(); final GlobalKey<FormState> _logMealForm = GlobalKey<FormState>();
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final _valueController = TextEditingController(text: ''); final _valueController = TextEditingController(text: '');
final _amountController = TextEditingController(text: '');
final _carbsRatioController = TextEditingController(text: ''); final _carbsRatioController = TextEditingController(text: '');
final _portionSizeController = TextEditingController(text: ''); final _portionSizeController = TextEditingController(text: '');
final _totalCarbsController = TextEditingController(text: ''); final _totalCarbsController = TextEditingController(text: '');
@ -62,6 +60,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
final _mealPortionTypeController = TextEditingController(text: ''); final _mealPortionTypeController = TextEditingController(text: '');
final _portionSizeAccuracyController = TextEditingController(text: ''); final _portionSizeAccuracyController = TextEditingController(text: '');
final _carbsRatioAccuracyController = TextEditingController(text: ''); final _carbsRatioAccuracyController = TextEditingController(text: '');
final _amountController = TextEditingController(text: '1');
List<Meal> _meals = []; List<Meal> _meals = [];
List<MealCategory> _mealCategories = []; List<MealCategory> _mealCategories = [];
@ -84,12 +83,11 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
if (widget.id != 0) { if (widget.id != 0) {
_valueController.text = _logMeal!.value; _valueController.text = _logMeal!.value;
_amountController.text = _logMeal!.amount.toString();
_carbsRatioController.text = (_logMeal!.carbsRatio ?? '').toString(); _carbsRatioController.text = (_logMeal!.carbsRatio ?? '').toString();
_portionSizeController.text = (_logMeal!.portionSize ?? '').toString(); _portionSizeController.text = (_logMeal!.portionSize ?? '').toString();
_totalCarbsController.text = (_logMeal!.totalCarbs ?? '').toString(); _totalCarbsController.text = (_logMeal!.totalCarbs ?? '').toString();
_amountController.text = (_logMeal!.amount).toString();
_notesController.text = _logMeal!.notes ?? ''; _notesController.text = _logMeal!.notes ?? '';
_meal = _logMeal!.meal.target; _meal = _logMeal!.meal.target;
_mealController.text = (_meal ?? '').toString(); _mealController.text = (_meal ?? '').toString();
_mealSource = _logMeal!.mealSource.target; _mealSource = _logMeal!.mealSource.target;
@ -105,10 +103,24 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
_carbsRatioAccuracyController.text = _carbsRatioAccuracyController.text =
(_carbsRatioAccuracy ?? '').toString(); (_carbsRatioAccuracy ?? '').toString();
} }
}
if (_amountController.text == '') { @override
_amountController.text = '1'; 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}) { void reload({String? message}) {
@ -177,7 +189,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
_carbsRatioController.text = (meal?.carbsRatio ?? '').toString(); _carbsRatioController.text = (meal?.carbsRatio ?? '').toString();
_amountController.text = '1'; _amountController.text = '1';
_portionSizeController.text = (meal?.portionSize ?? '').toString(); _portionSizeController.text = (meal?.portionSize ?? '').toString();
_totalCarbsController.text = (meal?.carbsPerPortion ?? '').toString(); _totalCarbsController.text = (meal?.carbsPerPortion ?? '').toString();
}); });
updateMealSource(meal?.mealSource.target); updateMealSource(meal?.mealSource.target);
updateMealCategory(meal?.mealCategory.target); updateMealCategory(meal?.mealCategory.target);
@ -197,6 +209,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
carbsRatio: double.tryParse(_carbsRatioController.text), carbsRatio: double.tryParse(_carbsRatioController.text),
portionSize: double.tryParse(_portionSizeController.text), portionSize: double.tryParse(_portionSizeController.text),
totalCarbs: double.tryParse(_totalCarbsController.text), totalCarbs: double.tryParse(_totalCarbsController.text),
amount: double.parse(_amountController.text),
); );
logMeal.logEntry.targetId = widget.logEntryId; logMeal.logEntry.targetId = widget.logEntryId;
logMeal.meal.target = _meal; logMeal.meal.target = _meal;
@ -222,6 +235,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
_mealSource != null || _mealSource != null ||
_mealCategory != null || _mealCategory != null ||
_mealPortionType != null || _mealPortionType != null ||
double.tryParse(_amountController.text) != 1 ||
double.tryParse(_carbsRatioController.text) != null || double.tryParse(_carbsRatioController.text) != null ||
double.tryParse(_portionSizeController.text) != null || double.tryParse(_portionSizeController.text) != null ||
double.tryParse(_totalCarbsController.text) != null || double.tryParse(_totalCarbsController.text) != null ||
@ -234,6 +248,8 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
_mealSource != _logMeal!.mealSource.target || _mealSource != _logMeal!.mealSource.target ||
_mealCategory != _logMeal!.mealCategory.target || _mealCategory != _logMeal!.mealCategory.target ||
_mealPortionType != _logMeal!.mealPortionType.target || _mealPortionType != _logMeal!.mealPortionType.target ||
double.tryParse(_amountController.text) !=
_logMeal!.amount ||
double.tryParse(_carbsRatioController.text) != double.tryParse(_carbsRatioController.text) !=
_logMeal!.carbsRatio || _logMeal!.carbsRatio ||
double.tryParse(_portionSizeController.text) != double.tryParse(_portionSizeController.text) !=
@ -245,7 +261,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
_portionSizeAccuracy != _portionSizeAccuracy !=
_logMeal!.portionSizeAccuracy.target || _logMeal!.portionSizeAccuracy.target ||
_notesController.text != (_logMeal!.notes ?? ''))))) { _notesController.text != (_logMeal!.notes ?? ''))))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,
@ -256,15 +272,16 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
} }
void updateAmount(double? amount) { void updateAmount(double? amount) {
double? previousAmount; double previousAmount;
double newAmount;
double? portionSize; double? portionSize;
double? carbsRatio; double? carbsRatio;
previousAmount = _amount; previousAmount = double.tryParse(_amountController.text) ?? 1;
newAmount = amount?.toDouble() ?? 1;
setState(() { setState(() {
_amountController.text = (amount ?? '').toString(); _amountController.text = newAmount.toString();
_amount = amount ?? 1;
}); });
if (_carbsRatioController.text != '') { if (_carbsRatioController.text != '') {
@ -274,9 +291,9 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
portionSize = double.tryParse(_portionSizeController.text); portionSize = double.tryParse(_portionSizeController.text);
} }
if (amount != null && portionSize != null) { if (portionSize != null) {
setState(() { setState(() {
portionSize = portionSize! / (previousAmount ?? 1) * amount; portionSize = portionSize! / previousAmount * newAmount;
_portionSizeController.text = portionSize.toString(); _portionSizeController.text = portionSize.toString();
}); });
if (carbsRatio != null) { if (carbsRatio != null) {
@ -289,14 +306,11 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
} }
void calculateThirdMeasurementOfPortionCarbsRelation() { void calculateThirdMeasurementOfPortionCarbsRelation() {
int? amount; double? amount = double.tryParse(_amountController.text) ?? 1;
double? carbsRatio; double? carbsRatio;
double? portionSize; double? portionSize;
double? carbsPerPortion; double? carbsPerPortion;
if (_amountController.text != '') {
amount = int.tryParse(_amountController.text);
}
if (_carbsRatioController.text != '') { if (_carbsRatioController.text != '') {
carbsRatio = double.tryParse(_carbsRatioController.text); carbsRatio = double.tryParse(_carbsRatioController.text);
} }
@ -310,15 +324,14 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
if (carbsRatio != null && portionSize != null && carbsPerPortion == null) { if (carbsRatio != null && portionSize != null && carbsPerPortion == null) {
setState(() { setState(() {
_totalCarbsController.text = _totalCarbsController.text =
Utils.calculateCarbs(carbsRatio!, portionSize! * (amount ?? 1)) Utils.calculateCarbs(carbsRatio!, portionSize! * amount).toString();
.toString();
}); });
} }
if (carbsRatio == null && portionSize != null && carbsPerPortion != null) { if (carbsRatio == null && portionSize != null && carbsPerPortion != null) {
setState(() { setState(() {
_carbsRatioController.text = Utils.calculateCarbsRatio( _carbsRatioController.text =
carbsPerPortion!, portionSize! * (amount ?? 1)) Utils.calculateCarbsRatio(carbsPerPortion!, portionSize! * amount)
.toString(); .toString();
}); });
} }
if (carbsRatio != null && portionSize == null && carbsPerPortion != null) { if (carbsRatio != null && portionSize == null && carbsPerPortion != null) {
@ -388,12 +401,44 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
), ),
], ],
), ),
NumberFormField( Row(
controller: _amountController, children: [
label: 'Amount', Expanded(
suffix: _mealPortionType?.value, child: NumberFormField(
min: 0, controller: _amountController,
onChanged: updateAmount, 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( Row(
children: [ children: [

View File

@ -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_entry.dart';
import 'package:diameter/models/log_meal.dart'; import 'package:diameter/models/log_meal.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
@ -21,6 +21,12 @@ class LogMealListScreen extends StatefulWidget {
class _LogMealListScreenState extends State<LogMealListScreen> { class _LogMealListScreenState extends State<LogMealListScreen> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
widget.reload(); widget.reload();
@ -56,7 +62,7 @@ class _LogMealListScreenState extends State<LogMealListScreen> {
void handleDeleteAction(LogMeal meal) async { void handleDeleteAction(LogMeal meal) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(meal), onConfirm: () => onDelete(meal),
message: 'Are you sure you want to delete this Meal?', message: 'Are you sure you want to delete this Meal?',

View File

@ -1,7 +1,10 @@
import 'package:diameter/components/detail.dart'; 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/dropdown.dart'; import 'package:diameter/components/forms/date_time_form_field.dart';
import 'package:diameter/components/forms.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/basal_profile.dart';
import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/models/bolus_profile.dart';
import 'package:diameter/models/log_event.dart'; import 'package:diameter/models/log_event.dart';
@ -99,6 +102,21 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
updateEndTime(); 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}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -242,7 +260,7 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
(_notesController.text != (_logEvent!.notes ?? '') || (_notesController.text != (_logEvent!.notes ?? '') ||
_eventType != _logEvent!.eventType.target || _eventType != _logEvent!.eventType.target ||
_hasEndTime != _logEvent!.hasEndTime)))) { _hasEndTime != _logEvent!.hasEndTime)))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,
@ -382,14 +400,17 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
), ),
], ],
), ),
TextFormField( Padding(
controller: _reminderDurationController, padding: const EdgeInsets.symmetric(vertical: 10.0),
keyboardType: child: TextFormField(
const TextInputType.numberWithOptions(), controller: _reminderDurationController,
decoration: InputDecoration( keyboardType:
labelText: 'Default Reminder Duration', const TextInputType.numberWithOptions(),
suffixText: ' min', decoration: InputDecoration(
enabled: _hasEndTime, labelText: 'Default Reminder Duration',
suffixText: ' min',
enabled: _hasEndTime,
),
), ),
), ),
Row( Row(
@ -426,39 +447,42 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
), ),
], ],
), ),
Row( Padding(
children: [ padding: const EdgeInsets.only(top: 10.0),
Expanded( child: Row(
child: AutoCompleteDropdownButton< children: [
BasalProfile>( Expanded(
controller: _basalProfileController, child: AutoCompleteDropdownButton<
selectedItem: _basalProfile, BasalProfile>(
label: 'Basal Profile', controller: _basalProfileController,
items: _basalProfiles, selectedItem: _basalProfile,
onChanged: updateBasalProfile, label: 'Basal Profile',
items: _basalProfiles,
onChanged: updateBasalProfile,
),
), ),
), IconButton(
IconButton( onPressed: () {
onPressed: () { Navigator.push(
Navigator.push( context,
context, MaterialPageRoute(
MaterialPageRoute( builder: (context) => _basalProfile ==
builder: (context) => _basalProfile == null
null ? const BasalProfileDetailScreen()
? const BasalProfileDetailScreen() : BasalProfileDetailScreen(
: BasalProfileDetailScreen( id: _basalProfile!.id),
id: _basalProfile!.id), ),
), ).then((result) {
).then((result) { updateBasalProfile(result?[1]);
updateBasalProfile(result?[1]); reload(message: result?[0]);
reload(message: result?[0]); });
}); },
}, icon: Icon(_basalProfile == null
icon: Icon(_basalProfile == null ? Icons.add
? Icons.add : Icons.edit),
: Icons.edit), ),
), ],
], ),
) )
] ]
: []), : []),

View File

@ -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/log_event.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/screens/log/log_event/log_event_detail.dart'; import 'package:diameter/screens/log/log_event/log_event_detail.dart';
@ -25,6 +25,12 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
reload(); reload();
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
setState(() { setState(() {
_activeEvents = LogEvent.getAllActiveForTime(DateTime.now()); _activeEvents = LogEvent.getAllActiveForTime(DateTime.now());
@ -73,7 +79,7 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
void handleDeleteAction(LogEvent logEvent) async { void handleDeleteAction(LogEvent logEvent) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(logEvent), onConfirm: () => onDelete(logEvent),
message: 'Are you sure you want to delete this Event?', message: 'Are you sure you want to delete this Event?',
@ -91,7 +97,7 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
void handleStopAction(LogEvent event) async { void handleStopAction(LogEvent event) async {
if (Settings.get().showConfirmationDialogOnStopEvent) { if (Settings.get().showConfirmationDialogOnStopEvent) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onStop(event), onConfirm: () => onStop(event),
message: 'Are you sure you want to end this Event?', message: 'Are you sure you want to end this Event?',

View File

@ -1,7 +1,9 @@
import 'package:diameter/components/detail.dart'; 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/dropdown.dart'; import 'package:diameter/components/forms/duration_form_field.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/basal_profile.dart'; import 'package:diameter/models/basal_profile.dart';
import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/models/bolus_profile.dart';
import 'package:diameter/models/log_event_type.dart'; import 'package:diameter/models/log_event_type.dart';
@ -32,10 +34,10 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final _valueController = TextEditingController(text: ''); final _valueController = TextEditingController(text: '');
final _defaultReminderDurationController = TextEditingController(text: '');
final _notesController = TextEditingController(text: ''); final _notesController = TextEditingController(text: '');
bool _hasEndTime = false; bool _hasEndTime = false;
int _defaultReminderDuration = 0;
BolusProfile? _bolusProfile; BolusProfile? _bolusProfile;
BasalProfile? _basalProfile; BasalProfile? _basalProfile;
final _bolusProfileController = TextEditingController(text: ''); final _bolusProfileController = TextEditingController(text: '');
@ -52,8 +54,8 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
if (_logEventType != null) { if (_logEventType != null) {
_valueController.text = _logEventType!.value; _valueController.text = _logEventType!.value;
_defaultReminderDurationController.text = _defaultReminderDuration =
(_logEventType!.defaultReminderDuration ?? '').toString(); _logEventType!.defaultReminderDuration ?? 0;
_hasEndTime = _logEventType!.hasEndTime; _hasEndTime = _logEventType!.hasEndTime;
_notesController.text = _logEventType!.notes ?? ''; _notesController.text = _logEventType!.notes ?? '';
_basalProfile = _logEventType!.basalProfile.target; _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}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -107,8 +119,7 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
id: widget.id, id: widget.id,
value: _valueController.text, value: _valueController.text,
notes: _notesController.text, notes: _notesController.text,
defaultReminderDuration: defaultReminderDuration: _defaultReminderDuration,
int.tryParse(_defaultReminderDurationController.text),
hasEndTime: _hasEndTime, hasEndTime: _hasEndTime,
); );
eventType.basalProfile.target = _basalProfile; eventType.basalProfile.target = _basalProfile;
@ -127,17 +138,16 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
if (Settings.get().showConfirmationDialogOnCancel && if (Settings.get().showConfirmationDialogOnCancel &&
((isNew && ((isNew &&
(_valueController.text != '' || (_valueController.text != '' ||
int.tryParse(_defaultReminderDurationController.text) != _defaultReminderDuration != 0 ||
null ||
_notesController.text != '' || _notesController.text != '' ||
_hasEndTime)) || _hasEndTime)) ||
(!isNew && (!isNew &&
(_valueController.text != _logEventType!.value || (_valueController.text != _logEventType!.value ||
int.tryParse(_defaultReminderDurationController.text) != _defaultReminderDuration !=
_logEventType!.defaultReminderDuration || _logEventType!.defaultReminderDuration ||
_notesController.text != (_logEventType!.notes ?? '') || _notesController.text != (_logEventType!.notes ?? '') ||
_hasEndTime != _logEventType!.hasEndTime)))) { _hasEndTime != _logEventType!.hasEndTime)))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: isNew, isNew: isNew,
onSave: handleSaveAction, onSave: handleSaveAction,
@ -189,16 +199,12 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
? [ ? [
Padding( Padding(
padding: const EdgeInsets.only(bottom: 10.0), padding: const EdgeInsets.only(bottom: 10.0),
child: TextFormField( child: DurationFormField(
controller: _defaultReminderDurationController, minutes: _defaultReminderDuration,
keyboardType: label: 'Default Reminder Duration',
const TextInputType.numberWithOptions(), onChanged: (value) => _defaultReminderDuration = value ?? 0,
decoration: InputDecoration( showSteppers: true,
labelText: 'Default Reminder Duration',
suffixText: ' min',
enabled: _hasEndTime,
), ),
),
), ),
Padding( Padding(
padding: const EdgeInsets.only(bottom: 10.0), padding: const EdgeInsets.only(bottom: 10.0),

View File

@ -22,6 +22,12 @@ class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> {
reload(); reload();
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
setState(() { setState(() {
_logEventTypes = LogEventType.getAll(); _logEventTypes = LogEventType.getAll();
@ -66,8 +72,8 @@ class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) => EventTypeDetailScreen(
EventTypeDetailScreen(id: logEventType.id), id: logEventType.id),
), ),
).then((result) => reload(message: result?[0])); ).then((result) => reload(message: result?[0]));
}, },

View File

@ -1,6 +1,6 @@
import 'package:diameter/components/detail.dart'; import 'package:diameter/components/detail.dart';
import 'package:diameter/components/dialogs.dart'; import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/components/forms.dart'; import 'package:diameter/components/forms/form_wrapper.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:flutter/material.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}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -80,7 +88,7 @@ class _MealCategoryDetailScreenState extends State<MealCategoryDetailScreen> {
(!_isNew && (!_isNew &&
(_mealCategory!.value != _valueController.text || (_mealCategory!.value != _valueController.text ||
(_mealCategory!.notes ?? '') != _notesController.text))) { (_mealCategory!.notes ?? '') != _notesController.text))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,

View File

@ -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/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/meal/meal_category_detail.dart'; import 'package:diameter/screens/meal/meal_category_detail.dart';
@ -25,6 +25,12 @@ class _MealCategoryListScreenState extends State<MealCategoryListScreen> {
reload(); reload();
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
setState(() { setState(() {
_mealCategories = MealCategory.getAll(); _mealCategories = MealCategory.getAll();
@ -49,7 +55,7 @@ class _MealCategoryListScreenState extends State<MealCategoryListScreen> {
void handleDeleteAction(MealCategory mealCategory) async { void handleDeleteAction(MealCategory mealCategory) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(mealCategory), onConfirm: () => onDelete(mealCategory),
message: 'Are you sure you want to delete this Meal Category?', message: 'Are you sure you want to delete this Meal Category?',

View File

@ -1,7 +1,7 @@
import 'package:diameter/components/detail.dart'; import 'package:diameter/components/detail.dart';
import 'package:diameter/components/dialogs.dart'; import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
import 'package:diameter/components/forms.dart'; import 'package:diameter/components/forms/form_wrapper.dart';
import 'package:diameter/models/accuracy.dart'; import 'package:diameter/models/accuracy.dart';
import 'package:diameter/models/meal.dart'; import 'package:diameter/models/meal.dart';
import 'package:diameter/models/meal_category.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}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -215,7 +232,7 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
_meal!.delayedBolusDuration || _meal!.delayedBolusDuration ||
_delayedBolusPercentage != _meal!.delayedBolusPercentage || _delayedBolusPercentage != _meal!.delayedBolusPercentage ||
_notesController.text != (_meal!.notes ?? ''))))) { _notesController.text != (_meal!.notes ?? ''))))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,
@ -488,11 +505,12 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
child: Row( child: Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Text( Expanded(
'ADDITIONAL FIELDS', child: Text(
style: Theme.of(context).textTheme.subtitle2, 'ADDITIONAL FIELDS',
style: Theme.of(context).textTheme.subtitle2,
),
), ),
const Spacer(),
Icon(_isExpanded Icon(_isExpanded
? Icons.expand_less ? Icons.expand_less
: Icons.expand_more), : Icons.expand_more),

View File

@ -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/meal.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
@ -25,6 +25,12 @@ class _MealListScreenState extends State<MealListScreen> {
reload(); reload();
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
setState(() { setState(() {
_meals = Meal.getAll(); _meals = Meal.getAll();
@ -49,7 +55,7 @@ class _MealListScreenState extends State<MealListScreen> {
void handleDeleteAction(Meal meal) async { void handleDeleteAction(Meal meal) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(meal), onConfirm: () => onDelete(meal),
message: 'Are you sure you want to delete this Meal?', message: 'Are you sure you want to delete this Meal?',
@ -97,42 +103,51 @@ class _MealListScreenState extends State<MealListScreen> {
), ),
subtitle: Padding( subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0), padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Row( child: Column(
children: [ children: [
Column( Row(
children: [ children: [
Text(meal.mealSource.target?.value ?? ''), Expanded(
Text(meal.notes ?? ''), 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( meal.notes != null && meal.notes!.trim() != '' ? Padding(
child: Column( padding: const EdgeInsets.only(top: 10.0),
mainAxisAlignment: MainAxisAlignment.center, child: Row(
crossAxisAlignment: CrossAxisAlignment.center, children: [
children: ((meal.carbsPerPortion ?? 0) > 0) Expanded(child: Text(meal.notes ?? '')),
? [ ],
Text(meal.carbsPerPortion!.toStringAsPrecision(3)),
Text(
'${Settings.nutritionMeasurementSuffix} carbs',
textScaleFactor: 0.75),
]
: [],
), ),
), ) : Container(),
Expanded(
child: Column(
children: (meal.mealPortionType.hasValue)
? [
Text(meal.portionSize!.toStringAsPrecision(3)),
Text(
'${Settings.nutritionMeasurementSuffix}$portionType',
textAlign: TextAlign.center,
textScaleFactor: 0.75
),
]
: [],
),
),
], ],
), ),
), ),

View File

@ -1,6 +1,6 @@
import 'package:diameter/components/detail.dart'; import 'package:diameter/components/detail.dart';
import 'package:diameter/components/dialogs.dart'; import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/components/forms.dart'; import 'package:diameter/components/forms/form_wrapper.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:flutter/material.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}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -82,7 +90,7 @@ class _MealPortionTypeDetailScreenState
(_valueController.text != _mealPortionType!.value || (_valueController.text != _mealPortionType!.value ||
_notesController.text != _notesController.text !=
(_mealPortionType!.notes ?? ''))))) { (_mealPortionType!.notes ?? ''))))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,

View File

@ -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/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/meal/meal_portion_type_detail.dart'; import 'package:diameter/screens/meal/meal_portion_type_detail.dart';
@ -26,6 +26,12 @@ class _MealPortionTypeListScreenState extends State<MealPortionTypeListScreen> {
reload(); reload();
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
setState(() { setState(() {
_mealPortionTypes = MealPortionType.getAll(); _mealPortionTypes = MealPortionType.getAll();
@ -50,7 +56,7 @@ class _MealPortionTypeListScreenState extends State<MealPortionTypeListScreen> {
void handleDeleteAction(MealPortionType mealPortionType) async { void handleDeleteAction(MealPortionType mealPortionType) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(mealPortionType), onConfirm: () => onDelete(mealPortionType),
message: 'Are you sure you want to delete this Meal Portion Type?', message: 'Are you sure you want to delete this Meal Portion Type?',

View File

@ -1,7 +1,7 @@
import 'package:diameter/components/detail.dart'; import 'package:diameter/components/detail.dart';
import 'package:diameter/components/dialogs.dart'; import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
import 'package:diameter/components/forms.dart'; import 'package:diameter/components/forms/form_wrapper.dart';
import 'package:diameter/models/accuracy.dart'; import 'package:diameter/models/accuracy.dart';
import 'package:diameter/models/meal_category.dart'; import 'package:diameter/models/meal_category.dart';
import 'package:diameter/models/meal_portion_type.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}) { void reload({String? message}) {
if (widget.id != 0) { if (widget.id != 0) {
setState(() { setState(() {
@ -132,7 +144,7 @@ class _MealSourceDetailScreenState extends State<MealSourceDetailScreen> {
_defaultMealPortionType != _defaultMealPortionType !=
_mealSource!.defaultMealPortionType.target || _mealSource!.defaultMealPortionType.target ||
_notesController.text != (_mealSource!.notes ?? ''))))) { _notesController.text != (_mealSource!.notes ?? ''))))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,

View File

@ -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/meal_source.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
@ -25,6 +25,12 @@ class _MealSourceListScreenState extends State<MealSourceListScreen> {
reload(); reload();
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void reload({String? message}) { void reload({String? message}) {
setState(() { setState(() {
_mealSources = MealSource.getAll(); _mealSources = MealSource.getAll();
@ -49,7 +55,7 @@ class _MealSourceListScreenState extends State<MealSourceListScreen> {
void handleDeleteAction(MealSource mealSource) async { void handleDeleteAction(MealSource mealSource) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(mealSource), onConfirm: () => onDelete(mealSource),
message: 'Are you sure you want to delete this Meal Source?', message: 'Are you sure you want to delete this Meal Source?',

View File

@ -1,7 +1,7 @@
import 'package:diameter/components/detail.dart'; import 'package:diameter/components/detail.dart';
import 'package:diameter/components/dialogs.dart'; import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
import 'package:diameter/components/forms.dart'; import 'package:diameter/components/forms/form_wrapper.dart';
import 'package:diameter/models/ingredient.dart'; import 'package:diameter/models/ingredient.dart';
import 'package:diameter/models/meal.dart'; import 'package:diameter/models/meal.dart';
import 'package:diameter/models/recipe.dart'; import 'package:diameter/models/recipe.dart';
@ -31,11 +31,11 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final _nameController = TextEditingController(text: ''); final _nameController = TextEditingController(text: '');
final _servingsController = TextEditingController(text: '');
final _notesController = TextEditingController(text: ''); final _notesController = TextEditingController(text: '');
double _servings = 1;
final List<TextEditingController> _ingredientControllers = []; final List<TextEditingController> _ingredientControllers = [];
final List<TextEditingController> _ingredientAmountControllers = [];
List<Meal> _meals = []; List<Meal> _meals = [];
@ -49,15 +49,13 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
if (_recipe != null) { if (_recipe != null) {
_nameController.text = _recipe!.name; _nameController.text = _recipe!.name;
_servingsController.text = (_recipe!.servings ?? '').toString(); _servings = _recipe!.servings ?? 1;
_notesController.text = _recipe!.notes ?? ''; _notesController.text = _recipe!.notes ?? '';
if (_ingredients.isNotEmpty) { if (_ingredients.isNotEmpty) {
for (Ingredient ingredient in _ingredients) { for (Ingredient ingredient in _ingredients) {
_ingredientControllers.add( _ingredientControllers.add(
TextEditingController(text: ingredient.ingredient.target?.value)); 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; newIngredient.recipe.target = _recipe;
_ingredients.add(newIngredient); _ingredients.add(newIngredient);
_ingredientControllers.add(TextEditingController(text: '')); _ingredientControllers.add(TextEditingController(text: ''));
_ingredientAmountControllers.add(TextEditingController(text: ''));
}); });
} }
@ -103,7 +100,7 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
Recipe recipe = Recipe( Recipe recipe = Recipe(
id: widget.id, id: widget.id,
name: _nameController.text, name: _nameController.text,
servings: double.tryParse(_servingsController.text), servings: _servings,
notes: _notesController.text, notes: _notesController.text,
); );
Recipe.put(recipe); Recipe.put(recipe);
@ -145,14 +142,13 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
if (Settings.get().showConfirmationDialogOnCancel && if (Settings.get().showConfirmationDialogOnCancel &&
((_isNew && ((_isNew &&
(_nameController.text != '' || (_nameController.text != '' ||
_servingsController.text != '' || _servings != 1 ||
_notesController.text != '')) || _notesController.text != '')) ||
(!_isNew && (!_isNew &&
(_nameController.text != _recipe!.name || (_nameController.text != _recipe!.name ||
_servingsController.text != _servings != _recipe!.servings ||
(_recipe!.servings ?? '').toString() ||
_notesController.text != (_recipe!.notes ?? ''))))) { _notesController.text != (_recipe!.notes ?? ''))))) {
Dialogs.showCancelConfirmationDialog( DialogUtils.showCancelConfirmationDialog(
context: context, context: context,
isNew: _isNew, isNew: _isNew,
onSave: handleSaveAction, onSave: handleSaveAction,
@ -190,19 +186,19 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
return null; return null;
}, },
), ),
NumberFormField( // NumberFormField(
controller: _servingsController, // value: _servings,
label: 'Servings', // label: 'Servings',
suffix: ' portions', // suffix: ' portions',
min: 0, // min: 0,
onChanged: (value) { // onChanged: (value) {
if ((value ?? 0) >= 0) { // if (value != null && value >= 0) {
setState(() { // setState(() {
_servingsController.text = (value ?? 0).toString(); // _servings = value.toDouble();
}); // });
} // }
}, // },
), // ),
TextFormField( TextFormField(
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
controller: _notesController, controller: _notesController,
@ -285,25 +281,23 @@ class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
), ),
], ],
), ),
Padding( // Padding(
padding: const EdgeInsets.only(top: 10.0), // padding: const EdgeInsets.only(top: 10.0),
child: NumberFormField( // child: NumberFormField(
controller: // controller:
_ingredientAmountControllers[index], // _ingredients[index].amount,
label: 'Amount', // label: 'Amount',
suffix: Settings.nutritionMeasurementSuffix, // suffix: Settings.nutritionMeasurementSuffix,
min: 0, // min: 0,
onChanged: (value) { // onChanged: (value) {
if ((value ?? 0) >= 0) { // if (value != null && value >= 0) {
setState(() { // setState(() {
_ingredients[index].amount = value ?? 0; // _ingredients[index].amount = value.toDouble();
_ingredientAmountControllers[index] // });
.text = (value ?? 0).toString(); // }
}); // },
} // ),
}, // ),
),
),
], ],
), ),
); );

View File

@ -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/ingredient.dart';
import 'package:diameter/models/recipe.dart'; import 'package:diameter/models/recipe.dart';
import 'package:diameter/models/settings.dart'; import 'package:diameter/models/settings.dart';
@ -50,7 +50,7 @@ class _RecipeListScreenState extends State<RecipeListScreen> {
void handleDeleteAction(Recipe recipe) async { void handleDeleteAction(Recipe recipe) async {
if (Settings.get().showConfirmationDialogOnDelete) { if (Settings.get().showConfirmationDialogOnDelete) {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: () => onDelete(recipe), onConfirm: () => onDelete(recipe),
message: 'Are you sure you want to delete this Recipe?', message: 'Are you sure you want to delete this Recipe?',

View File

@ -1,9 +1,12 @@
import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/forms/boolean_form_field.dart';
import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms/number_form_field.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/models/settings.dart'; import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/utils/utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class SettingsScreen extends StatefulWidget { class SettingsScreen extends StatefulWidget {
static const String routeName = '/settings'; static const String routeName = '/settings';
@ -17,29 +20,33 @@ class SettingsScreen extends StatefulWidget {
class _SettingsScreenState extends State<SettingsScreen> { class _SettingsScreenState extends State<SettingsScreen> {
late Settings _settings; late Settings _settings;
final TextEditingController _nutritionMeasurementLabelController = TextEditingController(text: ''); final ScrollController _scrollController = ScrollController();
final TextEditingController _glucoseMeasurementLabelController = TextEditingController(text: '');
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 _onlyDisplayActiveGlucoseMeasurement;
late bool _displayBothGlucoseMeasurementsInDetailView; late bool _displayBothGlucoseMeasurementsInDetailView;
late bool _displayBothGlucoseMeasurementsInListView; late bool _displayBothGlucoseMeasurementsInListView;
// late String _dateFormat;
// late String? _longDateFormat;
// late String _timeFormat;
// late String? _longTimeFormat;
late bool _showConfirmationDialogOnCancel; late bool _showConfirmationDialogOnCancel;
late bool _showConfirmationDialogOnDelete; late bool _showConfirmationDialogOnDelete;
late bool _showConfirmationDialogOnStopEvent; late bool _showConfirmationDialogOnStopEvent;
// late int _lowGlucoseMgPerDl;
// late int _moderateGlucoseMgPerDl;
// late int _highGlucoseMgPerDl;
// late double _lowGlucoseMmolPerL;
// late double _moderateGlucoseMmolPerL;
// late double _highGlucoseMmolPerDl;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -48,27 +55,50 @@ class _SettingsScreenState extends State<SettingsScreen> {
nutritionMeasurementLabels[_settings.nutritionMeasurementIndex]; nutritionMeasurementLabels[_settings.nutritionMeasurementIndex];
_glucoseMeasurementLabelController.text = _glucoseMeasurementLabelController.text =
glucoseMeasurementLabels[_settings.glucoseMeasurementIndex]; 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 = _displayBothGlucoseMeasurementsInDetailView =
_settings.glucoseDisplayModeIndex == GlucoseDisplayMode.both.index || _settings.glucoseDisplayModeIndex == GlucoseDisplayMode.both.index ||
_settings.glucoseDisplayModeIndex == GlucoseDisplayMode.bothForDetail.index; _settings.glucoseDisplayModeIndex ==
GlucoseDisplayMode.bothForDetail.index;
_displayBothGlucoseMeasurementsInListView = _displayBothGlucoseMeasurementsInListView =
_settings.glucoseDisplayModeIndex == GlucoseDisplayMode.both.index || _settings.glucoseDisplayModeIndex == GlucoseDisplayMode.both.index ||
_settings.glucoseDisplayModeIndex == GlucoseDisplayMode.bothForList.index; _settings.glucoseDisplayModeIndex ==
// _dateFormat = _settings.dateFormat; GlucoseDisplayMode.bothForList.index;
// _longDateFormat = _settings.longDateFormat; _dateFormatController.text = _settings.dateFormat;
// _timeFormat = _settings.timeFormat; _longDateFormatController.text = _settings.longDateFormat ?? '';
// _longTimeFormat = _settings.longTimeFormat; _timeFormatController.text = _settings.timeFormat;
_longTimeFormatController.text = _settings.longTimeFormat ?? '';
_showConfirmationDialogOnCancel = _settings.showConfirmationDialogOnCancel; _showConfirmationDialogOnCancel = _settings.showConfirmationDialogOnCancel;
_showConfirmationDialogOnDelete = _settings.showConfirmationDialogOnDelete; _showConfirmationDialogOnDelete = _settings.showConfirmationDialogOnDelete;
_showConfirmationDialogOnStopEvent = _showConfirmationDialogOnStopEvent =
_settings.showConfirmationDialogOnStopEvent; _settings.showConfirmationDialogOnStopEvent;
// _lowGlucoseMgPerDl = _settings.lowGlucoseMgPerDl; }
// _moderateGlucoseMgPerDl = _settings.moderateGlucoseMgPerDl;
// _highGlucoseMgPerDl = _settings.highGlucoseMgPerDl; @override
// _lowGlucoseMmolPerL = _settings.lowGlucoseMmolPerL; void dispose() {
// _moderateGlucoseMmolPerL = _settings.moderateGlucoseMmolPerL; _scrollController.dispose();
// _highGlucoseMmolPerDl = _settings.highGlucoseMmolPerDl; _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}) { void reload({String? message}) {
@ -92,42 +122,35 @@ class _SettingsScreenState extends State<SettingsScreen> {
void saveSettings() { void saveSettings() {
Settings.put(Settings( Settings.put(Settings(
id: _settings.id, id: _settings.id,
nutritionMeasurementIndex: nutritionMeasurementIndex: nutritionMeasurementLabels
nutritionMeasurementLabels.indexOf(_nutritionMeasurementLabelController.text), .indexOf(_nutritionMeasurementLabelController.text),
glucoseMeasurementIndex: glucoseMeasurementIndex: glucoseMeasurementLabels
glucoseMeasurementLabels.indexOf(_glucoseMeasurementLabelController.text), .indexOf(_glucoseMeasurementLabelController.text),
glucoseDisplayModeIndex: _onlyDisplayActiveGlucoseMeasurement glucoseDisplayModeIndex: _onlyDisplayActiveGlucoseMeasurement
? GlucoseDisplayMode.activeOnly.index ? GlucoseDisplayMode.activeOnly.index
: _displayBothGlucoseMeasurementsInDetailView && _displayBothGlucoseMeasurementsInListView : _displayBothGlucoseMeasurementsInDetailView &&
? GlucoseDisplayMode.both.index _displayBothGlucoseMeasurementsInListView
? GlucoseDisplayMode.both.index
: _displayBothGlucoseMeasurementsInDetailView : _displayBothGlucoseMeasurementsInDetailView
? GlucoseDisplayMode.bothForDetail.index ? GlucoseDisplayMode.bothForDetail.index
: GlucoseDisplayMode.bothForList.index, : GlucoseDisplayMode.bothForList.index,
// dateFormat: _dateFormat, targetGlucoseMgPerDl:
// longDateFormat: _longDateFormat, int.tryParse(_targetGlucoseMgPerDlController.text) ?? _settings.targetGlucoseMgPerDl,
// timeFormat: _timeFormat, targetGlucoseMmolPerL:
// longTimeFormat: _longTimeFormat, double.tryParse(_targetGlucoseMmolPerLController.text) ?? _settings.targetGlucoseMmolPerL,
// showConfirmationDialogOnCancel: _showConfirmationDialogOnCancel, insulinIncrements:
// showConfirmationDialogOnDelete: _showConfirmationDialogOnDelete, double.tryParse(_insulinIncrementsController.text) ?? _settings.insulinIncrements,
// showConfirmationDialogOnStopEvent: _showConfirmationDialogOnStopEvent, nutritionIncrements:
// lowGlucoseMgPerDl: _dateFormat: _dateFormat, double.tryParse(_nutritionIncrementsController.text) ?? _settings.nutritionIncrements,
// longDateFormat: _longDateFormat, mmolPerLIncrements:
// timeFormat: _timeFormat, double.tryParse(_mmolPerLIncrementsController.text) ?? _settings.mmolPerLIncrements,
// longTimeFormat: _longTimeFormat, dateFormat: _dateFormatController.text,
longDateFormat: _longDateFormatController.text,
timeFormat: _timeFormatController.text,
longTimeFormat: _longTimeFormatController.text,
showConfirmationDialogOnCancel: _showConfirmationDialogOnCancel, showConfirmationDialogOnCancel: _showConfirmationDialogOnCancel,
showConfirmationDialogOnDelete: _showConfirmationDialogOnDelete, showConfirmationDialogOnDelete: _showConfirmationDialogOnDelete,
showConfirmationDialogOnStopEvent: _showConfirmationDialogOnStopEvent, 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'); reload(message: 'Settings updated');
} }
@ -138,7 +161,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
} }
void handleResetAction() async { void handleResetAction() async {
Dialogs.showConfirmationDialog( DialogUtils.showConfirmationDialog(
context: context, context: context,
onConfirm: onReset, onConfirm: onReset,
message: 'Are you sure you want to reset all settings?', 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), drawer: const Navigation(currentLocation: SettingsScreen.routeName),
body: SingleChildScrollView( body: SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Padding( Padding(
padding: const EdgeInsets.only(bottom: 10.0), padding: const EdgeInsets.only(bottom: 10.0),
child: Row( child: GestureDetector(
children: [ onTap: () => setState(() {
Text( _measurementsIsExpanded = !_measurementsIsExpanded;
'MEASUREMENTS', }),
style: Theme.of(context).textTheme.subtitle2, child: Row(
), children: [
const Spacer(), Expanded(
], child: Text(
), 'MEASUREMENTS',
), style: Theme.of(context).textTheme.subtitle2,
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(),
],
), ),
), Icon(_measurementsIsExpanded
BooleanFormField( ? Icons.expand_less
value: _showConfirmationDialogOnCancel, : Icons.expand_more),
label: 'on cancelling edit or creation of a record if changes have already been made', ],
onChanged: (value) { ),
_showConfirmationDialogOnCancel = value; ),
saveSettings();
},
), ),
BooleanFormField( Column(
value: _showConfirmationDialogOnDelete, children: _measurementsIsExpanded
label: 'on deleting a record', ? [
onChanged: (value) { AutoCompleteDropdownButton<String>(
_showConfirmationDialogOnDelete = value; controller: _nutritionMeasurementLabelController,
saveSettings(); 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( const Divider(),
value: _showConfirmationDialogOnStopEvent, Padding(
label: 'on stopping (ending) an event', padding: const EdgeInsets.only(bottom: 10.0),
onChanged: (value) { child: GestureDetector(
_showConfirmationDialogOnStopEvent = value; onTap: () => setState(() {
saveSettings(); _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,
),
],
),
),
),
],
),
),
]
: [],
), ),
], ],
), ),

View File

@ -10,7 +10,8 @@ class DateTimeUtils {
return fallback; return fallback;
} }
DateTime localDate = date.toLocal(); 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); return formatter.format(localDate);
} }
@ -19,7 +20,8 @@ class DateTimeUtils {
return fallback; return fallback;
} }
DateTime localDate = date.toLocal(); 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); return formatter.format(localDate);
} }
@ -29,8 +31,9 @@ class DateTimeUtils {
return fallback; return fallback;
} }
DateTime localDate = date.toLocal(); DateTime localDate = date.toLocal();
final DateFormat formatter = DateFormat( final DateFormat formatter = DateFormat(longFormat == true
longFormat == true ? Settings.get().longTimeFormat ?? Settings.get().timeFormat : Settings.get().timeFormat); ? Settings.get().longTimeFormat ?? Settings.get().timeFormat
: Settings.get().timeFormat);
return formatter.format(localDate); return formatter.format(localDate);
} }
@ -39,8 +42,9 @@ class DateTimeUtils {
if (time == null) { if (time == null) {
return fallback; return fallback;
} }
final DateFormat formatter = DateFormat( final DateFormat formatter = DateFormat(longFormat == true
longFormat == true ? Settings.get().longTimeFormat ?? Settings.get().timeFormat : Settings.get().timeFormat); ? Settings.get().longTimeFormat ?? Settings.get().timeFormat
: Settings.get().timeFormat);
return formatter.format(convertTimeOfDayToDateTime(time)); return formatter.format(convertTimeOfDayToDateTime(time));
} }

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class Dialogs { class DialogUtils {
static void showCancelConfirmationDialog( static void showCancelConfirmationDialog(
{required BuildContext context, {required BuildContext context,
required bool isNew, required bool isNew,

View File

@ -1,11 +1,28 @@
import 'dart:math'; import 'dart:math';
class Utils { class Utils {
static double roundToDecimalPlaces(double value, int places) { static double roundToDecimalPlaces(double value, int precision) {
double mod = pow(10.0, places).toDouble(); double mod = pow(10.0, precision).toDouble();
return ((value * mod).round().toDouble() / mod); 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) { static double convertMgPerDlToMmolPerL(int mgPerDl) {
return Utils.roundToDecimalPlaces(mgPerDl * 0.0555, 2); return Utils.roundToDecimalPlaces(mgPerDl * 0.0555, 2);
} }
@ -14,18 +31,21 @@ class Utils {
return (mmolPerL * 18.018).round(); return (mmolPerL * 18.018).round();
} }
static double calculateCarbs( static double calculateCarbs(double carbsRatio, double portionSize) {
double carbsRatio, double portionSize) {
return Utils.roundToDecimalPlaces(carbsRatio * portionSize / 100, 2); return Utils.roundToDecimalPlaces(carbsRatio * portionSize / 100, 2);
} }
static double calculateCarbsRatio( static double calculateCarbsRatio(
double carbsPerPortion, double portionSize) { 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( static double calculatePortionSize(
double carbsRatio, double carbsPerPortion) { 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

Binary file not shown.

BIN
objectbox/lock.mdb Normal file

Binary file not shown.

View File

@ -141,48 +141,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0" 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: convert:
dependency: transitive dependency: transitive
description: description:
@ -211,20 +169,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.0" 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: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -277,11 +221,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@ -303,13 +242,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
http:
dependency: transitive
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.4"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -324,13 +256,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.0" version: "4.0.0"
idb_shim:
dependency: transitive
description:
name: idb_shim
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -380,6 +305,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.11" 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: meta:
dependency: transitive dependency: transitive
description: description:
@ -394,13 +326,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
mime_type:
dependency: transitive
description:
name: mime_type
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -408,13 +333,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
nm:
dependency: transitive
description:
name: nm
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.1"
objectbox: objectbox:
dependency: "direct main" dependency: "direct main"
description: description:
@ -443,62 +361,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" 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: path:
dependency: transitive dependency: transitive
description: description:
@ -555,13 +417,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.4" version: "2.0.4"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "4.4.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -611,76 +466,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" 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: shelf:
dependency: transitive dependency: transitive
description: description:
@ -714,20 +499,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" 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: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -756,13 +527,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
synchronized:
dependency: transitive
description:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -776,7 +540,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.3" version: "0.4.8"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -791,13 +555,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
uuid:
dependency: transitive
description:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.5"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -833,20 +590,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" 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: yaml:
dependency: transitive dependency: transitive
description: description:

View File

@ -9,14 +9,11 @@ environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
dependencies: dependencies:
parse_server_sdk_flutter: ^3.1.0
flutter: flutter:
sdk: flutter sdk: flutter
sqflite: ^2.0.0+4
path_provider: ^2.0.5 path_provider: ^2.0.5
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
flex_color_scheme: ^3.0.1 flex_color_scheme: ^3.0.1
shared_preferences: ^2.0.8
intl: ^0.17.0 intl: ^0.17.0
objectbox: ^1.2.0 objectbox: ^1.2.0
objectbox_sync_flutter_libs: any objectbox_sync_flutter_libs: any

BIN
sync-server Executable file

Binary file not shown.

12
sync-server-config.js Normal file
View 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"
}
}