From 575130aba07a0b968528f2502036963f05b59e26 Mon Sep 17 00:00:00 2001 From: spinel Date: Fri, 10 Dec 2021 06:42:20 +0100 Subject: [PATCH] various usability improvements for ui --- TODO | 25 ++-- lib/components/detail.dart | 36 +++++- lib/screens/accuracy_detail.dart | 2 +- lib/screens/basal/basal_detail.dart | 81 ++++++++---- lib/screens/basal/basal_profile_detail.dart | 62 ++++++--- lib/screens/bolus/bolus_detail.dart | 122 ++++++++++++------ lib/screens/bolus/bolus_list.dart | 2 +- lib/screens/bolus/bolus_profile_detail.dart | 63 ++++++--- .../log/log_entry/log_bolus_detail.dart | 63 ++++++--- lib/screens/log/log_entry/log_bolus_list.dart | 8 +- lib/screens/log/log_entry/log_entry.dart | 39 ++++-- .../log/log_entry/log_meal_detail.dart | 2 +- lib/screens/log/log_entry/log_meal_list.dart | 6 +- .../log/log_event/log_event_detail.dart | 2 +- .../log/log_event/log_event_type_detail.dart | 4 +- lib/screens/meal/meal_category_detail.dart | 2 +- lib/screens/meal/meal_detail.dart | 17 ++- .../meal/meal_portion_type_detail.dart | 2 +- lib/screens/meal/meal_source_detail.dart | 2 +- lib/settings.dart | 20 ++- 20 files changed, 371 insertions(+), 189 deletions(-) diff --git a/TODO b/TODO index dd32359..0bc6177 100644 --- a/TODO +++ b/TODO @@ -1,31 +1,28 @@ BUGFIXES: - General/Framework: - ☐ make sure 'null' isn't shown in text fields - Basal/Bolus: - ☐ "no element" error on creating basal/bolus rates when working from apk - + MAIN TASKS: Layout: - ☐ make a styleguide (actively decide what components should look like) - ☐ make components rounder/nicer/closer to new material style @started(21-12-08 02:17) + ✔ make components rounder/nicer/closer to new material style @done(21-12-10 04:10) General/Framework: + ✔ make sure 'null' isn't shown in text fields @done(21-12-10 04:23) ☐ show indicator and make all fields readonly if user somehow gets to a deleted record detail view ☐ clean up controllers (dispose method of each stateful widget) ☐ account for deleted/disabled elements in dropdowns ☐ check through all detail forms and set required fields/according messages + ☐ set name properties as unique (and add checks to forms) ☐ implement component for durations ☐ change placement of delete and floating button because its very easy to accidentally hit delete - ☐ hide details like accuracies etc when picking meals + ✔ hide details like accuracies etc when picking meals @done(21-12-10 06:12) Basal/Bolus: - ☐ add save and close and next buttons on rate creations - ☐ always calculate other glucose measurement from active one and make other one readonly + ✔ add save and close and next buttons on rate creations @done(21-12-10 06:12) + ✔ always calculate other glucose measurement from active one and make other one readonly @done(21-12-10 04:33) Log Entry: - ☐ add save and close button - ☐ move on to newly created entry after saving + ✔ add save and close button @done(21-12-10 06:11) + ✔ move on to newly created entry after saving @done(21-12-10 06:11) ☐ recalculate bolus upon deactivating 'set manually' option ☐ account for delayed percentage setting on choosing meals - ☐ give option to supply quantity - ☐ give option to pick meal from a different log entry (that doesn't have an associated bolus yet) + ☐ give option to specify quantity + ☐ give option to pick meal from a different log entry (that doesn't have an associated bolus yet and within certain time span) Event Types: ☐ add colors as indicators for log entries (and later graphs in reports) Settings: diff --git a/lib/components/detail.dart b/lib/components/detail.dart index 160a853..8beb917 100644 --- a/lib/components/detail.dart +++ b/lib/components/detail.dart @@ -2,10 +2,22 @@ import 'package:flutter/material.dart'; class DetailBottomRow extends StatefulWidget { final void Function()? onCancel; - final void Function()? onSave; + final void Function()? onAction; + final void Function()? onMiddleAction; + final String actionText; + final String middleActionText; + final IconData actionIcon; + final IconData middleActionIcon; const DetailBottomRow( - {Key? key, required this.onCancel, required this.onSave}) + {Key? key, + required this.onCancel, + required this.onAction, + this.onMiddleAction, + this.actionText = 'SAVE', + this.actionIcon = Icons.save, + this.middleActionText = 'SAVE & CLOSE', + this.middleActionIcon = Icons.done}) : super(key: key); @override @@ -19,6 +31,7 @@ class _DetailBottomRowState extends State { child: Padding( padding: const EdgeInsets.all(10.0), child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ElevatedButton.icon( onPressed: widget.onCancel, @@ -28,14 +41,23 @@ class _DetailBottomRowState extends State { ), label: const Text('CANCEL'), ), - const Spacer(), + widget.onMiddleAction != null + ? ElevatedButton.icon( + onPressed: widget.onMiddleAction, + icon: Icon( + widget.middleActionIcon, + size: 18.0, + ), + label: Text(widget.middleActionText), + ) + : const Spacer(), ElevatedButton.icon( - onPressed: widget.onSave, - icon: const Icon( - Icons.save, + onPressed: widget.onAction, + icon: Icon( + widget.actionIcon, size: 18.0, ), - label: const Text('SAVE'), + label: Text(widget.actionText), ), ], ), diff --git a/lib/screens/accuracy_detail.dart b/lib/screens/accuracy_detail.dart index 507c1d4..94f0beb 100644 --- a/lib/screens/accuracy_detail.dart +++ b/lib/screens/accuracy_detail.dart @@ -183,7 +183,7 @@ class _AccuracyDetailScreenState extends State { ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: _isSaving ? null : handleSaveAction, + onAction: _isSaving ? null : handleSaveAction, ), ); } diff --git a/lib/screens/basal/basal_detail.dart b/lib/screens/basal/basal_detail.dart index 2d258f7..35ff3e1 100644 --- a/lib/screens/basal/basal_detail.dart +++ b/lib/screens/basal/basal_detail.dart @@ -32,6 +32,7 @@ class _BasalDetailScreenState extends State { Basal? _basal; bool _isNew = true; bool _isSaving = false; + bool _isFinalRate = true; final GlobalKey _basalForm = GlobalKey(); final ScrollController _scrollController = ScrollController(); @@ -61,8 +62,8 @@ class _BasalDetailScreenState extends State { _unitsController.text = _basal!.units.toString(); } - updateStartTime(); - updateEndTime(); + _startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime); + _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime); } void reload({String? message}) { @@ -86,12 +87,24 @@ class _BasalDetailScreenState extends State { }); } - void updateStartTime() { - _startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime); + void updateStartTime(TimeOfDay? value) { + if (value != null) { + setState(() { + _startTime = value; + _startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime); + }); + } } - void updateEndTime() { - _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime); + void updateEndTime(TimeOfDay? value) { + if (value != null) { + setState(() { + _endTime = value; + _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime); + _isFinalRate = widget.suggestedEndTime == null || + _endTime == widget.suggestedEndTime!; + }); + } } Future validateTimePeriod() async { @@ -139,7 +152,7 @@ class _BasalDetailScreenState extends State { }); } - void handleSaveAction() async { + void handleSaveAction({bool next = true}) async { setState(() { _isSaving = true; }); @@ -154,7 +167,30 @@ class _BasalDetailScreenState extends State { ); basal.basalProfile.targetId = widget.basalProfileId; Basal.put(basal); - Navigator.pop(context, ['${_isNew ? 'New' : ''} Basal Rate saved', basal]); + + if (next) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return BasalDetailScreen( + basalProfileId: widget.basalProfileId, + suggestedStartTime: _endTime, + suggestedEndTime: widget.suggestedEndTime, + ); + }, + ), + ).then((result) { + Navigator.pop( + context, + ['New Basal Rate${result[1] != null ? 's' : ''} saved', basal] + + [result[1]], + ); + }); + } else { + Navigator.pop( + context, ['${_isNew ? 'New' : ''} Basal Rate saved', basal]); + } } }); } @@ -173,8 +209,7 @@ class _BasalDetailScreenState extends State { _endTime.minute != (widget.suggestedEndTime?.minute ?? 0) || double.tryParse(_unitsController.text) != null)) || (!_isNew && - (TimeOfDay.fromDateTime(_basal!.startTime) != - _startTime || + (TimeOfDay.fromDateTime(_basal!.startTime) != _startTime || TimeOfDay.fromDateTime(_basal!.endTime) != _endTime || (double.tryParse(_unitsController.text) ?? 0) != _basal!.units)))) { @@ -214,14 +249,7 @@ class _BasalDetailScreenState extends State { label: 'Start Time', controller: _startTimeController, time: _startTime, - onChanged: (newStartTime) { - if (newStartTime != null) { - setState(() { - _startTime = newStartTime; - }); - updateStartTime(); - } - }, + onChanged: updateStartTime, ), ), ), @@ -232,14 +260,7 @@ class _BasalDetailScreenState extends State { label: 'End Time', controller: _endTimeController, time: _endTime, - onChanged: (newEndTime) { - if (newEndTime != null) { - setState(() { - _endTime = newEndTime; - }); - updateEndTime(); - } - }, + onChanged: updateEndTime, ), ), ), @@ -268,7 +289,13 @@ class _BasalDetailScreenState extends State { ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: _isSaving ? null : handleSaveAction, + onAction: + _isSaving ? null : () => handleSaveAction(next: !_isFinalRate), + onMiddleAction: _isSaving || _isFinalRate + ? null + : () => handleSaveAction(next: false), + actionText: _isFinalRate ? 'SAVE & CLOSE' : 'NEXT', + middleActionText: 'SAVE & CLOSE', ), ); } diff --git a/lib/screens/basal/basal_profile_detail.dart b/lib/screens/basal/basal_profile_detail.dart index 48d6d28..a0e2d61 100644 --- a/lib/screens/basal/basal_profile_detail.dart +++ b/lib/screens/basal/basal_profile_detail.dart @@ -78,12 +78,13 @@ class _BasalProfileDetailScreenState extends State { detailBottomRow = DetailBottomRow( onCancel: handleCancelAction, - onSave: handleSaveAction, + onAction: handleSaveAction, + onMiddleAction: () => handleSaveAction(close: true), ); detailBottomRowWhileSaving = DetailBottomRow( onCancel: handleCancelAction, - onSave: null, + onAction: null, ); actionButton = null; @@ -184,25 +185,30 @@ class _BasalProfileDetailScreenState extends State { TimeOfDay? suggestedStartTime; TimeOfDay? suggestedEndTime; - _basalRates.asMap().forEach((index, basal) { - if (suggestedStartTime == null && suggestedEndTime == null) { - if (index == 0 && - (basal.startTime.hour != 0 || basal.startTime.minute != 0)) { - suggestedStartTime = const TimeOfDay(hour: 0, minute: 0); - suggestedEndTime = TimeOfDay.fromDateTime(basal.startTime); - } else if ((index == _basalRates.length - 1) && - (basal.endTime.hour != 0 || basal.endTime.minute != 0)) { - suggestedStartTime = TimeOfDay.fromDateTime(basal.endTime); - suggestedEndTime = const TimeOfDay(hour: 0, minute: 0); - } else if (index != 0) { - var lastEndTime = _basalRates[index - 1].endTime; - if (basal.startTime.isAfter(lastEndTime)) { - suggestedStartTime = TimeOfDay.fromDateTime(lastEndTime); + if (_basalRates.isEmpty) { + suggestedStartTime = const TimeOfDay(hour: 0, minute: 0); + suggestedEndTime = const TimeOfDay(hour: 0, minute: 0); + } else { + _basalRates.asMap().forEach((index, basal) { + if (suggestedStartTime == null && suggestedEndTime == null) { + if (index == 0 && + (basal.startTime.hour != 0 || basal.startTime.minute != 0)) { + suggestedStartTime = const TimeOfDay(hour: 0, minute: 0); suggestedEndTime = TimeOfDay.fromDateTime(basal.startTime); + } else if ((index == _basalRates.length - 1) && + (basal.endTime.hour != 0 || basal.endTime.minute != 0)) { + suggestedStartTime = TimeOfDay.fromDateTime(basal.endTime); + suggestedEndTime = const TimeOfDay(hour: 0, minute: 0); + } else if (index != 0) { + var lastEndTime = _basalRates[index - 1].endTime; + if (basal.startTime.isAfter(lastEndTime)) { + suggestedStartTime = TimeOfDay.fromDateTime(lastEndTime); + suggestedEndTime = TimeOfDay.fromDateTime(basal.startTime); + } } } - } - }); + }); + } Navigator.push( context, @@ -218,7 +224,7 @@ class _BasalProfileDetailScreenState extends State { ).then((result) => reload(message: result?[0])); } - void handleSaveAction() async { + void handleSaveAction({bool close = false}) async { setState(() { bottomNav = detailBottomRowWhileSaving; }); @@ -231,7 +237,23 @@ class _BasalProfileDetailScreenState extends State { notes: _notesController.text, ); BasalProfile.put(basalProfile); - Navigator.pop(context, ['${_isNew ? 'New' : ''} Basal Profile saved', basalProfile]); + + if (close) { + Navigator.pop(context, + ['${_isNew ? 'New' : ''} Basal Profile saved', basalProfile]); + } else { + if (_isNew) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + BasalProfileDetailScreen(id: basalProfile.id), + ), + ).then((result) => Navigator.pop(context, result)); + } else { + reload(message: 'Basal Profile saved'); + } + } } setState(() { bottomNav = detailBottomRow; diff --git a/lib/screens/bolus/bolus_detail.dart b/lib/screens/bolus/bolus_detail.dart index 56a1a54..c95d61d 100644 --- a/lib/screens/bolus/bolus_detail.dart +++ b/lib/screens/bolus/bolus_detail.dart @@ -33,6 +33,7 @@ class _BolusDetailScreenState extends State { Bolus? _bolus; bool _isNew = true; bool _isSaving = false; + bool _isFinalRate = true; final GlobalKey _bolusForm = GlobalKey(); final ScrollController _scrollController = ScrollController(); @@ -61,16 +62,16 @@ class _BolusDetailScreenState extends State { if (_bolus != null) { _startTime = TimeOfDay.fromDateTime(_bolus!.startTime); + _endTime = TimeOfDay.fromDateTime(_bolus!.endTime); _unitsController.text = _bolus!.units.toString(); _carbsController.text = _bolus!.carbs.toString(); - _mgPerDlController.text = _bolus!.mgPerDl.toString(); - _mmolPerLController.text = _bolus!.mmolPerL.toString(); + _mgPerDlController.text = (_bolus!.mgPerDl ?? '').toString(); + _mmolPerLController.text = (_bolus!.mmolPerL ?? '').toString(); } - - updateStartTime(); - updateEndTime(); + _startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime); + _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime); } void reload({String? message}) { @@ -94,18 +95,30 @@ class _BolusDetailScreenState extends State { }); } - void updateStartTime() { - _startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime); + void updateStartTime(TimeOfDay? value) { + if (value != null) { + setState(() { + _startTime = value; + _startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime); + }); + } } - void updateEndTime() { - _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime); + void updateEndTime(TimeOfDay? value) { + if (value != null) { + setState(() { + _endTime = value; + _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime); + _isFinalRate = widget.suggestedEndTime == null || + _endTime == widget.suggestedEndTime!; + }); + } } Future validateTimePeriod() async { String? error; List bolusRates = Bolus.getAllForProfile(widget.bolusProfileId); - + // check for duplicates if (bolusRates .where((other) => @@ -148,7 +161,7 @@ class _BolusDetailScreenState extends State { }); } - void handleSaveAction() async { + void handleSaveAction({bool next = true}) async { setState(() { _isSaving = true; }); @@ -167,7 +180,29 @@ class _BolusDetailScreenState extends State { ); bolus.bolusProfile.targetId = widget.bolusProfileId; Bolus.put(bolus); - Navigator.pop(context, ['${_isNew ? 'New' : ''} Bolus Rate saved', bolus]); + + if (next) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return BolusDetailScreen( + bolusProfileId: widget.bolusProfileId, + suggestedStartTime: _endTime, + suggestedEndTime: widget.suggestedEndTime, + ); + }, + ), + ).then((result) { + Navigator.pop( + context, + ['New Bolus Rate${result[1] != null ? 's' : ''} saved', bolus] + [result[1]], + ); + }); + } else { + Navigator.pop( + context, ['${_isNew ? 'New' : ''} Bolus Rate saved', bolus]); + } } }); } @@ -263,14 +298,7 @@ class _BolusDetailScreenState extends State { label: 'Start Time', controller: _startTimeController, time: _startTime, - onChanged: (newStartTime) { - if (newStartTime != null) { - setState(() { - _startTime = newStartTime; - }); - updateStartTime(); - } - }, + onChanged: updateStartTime, ), ), ), @@ -281,14 +309,7 @@ class _BolusDetailScreenState extends State { label: 'End Time', controller: _endTimeController, time: _endTime, - onChanged: (newEndTime) { - if (newEndTime != null) { - setState(() { - _endTime = newEndTime; - }); - updateEndTime(); - } - }, + onChanged: updateEndTime, ), ), ), @@ -326,8 +347,10 @@ class _BolusDetailScreenState extends State { ), Row( children: [ - Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl || - Settings.glucoseDisplayMode == GlucoseDisplayMode.both || + Settings.glucoseMeasurement == + GlucoseMeasurement.mgPerDl || + Settings.glucoseDisplayMode == + GlucoseDisplayMode.both || Settings.glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? Expanded( @@ -336,12 +359,15 @@ class _BolusDetailScreenState extends State { labelText: 'per mg/dl', suffixText: 'mg/dl', ), + readOnly: Settings.glucoseMeasurement == + GlucoseMeasurement.mmolPerL, controller: _mgPerDlController, onChanged: (_) async { - await Future.delayed(const Duration(seconds: 1)); + await Future.delayed( + const Duration(seconds: 1)); convertBetweenMgPerDlAndMmolPerL( - calculateFrom: - GlucoseMeasurement.mgPerDl); + calculateFrom: + GlucoseMeasurement.mgPerDl); }, keyboardType: const TextInputType.numberWithOptions(), @@ -364,20 +390,27 @@ class _BolusDetailScreenState extends State { icon: const Icon(Icons.calculate), ) : Container(), - Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL || - [GlucoseDisplayMode.both, GlucoseDisplayMode.bothForDetail].contains(Settings.glucoseDisplayMode) + Settings.glucoseMeasurement == + GlucoseMeasurement.mmolPerL || + [ + GlucoseDisplayMode.both, + GlucoseDisplayMode.bothForDetail + ].contains(Settings.glucoseDisplayMode) ? Expanded( child: TextFormField( decoration: const InputDecoration( labelText: 'per mmol/l', suffixText: 'mmol/l', ), + readOnly: Settings.glucoseMeasurement == + GlucoseMeasurement.mgPerDl, controller: _mmolPerLController, onChanged: (_) async { - await Future.delayed(const Duration(seconds: 1)); + await Future.delayed( + const Duration(seconds: 1)); convertBetweenMgPerDlAndMmolPerL( - calculateFrom: - GlucoseMeasurement.mmolPerL); + calculateFrom: + GlucoseMeasurement.mmolPerL); }, keyboardType: const TextInputType.numberWithOptions( @@ -392,7 +425,10 @@ class _BolusDetailScreenState extends State { ), ) : Container(), - [GlucoseDisplayMode.both, GlucoseDisplayMode.bothForDetail].contains(Settings.glucoseDisplayMode) + [ + GlucoseDisplayMode.both, + GlucoseDisplayMode.bothForDetail + ].contains(Settings.glucoseDisplayMode) ? IconButton( onPressed: () => convertBetweenMgPerDlAndMmolPerL( calculateFrom: GlucoseMeasurement.mgPerDl), @@ -409,7 +445,13 @@ class _BolusDetailScreenState extends State { ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: _isSaving ? null : handleSaveAction, + onAction: + _isSaving ? null : () => handleSaveAction(next: !_isFinalRate), + onMiddleAction: _isSaving || _isFinalRate + ? null + : () => handleSaveAction(next: false), + actionText: _isFinalRate ? 'SAVE & CLOSE' : 'NEXT', + middleActionText: 'SAVE & CLOSE', ), ); } diff --git a/lib/screens/bolus/bolus_list.dart b/lib/screens/bolus/bolus_list.dart index f73dacb..d6218c1 100644 --- a/lib/screens/bolus/bolus_list.dart +++ b/lib/screens/bolus/bolus_list.dart @@ -167,7 +167,7 @@ class _BolusListScreenState extends State { child: Column( children: (bolus.units > 0 && (bolus.mgPerDl ?? bolus.mmolPerL ?? 0) > 0) ? [ - Text((((Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDl : bolus.mmolPerL)! / bolus.units)).toString()), + Text((((Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDl : bolus.mmolPerL ?? 0)! / bolus.units)).toString()), Text('${Settings.glucoseMeasurementSuffix} per unit', textAlign: TextAlign.center, textScaleFactor: 0.75), ] diff --git a/lib/screens/bolus/bolus_profile_detail.dart b/lib/screens/bolus/bolus_profile_detail.dart index fb69747..794ecf3 100644 --- a/lib/screens/bolus/bolus_profile_detail.dart +++ b/lib/screens/bolus/bolus_profile_detail.dart @@ -76,12 +76,13 @@ class _BolusProfileDetailScreenState extends State { detailBottomRow = DetailBottomRow( onCancel: handleCancelAction, - onSave: handleSaveAction, + onAction: handleSaveAction, + onMiddleAction: () => handleSaveAction(close: true), ); detailBottomRowWhileSaving = DetailBottomRow( onCancel: handleCancelAction, - onSave: null, + onAction: null, ); actionButton = null; @@ -181,25 +182,30 @@ class _BolusProfileDetailScreenState extends State { TimeOfDay? suggestedStartTime; TimeOfDay? suggestedEndTime; - _bolusRates.asMap().forEach((index, bolus) { - if (suggestedStartTime == null && suggestedEndTime == null) { - if (index == 0 && - (bolus.startTime.hour != 0 || bolus.startTime.minute != 0)) { - suggestedStartTime = const TimeOfDay(hour: 0, minute: 0); - suggestedEndTime = TimeOfDay.fromDateTime(bolus.startTime); - } else if ((index == _bolusRates.length - 1) && - (bolus.endTime.hour != 0 || bolus.endTime.minute != 0)) { - suggestedStartTime = TimeOfDay.fromDateTime(bolus.endTime); - suggestedEndTime = const TimeOfDay(hour: 0, minute: 0); - } else if (index != 0) { - var lastEndTime = _bolusRates[index - 1].endTime; - if (bolus.startTime.isAfter(lastEndTime)) { - suggestedStartTime = TimeOfDay.fromDateTime(lastEndTime); + if (_bolusRates.isEmpty) { + suggestedStartTime = const TimeOfDay(hour: 0, minute: 0); + suggestedEndTime = const TimeOfDay(hour: 0, minute: 0); + } else { + _bolusRates.asMap().forEach((index, bolus) { + if (suggestedStartTime == null && suggestedEndTime == null) { + if (index == 0 && + (bolus.startTime.hour != 0 || bolus.startTime.minute != 0)) { + suggestedStartTime = const TimeOfDay(hour: 0, minute: 0); suggestedEndTime = TimeOfDay.fromDateTime(bolus.startTime); + } else if ((index == _bolusRates.length - 1) && + (bolus.endTime.hour != 0 || bolus.endTime.minute != 0)) { + suggestedStartTime = TimeOfDay.fromDateTime(bolus.endTime); + suggestedEndTime = const TimeOfDay(hour: 0, minute: 0); + } else if (index != 0) { + var lastEndTime = _bolusRates[index - 1].endTime; + if (bolus.startTime.isAfter(lastEndTime)) { + suggestedStartTime = TimeOfDay.fromDateTime(lastEndTime); + suggestedEndTime = TimeOfDay.fromDateTime(bolus.startTime); + } } } - } - }); + }); + } Navigator.push( context, @@ -215,7 +221,7 @@ class _BolusProfileDetailScreenState extends State { ).then((result) => reload(message: result?[0])); } - void handleSaveAction() async { + void handleSaveAction({bool close = false}) async { setState(() { bottomNav = detailBottomRowWhileSaving; }); @@ -229,8 +235,23 @@ class _BolusProfileDetailScreenState extends State { notes: _notesController.text, ); BolusProfile.put(bolusProfile); - Navigator.pop(context, - ['${_isNew ? 'New' : ''} Bolus Profile saved', bolusProfile]); + + if (close) { + Navigator.pop(context, + ['${_isNew ? 'New' : ''} Bolus Profile saved', bolusProfile]); + } else { + if (_isNew) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + BolusProfileDetailScreen(id: bolusProfile.id), + ), + ).then((result) => Navigator.pop(context, result)); + } else { + reload(message: 'Bolus Profile saved'); + } + } } setState(() { diff --git a/lib/screens/log/log_entry/log_bolus_detail.dart b/lib/screens/log/log_entry/log_bolus_detail.dart index 45037b7..266fc0c 100644 --- a/lib/screens/log/log_entry/log_bolus_detail.dart +++ b/lib/screens/log/log_entry/log_bolus_detail.dart @@ -162,6 +162,21 @@ class _LogBolusDetailScreenState extends State { _meal = value; _mealController.text = (_meal ?? '').toString(); }); + if (_meal != null) { + if (_meal!.carbsPerPortion != null) { + _carbsController.text = (_meal!.carbsPerPortion).toString(); + } + if (_meal!.meal.hasValue) { + if (_meal!.meal.target!.delayedBolusDuration != null) { + _delayController.text = + (_meal!.meal.target?.delayedBolusDuration).toString(); + } + if (_meal!.meal.target!.delayedBolusDuration != null) { + _delayPercentage = _meal!.meal.target!.delayedBolusPercentage!; + } + } + calculateBolus(); + } } void updateDelayedRatio() { @@ -186,12 +201,12 @@ class _LogBolusDetailScreenState extends State { if (meal != null && meal.carbsPerPortion != null) { setState(() { _carbsController.text = meal.carbsPerPortion.toString(); - onChangeCarbs(); + calculateBolus(); }); } } - void onChangeCarbs() { + void calculateBolus() { setState(() { if (_rate != null && !_setManually) { _unitsController.text = ((double.tryParse(_carbsController.text) ?? 0) / @@ -356,7 +371,8 @@ class _LogBolusDetailScreenState extends State { LogBolus.put(delayedBolus); } - Navigator.pop(context, ['${_isNew ? 'New' : ''} Bolus Saved', logBolus, delayedBolus]); + Navigator.pop(context, + ['${_isNew ? 'New' : ''} Bolus Saved', logBolus, delayedBolus]); } setState(() { _isSaving = false; @@ -453,6 +469,7 @@ class _LogBolusDetailScreenState extends State { onChanged: (value) { setState(() { _setManually = value; + calculateBolus(); }); }, ), @@ -698,10 +715,11 @@ class _LogBolusDetailScreenState extends State { child: TextFormField( decoration: InputDecoration( labelText: 'Carbs', - suffixText: Settings.nutritionMeasurementSuffix, + suffixText: + Settings.nutritionMeasurementSuffix, ), controller: _carbsController, - onChanged: (_) => onChangeCarbs(), + onChanged: (_) => calculateBolus(), keyboardType: const TextInputType.numberWithOptions( decimal: true), @@ -709,17 +727,21 @@ class _LogBolusDetailScreenState extends State { ), ], ), - TextFormField( - decoration: const InputDecoration( - labelText: 'Delayed Bolus Duration', - suffixText: ' min', - ), - controller: _delayController, - onChanged: (value) => setState(() {}), - keyboardType: const TextInputType.numberWithOptions(), - ), - (int.tryParse(_delayController.text) ?? 0) != 0 - ? Slider( + Row( + children: [ + Expanded( + child: TextFormField( + decoration: const InputDecoration( + labelText: 'Delayed Bolus Duration', + suffixText: ' min', + ), + controller: _delayController, + onChanged: (value) => setState(() {}), + keyboardType: const TextInputType.numberWithOptions(), + ), + ), + Expanded( + child: Slider( label: '${_delayPercentage.floor().toString()}%', divisions: 100, value: _delayPercentage, @@ -733,8 +755,11 @@ class _LogBolusDetailScreenState extends State { updateDelayedRatio(); } : null, - ) - : Container(), + ), + ), + const Text('%', textScaleFactor: 1.5), + ], + ), Row( children: (int.tryParse(_delayController.text) ?? 0) != 0 ? [ @@ -792,7 +817,7 @@ class _LogBolusDetailScreenState extends State { ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: _isSaving ? null : handleSaveAction, + onAction: _isSaving ? null : handleSaveAction, ), ); } diff --git a/lib/screens/log/log_entry/log_bolus_list.dart b/lib/screens/log/log_entry/log_bolus_list.dart index 257e6c3..c109d08 100644 --- a/lib/screens/log/log_entry/log_bolus_list.dart +++ b/lib/screens/log/log_entry/log_bolus_list.dart @@ -88,6 +88,7 @@ class _LogBolusListScreenState extends State { ? Scrollbar( controller: _scrollController, child: ListView.builder( + padding: const EdgeInsets.all(10.0), controller: _scrollController, shrinkWrap: true, itemCount: widget.logBoli.length, @@ -99,9 +100,12 @@ class _LogBolusListScreenState extends State { return Card( child: ListTile( onTap: () => handleEditAction(bolus), - title: Text(titleText), + title: Text( + titleText.toUpperCase(), + style: Theme.of(context).textTheme.subtitle2, + ), subtitle: Text(bolus.carbs != null ? - 'for ${bolus.meal.target.toString()} (${bolus.carbs}${Settings.nutritionMeasurementSuffix} carbs)' + 'for ${(bolus.meal.target ?? '').toString()} (${bolus.carbs}${Settings.nutritionMeasurementSuffix} carbs)' : 'to correct ${Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDlCorrection : bolus.mmolPerLCorrection} ${Settings.glucoseMeasurementSuffix}'), trailing: Row( mainAxisSize: MainAxisSize.min, diff --git a/lib/screens/log/log_entry/log_entry.dart b/lib/screens/log/log_entry/log_entry.dart index a52a11e..886736b 100644 --- a/lib/screens/log/log_entry/log_entry.dart +++ b/lib/screens/log/log_entry/log_entry.dart @@ -31,7 +31,6 @@ class _LogEntryScreenState extends State { List _logBoli = []; bool _isNew = true; - bool _isSaving = false; final GlobalKey logEntryForm = GlobalKey(); final ScrollController _scrollController = ScrollController(); @@ -50,6 +49,7 @@ class _LogEntryScreenState extends State { late IconButton refreshButton; late IconButton closeButton; late DetailBottomRow detailBottomRow; + late DetailBottomRow detailBottomRowWhileSaving; FloatingActionButton? actionButton; List appBarActions = []; @@ -83,7 +83,13 @@ class _LogEntryScreenState extends State { detailBottomRow = DetailBottomRow( onCancel: handleCancelAction, - onSave: _isSaving ? null : handleSaveAction, + onAction: handleSaveAction, + onMiddleAction: () => handleSaveAction(close: true), + ); + + detailBottomRowWhileSaving = DetailBottomRow( + onCancel: handleCancelAction, + onAction: null, ); actionButton = null; @@ -140,7 +146,7 @@ class _LogEntryScreenState extends State { mgPerDl = int.tryParse(_mgPerDlController.text); setState(() { _mmolPerLController.text = - Utils.convertMgPerDlToMmolPerL(mgPerDl!).toString(); + Utils.convertMgPerDlToMmolPerL(mgPerDl ?? 0).toString(); }); } if (Settings.glucoseMeasurement != GlucoseMeasurement.mgPerDl && @@ -148,14 +154,14 @@ class _LogEntryScreenState extends State { mmolPerL = double.tryParse(_mmolPerLController.text); setState(() { _mgPerDlController.text = - Utils.convertMmolPerLToMgPerDl(mmolPerL!).toString(); + Utils.convertMmolPerLToMgPerDl(mmolPerL ?? 0).toString(); }); } } - void handleSaveAction() async { + void handleSaveAction({bool close = false}) async { setState(() { - _isSaving = true; + bottomNav = detailBottomRowWhileSaving; }); if (logEntryForm.currentState!.validate()) { LogEntry logEntry = LogEntry( @@ -167,11 +173,26 @@ class _LogEntryScreenState extends State { notes: _notesController.text, ); LogEntry.put(logEntry); - Navigator.pushReplacementNamed(context, '/log', - arguments: ['${_isNew ? 'New' : ''} Log Entry Saved', logEntry]); + + if (close) { + Navigator.pop( + context, ['${_isNew ? 'New' : ''} Log Entry Saved', logEntry]); + } else { + if (_isNew) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LogEntryScreen(id: logEntry.id), + ), + ).then((result) => Navigator.pop(context, result)); + } else { + reload(message: 'Log Entry Saved'); + } + } } + setState(() { - _isSaving = false; + bottomNav = detailBottomRow; }); } diff --git a/lib/screens/log/log_entry/log_meal_detail.dart b/lib/screens/log/log_entry/log_meal_detail.dart index bbedeb5..a40565c 100644 --- a/lib/screens/log/log_entry/log_meal_detail.dart +++ b/lib/screens/log/log_entry/log_meal_detail.dart @@ -670,7 +670,7 @@ class _LogMealDetailScreenState extends State { ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: _isSaving ? null : handleSaveAction, + onAction: _isSaving ? null : handleSaveAction, ), ); } diff --git a/lib/screens/log/log_entry/log_meal_list.dart b/lib/screens/log/log_entry/log_meal_list.dart index c562cb2..cfd6002 100644 --- a/lib/screens/log/log_entry/log_meal_list.dart +++ b/lib/screens/log/log_entry/log_meal_list.dart @@ -72,6 +72,7 @@ class _LogMealListScreenState extends State { ? Scrollbar( controller: _scrollController, child: ListView.builder( + padding: const EdgeInsets.all(10.0), controller: _scrollController, shrinkWrap: true, itemCount: widget.logMeals.length, @@ -82,7 +83,10 @@ class _LogMealListScreenState extends State { onTap: () => handleEditAction(meal), title: Row( children: [ - Expanded(child: Text(meal.value)), + Expanded(child: Text( + meal.value.toUpperCase(), + style: Theme.of(context).textTheme.subtitle2, + )), Expanded( child: Column( children: ((meal.carbsPerPortion ?? 0) > 0) diff --git a/lib/screens/log/log_event/log_event_detail.dart b/lib/screens/log/log_event/log_event_detail.dart index 9bade50..1d5c7cc 100644 --- a/lib/screens/log/log_event/log_event_detail.dart +++ b/lib/screens/log/log_event/log_event_detail.dart @@ -479,7 +479,7 @@ class _LogEventDetailScreenState extends State { ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: _isSaving ? null : handleSaveAction, + onAction: _isSaving ? null : handleSaveAction, ), ); } diff --git a/lib/screens/log/log_event/log_event_type_detail.dart b/lib/screens/log/log_event/log_event_type_detail.dart index 98ea5b4..3172e54 100644 --- a/lib/screens/log/log_event/log_event_type_detail.dart +++ b/lib/screens/log/log_event/log_event_type_detail.dart @@ -228,8 +228,6 @@ class _EventTypeDetailScreenState extends State { ).then((result) { setState(() { updateBolusProfile(result?[1]); - _bolusProfileController.text = - _bolusProfile.toString(); }); reload(message: result?[0]); }); @@ -293,7 +291,7 @@ class _EventTypeDetailScreenState extends State { ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: _isSaving ? null : handleSaveAction, + onAction: _isSaving ? null : handleSaveAction, ), ); } diff --git a/lib/screens/meal/meal_category_detail.dart b/lib/screens/meal/meal_category_detail.dart index 0708dac..0c02654 100644 --- a/lib/screens/meal/meal_category_detail.dart +++ b/lib/screens/meal/meal_category_detail.dart @@ -137,7 +137,7 @@ class _MealCategoryDetailScreenState extends State { ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: handleSaveAction, + onAction: handleSaveAction, ), ); } diff --git a/lib/screens/meal/meal_detail.dart b/lib/screens/meal/meal_detail.dart index d612ed1..95f6b29 100644 --- a/lib/screens/meal/meal_detail.dart +++ b/lib/screens/meal/meal_detail.dart @@ -228,7 +228,7 @@ class _MealDetailScreenState extends State { Future onSelectMealSource(MealSource? mealSource) async { setState(() { _mealSource = mealSource; - _mealSourceController.text = _mealSource.toString(); + _mealSourceController.text = (_mealSource ?? '').toString(); }); if (mealSource != null) { if (mealSource.defaultCarbsRatioAccuracy.hasValue) { @@ -458,6 +458,7 @@ class _MealDetailScreenState extends State { suffixText: ' min', ), controller: _delayedBolusDurationController, + onChanged: (value) => setState(() {}), keyboardType: const TextInputType.numberWithOptions(), ), ), @@ -469,11 +470,13 @@ class _MealDetailScreenState extends State { value: _delayedBolusPercentage, min: 0, max: 100, - onChanged: (value) { - setState(() { - _delayedBolusPercentage = value; - }); - }), + onChanged: _delayedBolusDurationController.text != '' + ? (value) { + setState(() { + _delayedBolusPercentage = value; + }); + } : null + ), ), const Text('%', textScaleFactor: 1.5), ], @@ -626,7 +629,7 @@ class _MealDetailScreenState extends State { ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: _isSaving ? null : handleSaveAction, + onAction: _isSaving ? null : handleSaveAction, ), ); } diff --git a/lib/screens/meal/meal_portion_type_detail.dart b/lib/screens/meal/meal_portion_type_detail.dart index 6b69054..85c774b 100644 --- a/lib/screens/meal/meal_portion_type_detail.dart +++ b/lib/screens/meal/meal_portion_type_detail.dart @@ -140,7 +140,7 @@ class _MealPortionTypeDetailScreenState ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: handleSaveAction, + onAction: handleSaveAction, ), ); } diff --git a/lib/screens/meal/meal_source_detail.dart b/lib/screens/meal/meal_source_detail.dart index cc7c228..ac941cd 100644 --- a/lib/screens/meal/meal_source_detail.dart +++ b/lib/screens/meal/meal_source_detail.dart @@ -362,7 +362,7 @@ class _MealSourceDetailScreenState extends State { ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: handleSaveAction, + onAction: handleSaveAction, ), ); } diff --git a/lib/settings.dart b/lib/settings.dart index 36fd80f..74d7594 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -175,12 +175,10 @@ class _SettingsScreenState extends State { label: 'Preferred Nutrition Measurement', items: nutritionMeasurementLabels, onChanged: (value) { - if (value != null) { - setState(() { - _nutritionMeasurementLabelController.text = value; - }); - saveSettings(); - } + setState(() { + _nutritionMeasurementLabelController.text = value ?? ''; + }); + saveSettings(); }, ), Padding( @@ -191,12 +189,10 @@ class _SettingsScreenState extends State { label: 'Preferred Glucose Measurement', items: glucoseMeasurementLabels, onChanged: (value) { - if (value != null) { - setState(() { - _glucoseMeasurementLabelController.text = value; - }); - saveSettings(); - } + setState(() { + _glucoseMeasurementLabelController.text = value ?? ''; + }); + saveSettings(); }, ), ),