various usability improvements for ui

This commit is contained in:
spinel 2021-12-10 06:42:20 +01:00
parent 0dfcedff0b
commit 575130aba0
20 changed files with 371 additions and 189 deletions

25
TODO
View File

@ -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:

View File

@ -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<T> extends State<DetailBottomRow> {
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<T> extends State<DetailBottomRow> {
),
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),
),
],
),

View File

@ -183,7 +183,7 @@ class _AccuracyDetailScreenState extends State<AccuracyDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: _isSaving ? null : handleSaveAction,
onAction: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -32,6 +32,7 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
Basal? _basal;
bool _isNew = true;
bool _isSaving = false;
bool _isFinalRate = true;
final GlobalKey<FormState> _basalForm = GlobalKey<FormState>();
final ScrollController _scrollController = ScrollController();
@ -61,8 +62,8 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
_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<BasalDetailScreen> {
});
}
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<String?> validateTimePeriod() async {
@ -139,7 +152,7 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
});
}
void handleSaveAction() async {
void handleSaveAction({bool next = true}) async {
setState(() {
_isSaving = true;
});
@ -154,7 +167,30 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
);
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<BasalDetailScreen> {
_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<BasalDetailScreen> {
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<BasalDetailScreen> {
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<BasalDetailScreen> {
),
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',
),
);
}

View File

@ -78,12 +78,13 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
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<BasalProfileDetailScreen> {
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<BasalProfileDetailScreen> {
).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<BasalProfileDetailScreen> {
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;

View File

@ -33,6 +33,7 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
Bolus? _bolus;
bool _isNew = true;
bool _isSaving = false;
bool _isFinalRate = true;
final GlobalKey<FormState> _bolusForm = GlobalKey<FormState>();
final ScrollController _scrollController = ScrollController();
@ -61,16 +62,16 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
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<BolusDetailScreen> {
});
}
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<String?> validateTimePeriod() async {
String? error;
List<Bolus> bolusRates = Bolus.getAllForProfile(widget.bolusProfileId);
// check for duplicates
if (bolusRates
.where((other) =>
@ -148,7 +161,7 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
});
}
void handleSaveAction() async {
void handleSaveAction({bool next = true}) async {
setState(() {
_isSaving = true;
});
@ -167,7 +180,29 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
);
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<BolusDetailScreen> {
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<BolusDetailScreen> {
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<BolusDetailScreen> {
),
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<BolusDetailScreen> {
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<BolusDetailScreen> {
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<BolusDetailScreen> {
),
)
: 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<BolusDetailScreen> {
),
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',
),
);
}

View File

@ -167,7 +167,7 @@ class _BolusListScreenState extends State<BolusListScreen> {
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),
]

View File

@ -76,12 +76,13 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
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<BolusProfileDetailScreen> {
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<BolusProfileDetailScreen> {
).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<BolusProfileDetailScreen> {
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(() {

View File

@ -162,6 +162,21 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
_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<LogBolusDetailScreen> {
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<LogBolusDetailScreen> {
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<LogBolusDetailScreen> {
onChanged: (value) {
setState(() {
_setManually = value;
calculateBolus();
});
},
),
@ -698,10 +715,11 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
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<LogBolusDetailScreen> {
),
],
),
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<LogBolusDetailScreen> {
updateDelayedRatio();
}
: null,
)
: Container(),
),
),
const Text('%', textScaleFactor: 1.5),
],
),
Row(
children: (int.tryParse(_delayController.text) ?? 0) != 0
? [
@ -792,7 +817,7 @@ class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: _isSaving ? null : handleSaveAction,
onAction: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -88,6 +88,7 @@ class _LogBolusListScreenState extends State<LogBolusListScreen> {
? 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<LogBolusListScreen> {
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,

View File

@ -31,7 +31,6 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
List<LogBolus> _logBoli = [];
bool _isNew = true;
bool _isSaving = false;
final GlobalKey<FormState> logEntryForm = GlobalKey<FormState>();
final ScrollController _scrollController = ScrollController();
@ -50,6 +49,7 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
late IconButton refreshButton;
late IconButton closeButton;
late DetailBottomRow detailBottomRow;
late DetailBottomRow detailBottomRowWhileSaving;
FloatingActionButton? actionButton;
List<Widget> appBarActions = [];
@ -83,7 +83,13 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
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<LogEntryScreen> {
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<LogEntryScreen> {
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<LogEntryScreen> {
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;
});
}

View File

@ -670,7 +670,7 @@ class _LogMealDetailScreenState extends State<LogMealDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: _isSaving ? null : handleSaveAction,
onAction: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -72,6 +72,7 @@ class _LogMealListScreenState extends State<LogMealListScreen> {
? 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<LogMealListScreen> {
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)

View File

@ -479,7 +479,7 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: _isSaving ? null : handleSaveAction,
onAction: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -228,8 +228,6 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
).then((result) {
setState(() {
updateBolusProfile(result?[1]);
_bolusProfileController.text =
_bolusProfile.toString();
});
reload(message: result?[0]);
});
@ -293,7 +291,7 @@ class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: _isSaving ? null : handleSaveAction,
onAction: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -137,7 +137,7 @@ class _MealCategoryDetailScreenState extends State<MealCategoryDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
onAction: handleSaveAction,
),
);
}

View File

@ -228,7 +228,7 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
Future<void> 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<MealDetailScreen> {
suffixText: ' min',
),
controller: _delayedBolusDurationController,
onChanged: (value) => setState(() {}),
keyboardType: const TextInputType.numberWithOptions(),
),
),
@ -469,11 +470,13 @@ class _MealDetailScreenState extends State<MealDetailScreen> {
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<MealDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: _isSaving ? null : handleSaveAction,
onAction: _isSaving ? null : handleSaveAction,
),
);
}

View File

@ -140,7 +140,7 @@ class _MealPortionTypeDetailScreenState
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
onAction: handleSaveAction,
),
);
}

View File

@ -362,7 +362,7 @@ class _MealSourceDetailScreenState extends State<MealSourceDetailScreen> {
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: handleSaveAction,
onAction: handleSaveAction,
),
);
}

View File

@ -175,12 +175,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
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<SettingsScreen> {
label: 'Preferred Glucose Measurement',
items: glucoseMeasurementLabels,
onChanged: (value) {
if (value != null) {
setState(() {
_glucoseMeasurementLabelController.text = value;
});
saveSettings();
}
setState(() {
_glucoseMeasurementLabelController.text = value ?? '';
});
saveSettings();
},
),
),