diameter/lib/screens/log/log_entry/log_bolus_detail.dart

844 lines
34 KiB
Dart

import 'dart:math';
import 'package:diameter/components/detail.dart';
import 'package:diameter/components/forms/boolean_form_field.dart';
import 'package:diameter/components/forms/number_form_field.dart';
import 'package:diameter/utils/dialog_utils.dart';
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
import 'package:diameter/components/forms/form_wrapper.dart';
import 'package:diameter/models/bolus.dart';
import 'package:diameter/models/log_bolus.dart';
import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_meal.dart';
import 'package:diameter/models/settings.dart';
import 'package:diameter/navigation.dart';
import 'package:diameter/screens/log/log_entry/log_meal_detail.dart';
import 'package:diameter/utils/utils.dart';
import 'package:flutter/material.dart';
enum BolusType {
meal,
glucose,
}
enum GlucoseParameter {
mgdlCurrent,
mgdlTarget,
mgdlCorrection,
mmolCurrent,
mmolTarget,
mmolCorrection,
}
class LogBolusDetailScreen extends StatefulWidget {
static const String routeName = '/log-bolus';
final int logEntryId;
final int id;
const LogBolusDetailScreen({Key? key, this.logEntryId = 0, this.id = 0})
: super(key: key);
@override
_LogBolusDetailScreenState createState() => _LogBolusDetailScreenState();
}
class _LogBolusDetailScreenState extends State<LogBolusDetailScreen> {
LogEntry? _logEntry;
LogBolus? _logBolus;
bool _isNew = true;
bool _isSaving = false;
final GlobalKey<FormState> _logBolusForm = GlobalKey<FormState>();
final ScrollController _scrollController = ScrollController();
final _unitsController = TextEditingController(text: '');
final _carbsController = TextEditingController(text: '');
final _mgPerDlCurrentController = TextEditingController(text: '');
final _mgPerDlTargetController = TextEditingController(text: '');
final _mgPerDlCorrectionController = TextEditingController(text: '');
final _mmolPerLCurrentController = TextEditingController(text: '');
final _mmolPerLTargetController = TextEditingController(text: '');
final _mmolPerLCorrectionController = TextEditingController(text: '');
final _delayController = TextEditingController(text: '');
final _notesController = TextEditingController(text: '');
final _delayedUnitsController = TextEditingController(text: '');
final _immediateUnitsController = TextEditingController(text: '');
final _mealController = TextEditingController(text: '');
bool _setManually = false;
BolusType _bolusType = BolusType.meal;
LogMeal? _meal;
Bolus? _rate;
double _delayPercentage = 0;
List<LogMeal> _logMeals = [];
@override
void initState() {
super.initState();
reload();
_logEntry = LogEntry.get(widget.logEntryId);
_logMeals = LogMeal.getRecentWithoutBolus(widget.logEntryId);
if (widget.id != 0) {
_carbsController.text = (_logBolus!.carbs ?? '').toString();
_delayController.text = (_logBolus!.delay ?? '').toString();
_notesController.text = _logBolus!.notes ?? '';
_setManually = _logBolus!.setManually;
_meal = _logBolus!.meal.target;
_mealController.text = (_meal ?? '').toString();
_rate = _logBolus!.rate.target;
}
_rate ??= Bolus.getRateForTime(_logEntry?.time);
_mgPerDlCurrentController.text = (_logBolus?.mgPerDlCurrent ??
(LogEntry.hasUncorrectedGlucose(widget.logEntryId)
? _logEntry?.mgPerDl ?? 0
: 0))
.toString();
_mgPerDlTargetController.text =
(_logBolus?.mgPerDlTarget ?? Settings.targetMgPerDl).toString();
_mgPerDlCorrectionController.text = (_logBolus?.mgPerDlCorrection ??
max(
(int.tryParse(_mgPerDlCurrentController.text) ?? 0) -
(int.tryParse(_mgPerDlTargetController.text) ?? 0),
0))
.toString();
_mmolPerLCurrentController.text = (_logBolus?.mmolPerLCurrent ??
(LogEntry.hasUncorrectedGlucose(widget.logEntryId)
? _logEntry?.mmolPerL ?? 0
: 0))
.toString();
_mmolPerLTargetController.text =
(_logBolus?.mmolPerLTarget ?? Settings.targetMmolPerL).toString();
_mmolPerLCorrectionController.text = (_logBolus?.mmolPerLCorrection ??
max(
(double.tryParse(_mmolPerLCurrentController.text) ?? 0) -
(double.tryParse(_mmolPerLTargetController.text) ?? 0),
0))
.toString();
_unitsController.text = (_logBolus?.units ??
(_rate != null && !_setManually
? ((int.tryParse(_mgPerDlCorrectionController.text) ?? 0) /
((_rate!.mgPerDl ?? 0) / _rate!.units))
: 0))
.toString();
if (widget.id == 0 && LogEntry.hasUncorrectedGlucose(widget.logEntryId)) {
_bolusType = BolusType.glucose;
}
calculateBolus();
}
@override
void dispose() {
_scrollController.dispose();
_unitsController.dispose();
_carbsController.dispose();
_mgPerDlCurrentController.dispose();
_mgPerDlTargetController.dispose();
_mgPerDlCorrectionController.dispose();
_mmolPerLCurrentController.dispose();
_mmolPerLTargetController.dispose();
_mmolPerLCorrectionController.dispose();
_delayController.dispose();
_notesController.dispose();
_delayedUnitsController.dispose();
_immediateUnitsController.dispose();
_mealController.dispose();
super.dispose();
}
void reload({String? message}) {
if (widget.id != 0) {
setState(() {
_logBolus = LogBolus.get(widget.id);
});
}
_isNew = _logBolus == null;
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
void updateLogMeal(LogMeal? value) {
setState(() {
_meal = value;
_mealController.text = (_meal ?? '').toString();
});
if (_meal != null) {
if (_meal!.totalCarbs != null) {
_carbsController.text = (_meal!.totalCarbs).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 onSelectMeal(LogMeal? meal) {
updateLogMeal(meal);
if (meal != null && meal.totalCarbs != null) {
setState(() {
_carbsController.text = meal.totalCarbs.toString();
calculateBolus();
});
}
}
void calculateBolus() {
if (_rate != null && !_setManually) {
double? units;
if (_bolusType == BolusType.glucose) {
if (Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl) {
units = (int.tryParse(_mgPerDlCorrectionController.text) ?? 0) /
(_rate!.mgPerDl ?? 1) /
_rate!.units;
}
if (Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL) {
units = (int.tryParse(_mmolPerLCorrectionController.text) ?? 0) /
(_rate!.mmolPerL ?? 1) /
_rate!.units;
}
}
if (_bolusType == BolusType.meal) {
units = (double.tryParse(_carbsController.text) ?? 0) /
(_rate!.carbs / _rate!.units);
}
updateDelayedRatio(totalUnitsUpdate: units);
}
}
void onChangeGlucose() {
int? mgPerDlCurrent;
int? mgPerDlTarget;
int? mgPerDlCorrection;
double? mmolPerLCurrent;
double? mmolPerLTarget;
double? mmolPerLCorrection;
if (Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl &&
_mgPerDlCurrentController.text != '' &&
_mgPerDlTargetController.text != '') {
mgPerDlCurrent = int.tryParse(_mgPerDlCurrentController.text);
mgPerDlTarget = int.tryParse(_mgPerDlTargetController.text);
mgPerDlCorrection = max((mgPerDlCurrent ?? 0) - (mgPerDlTarget ?? 0), 0);
}
if (Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL &&
_mmolPerLCurrentController.text != '') {
mmolPerLCurrent = double.tryParse(_mmolPerLCurrentController.text);
mmolPerLTarget = double.tryParse(_mmolPerLTargetController.text);
mmolPerLCorrection =
max((mmolPerLCurrent ?? 0) - (mmolPerLTarget ?? 0), 0);
}
if ((mgPerDlCurrent != null && mmolPerLCurrent == null) ||
(mgPerDlTarget != null && mmolPerLTarget == null) ||
(mgPerDlCorrection != null && mmolPerLCorrection == null)) {
setState(() {
_mgPerDlCorrectionController.text = (mgPerDlCorrection ?? 0).toString();
_mmolPerLCurrentController.text =
Utils.convertMgPerDlToMmolPerL(mgPerDlCurrent ?? 0).toString();
_mmolPerLTargetController.text =
Utils.convertMgPerDlToMmolPerL(mgPerDlTarget ?? 0).toString();
_mmolPerLCorrectionController.text =
Utils.convertMgPerDlToMmolPerL(mgPerDlCorrection ?? 0).toString();
calculateBolus();
});
}
if ((mmolPerLCurrent != null && mgPerDlCurrent == null) ||
(mmolPerLTarget != null && mgPerDlTarget == null) ||
(mmolPerLCorrection != null && mgPerDlCorrection == null)) {
setState(() {
_mmolPerLCurrentController.text = (mmolPerLCorrection ?? 0).toString();
_mgPerDlCurrentController.text =
Utils.convertMmolPerLToMgPerDl(mmolPerLCurrent ?? 0).toString();
_mgPerDlTargetController.text =
Utils.convertMmolPerLToMgPerDl(mmolPerLTarget ?? 0).toString();
_mgPerDlCorrectionController.text =
Utils.convertMmolPerLToMgPerDl(mmolPerLCorrection ?? 0).toString();
calculateBolus();
});
}
}
void updateDelayedRatio({
double? totalUnitsUpdate,
double? delayedUnitsUpdate,
double? immediateUnitsUpdate,
double? percentageUpdate
}) {
int precision = Utils.getFractionDigitsLength(Settings.insulinSteps);
double? totalUnits;
double? delayedUnits;
double? immediateUnits;
if (totalUnitsUpdate != null) {
totalUnits = Utils.roundToMultipleOfBase(totalUnitsUpdate, Settings.insulinSteps);
} else if (double.tryParse(_unitsController.text) != null) {
totalUnits = Utils.roundToMultipleOfBase(double.tryParse(_unitsController.text)!, Settings.insulinSteps);
}
if (delayedUnitsUpdate != null) {
delayedUnits = Utils.roundToMultipleOfBase(delayedUnitsUpdate, Settings.insulinSteps);
} else if (double.tryParse(_delayedUnitsController.text) != null) {
delayedUnits = Utils.roundToMultipleOfBase(double.tryParse(_delayedUnitsController.text)!, Settings.insulinSteps);
}
if (immediateUnitsUpdate != null) {
immediateUnits = Utils.roundToMultipleOfBase(immediateUnitsUpdate, Settings.insulinSteps);
} else if (double.tryParse(_immediateUnitsController.text) != null) {
immediateUnits = Utils.roundToMultipleOfBase(double.tryParse(_immediateUnitsController.text)!, Settings.insulinSteps);
}
if (totalUnits == null) {
if (percentageUpdate != null) {
if (immediateUnits != null) {
totalUnits = immediateUnits / (100 - percentageUpdate) * 100;
} else if (delayedUnits != null) {
totalUnits = delayedUnits / percentageUpdate * 100;
}
} else if (delayedUnits != null && immediateUnits != null) {
totalUnits = Utils.addDoublesWithPrecision(
delayedUnits, immediateUnits, precision);
}
if (totalUnits != null) {
totalUnits =
Utils.roundToMultipleOfBase(totalUnits, Settings.insulinSteps);
}
}
setState(() {
_unitsController.text = Utils.toStringMatchingTemplateFractionPrecision(
totalUnits ?? 0, Settings.insulinSteps);
});
if (totalUnits != null) {
double percentage = percentageUpdate ?? _delayPercentage;
if (totalUnitsUpdate != null || percentageUpdate != null) {
immediateUnits = Utils.roundToMultipleOfBase(
totalUnits * (100 - percentage) / 100, Settings.insulinSteps);
} else if (delayedUnitsUpdate != null) {
immediateUnits = totalUnits - delayedUnits!;
}
if (immediateUnits != null) {
delayedUnits = Utils.addDoublesWithPrecision(
totalUnits, -immediateUnits, precision);
setState(() {
_immediateUnitsController.text =
Utils.toStringMatchingTemplateFractionPrecision(
immediateUnits!, Settings.insulinSteps);
_delayedUnitsController.text =
Utils.toStringMatchingTemplateFractionPrecision(
delayedUnits!, Settings.insulinSteps);
if (totalUnits != 0) {
_delayPercentage = delayedUnits / totalUnits! * 100;
}
});
}
}
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (_logBolusForm.currentState!.validate()) {
LogBolus logBolus;
LogBolus? delayedBolus;
if ((int.tryParse(_delayController.text) ?? 0) != 0 &&
_delayPercentage != 0 &&
_delayPercentage != 100) {
logBolus = LogBolus(
id: widget.id,
units: double.tryParse(_immediateUnitsController.text) ?? 0,
setManually: _setManually,
notes: _notesController.text,
);
delayedBolus = LogBolus(
delay: int.tryParse(_delayController.text),
units: double.tryParse(_delayedUnitsController.text) ?? 0,
setManually: _setManually,
notes: _notesController.text,
);
} else {
logBolus = LogBolus(
id: widget.id,
units: double.tryParse(_unitsController.text) ?? 0,
delay: _delayPercentage == 100
? int.tryParse(_delayController.text)
: null,
setManually: _setManually,
notes: _notesController.text,
);
}
if (_bolusType == BolusType.meal) {
logBolus.carbs = double.tryParse(_carbsController.text);
if (delayedBolus != null) {
delayedBolus.carbs = double.tryParse(_carbsController.text);
}
logBolus.mgPerDlCurrent = null;
logBolus.mmolPerLCurrent = null;
} else {
logBolus.carbs = null;
logBolus.mgPerDlCurrent = int.tryParse(_mgPerDlCurrentController.text);
logBolus.mmolPerLCurrent =
double.tryParse(_mmolPerLCurrentController.text);
logBolus.mgPerDlTarget = int.tryParse(_mgPerDlTargetController.text);
logBolus.mmolPerLTarget =
double.tryParse(_mmolPerLTargetController.text);
logBolus.mgPerDlCorrection =
int.tryParse(_mgPerDlCorrectionController.text);
logBolus.mmolPerLCorrection =
double.tryParse(_mmolPerLCorrectionController.text);
if (delayedBolus != null) {
delayedBolus.mgPerDlCurrent =
int.tryParse(_mgPerDlCurrentController.text);
delayedBolus.mmolPerLCurrent =
double.tryParse(_mmolPerLCurrentController.text);
delayedBolus.mgPerDlTarget =
int.tryParse(_mgPerDlTargetController.text);
delayedBolus.mmolPerLTarget =
double.tryParse(_mmolPerLTargetController.text);
delayedBolus.mgPerDlCorrection =
int.tryParse(_mgPerDlCorrectionController.text);
delayedBolus.mmolPerLCorrection =
double.tryParse(_mmolPerLCorrectionController.text);
}
}
logBolus.logEntry.target = _logEntry;
logBolus.meal.target = _meal;
logBolus.rate.target = _rate;
LogBolus.put(logBolus);
if (delayedBolus != null) {
delayedBolus.logEntry.target = _logEntry;
delayedBolus.meal.target = _meal;
delayedBolus.rate.target = _rate;
LogBolus.put(delayedBolus);
}
Navigator.pop(context,
['${_isNew ? 'New' : ''} Bolus Saved', logBolus, delayedBolus]);
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
if (Settings.get().showConfirmationDialogOnCancel &&
((_isNew &&
(_carbsController.text != '' ||
(_bolusType == BolusType.glucose &&
(_mgPerDlCurrentController.text !=
(_logEntry?.mgPerDl.toString() ?? '') ||
_mmolPerLCurrentController.text !=
(_logEntry?.mmolPerL.toString() ?? ''))) ||
_mgPerDlTargetController.text !=
Settings.targetMgPerDl.toString() ||
_mmolPerLTargetController.text !=
Settings.targetMmolPerL.toString() ||
_delayController.text != '' ||
_setManually ||
_notesController.text != '')) ||
(!_isNew &&
(double.tryParse(_unitsController.text) != _logBolus!.units ||
double.tryParse(_carbsController.text) !=
_logBolus!.carbs ||
int.tryParse(_mgPerDlCurrentController.text) !=
_logBolus!.mgPerDlCurrent ||
int.tryParse(_mgPerDlTargetController.text) !=
_logBolus!.mgPerDlTarget ||
int.tryParse(_mgPerDlCorrectionController.text) !=
_logBolus!.mgPerDlCorrection ||
double.tryParse(_mmolPerLCurrentController.text) !=
_logBolus!.mmolPerLCurrent ||
double.tryParse(_mmolPerLTargetController.text) !=
_logBolus!.mmolPerLTarget ||
double.tryParse(_mmolPerLCorrectionController.text) !=
_logBolus!.mmolPerLCorrection ||
int.tryParse(_delayController.text) != _logBolus!.delay ||
_setManually != _logBolus!.setManually ||
_notesController.text != (_logBolus!.notes ?? ''))))) {
DialogUtils.showCancelConfirmationDialog(
context: context,
isNew: _isNew,
onSave: handleSaveAction,
);
} else {
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_isNew ? 'New Bolus' : 'Edit Bolus'),
),
drawer: const Navigation(currentLocation: LogBolusDetailScreen.routeName),
body: Scrollbar(
controller: _scrollController,
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
FormWrapper(
formState: _logBolusForm,
fields: [
Row(
children: [
Expanded(
child: NumberFormField(
label: 'Bolus Units',
suffix: ' U',
controller: _unitsController,
step: Settings.insulinSteps,
autoRoundToMultipleOfStep: true,
onChanged: (value) {
setState(() {
_setManually = true;
});
updateDelayedRatio(totalUnitsUpdate: value);
},
),
),
Expanded(
child: BooleanFormField(
contentPadding: const EdgeInsets.only(
left: 10.0, right: 10.0, top: 10.0),
value: _setManually,
label: 'set manually',
onChanged: (value) {
setState(() {
_setManually = value;
calculateBolus();
});
},
),
),
],
),
Row(
children: [
Expanded(
child: RadioListTile(
title: const Text('for glucose'),
groupValue: _bolusType,
value: BolusType.glucose,
onChanged: (_) {
setState(() {
_bolusType = BolusType.glucose;
calculateBolus();
});
}),
),
Expanded(
child: RadioListTile(
title: const Text('for meal'),
groupValue: _bolusType,
value: BolusType.meal,
onChanged: (value) {
setState(() {
_bolusType = BolusType.meal;
calculateBolus();
});
}),
),
],
),
Column(
children: _bolusType == BolusType.glucose
? [
Row(
children: Settings.glucoseMeasurement ==
GlucoseMeasurement.mgPerDl ||
[
GlucoseDisplayMode.both,
GlucoseDisplayMode.bothForDetail
].contains(Settings.glucoseDisplayMode)
? [
Expanded(
child: Padding(
padding:
const EdgeInsets.only(right: 5.0),
child: NumberFormField(
label: 'Current',
suffix: 'mg/dl',
controller:
_mgPerDlCurrentController,
onChanged: (_) => onChangeGlucose(),
showSteppers: false,
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 5.0),
child: NumberFormField(
label: 'Target',
suffix: 'mg/dl',
controller:
_mgPerDlTargetController,
onChanged: (_) => onChangeGlucose(),
showSteppers: false,
),
),
),
Expanded(
child: Padding(
padding:
const EdgeInsets.only(left: 5.0),
child: TextFormField(
decoration: const InputDecoration(
labelText: 'Correction',
suffixText: 'mg/dl',
),
controller:
_mgPerDlCorrectionController,
readOnly: true,
),
),
),
]
: [],
),
Padding(
padding: EdgeInsets.only(
top: [
GlucoseDisplayMode.both,
GlucoseDisplayMode.bothForDetail
].contains(Settings.glucoseDisplayMode)
? 10.0
: 0.0),
child: Row(
children: Settings.glucoseMeasurement ==
GlucoseMeasurement.mmolPerL ||
[
GlucoseDisplayMode.both,
GlucoseDisplayMode.bothForDetail
].contains(Settings.glucoseDisplayMode)
? [
Expanded(
child: Padding(
padding: const EdgeInsets.only(
right: 5.0),
child: NumberFormField(
label: 'Current',
suffix: 'mmol/l',
controller:
_mmolPerLCurrentController,
onChanged: (_) =>
onChangeGlucose(),
showSteppers: false,
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 5.0),
child: NumberFormField(
label: 'Target',
suffix: 'mmol/l',
controller:
_mmolPerLTargetController,
onChanged: (_) =>
onChangeGlucose(),
showSteppers: false,
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 5.0),
child: TextFormField(
decoration: const InputDecoration(
labelText: 'Correction',
suffixText: 'mmol/l',
),
controller:
_mmolPerLCorrectionController,
readOnly: true,
),
),
),
]
: [],
),
),
]
: [
Row(
children: [
Expanded(
child: AutoCompleteDropdownButton<LogMeal>(
controller: _mealController,
selectedItem: _meal,
label: 'Meal',
items: _logMeals,
onChanged: onSelectMeal,
),
),
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => _meal == null
? const LogMealDetailScreen()
: LogMealDetailScreen(
id: _meal!.id),
),
).then((result) {
updateLogMeal(result?[1]);
reload(message: result?[0]);
});
},
icon: Icon(
_meal == null ? Icons.add : Icons.edit),
),
],
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: NumberFormField(
label: 'Carbs',
suffix: Settings.nutritionMeasurementSuffix,
controller: _carbsController,
step: Settings.nutritionSteps,
onChanged: (value) {
_carbsController.text =
(value ?? 0).toString();
calculateBolus();
},
),
),
],
),
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,
min: 0,
max: 100,
onChanged: (value) {
updateDelayedRatio(percentageUpdate: value);
},
),
),
const Text('%', textScaleFactor: 1.5),
],
),
Row(
children: (int.tryParse(_delayController.text) ?? 0) != 0
? [
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 5.0),
child: NumberFormField(
label: 'Immediate Bolus',
suffix: ' U',
controller: _immediateUnitsController,
max: double.tryParse(_unitsController.text),
step: Settings.insulinSteps,
readOnly: true,
onChanged: (value) => updateDelayedRatio(
immediateUnitsUpdate: value),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 5.0),
child: NumberFormField(
label: 'Delayed Bolus',
suffix: ' U',
controller: _delayedUnitsController,
max: double.tryParse(_unitsController.text),
step: Settings.insulinSteps,
readOnly: true,
onChanged: (value) => {
updateDelayedRatio(
delayedUnitsUpdate: value),
}),
),
),
]
: [],
),
TextFormField(
controller: _notesController,
decoration: const InputDecoration(
labelText: 'Notes',
),
keyboardType: TextInputType.multiline,
minLines: 2,
maxLines: 5,
),
],
),
],
),
),
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onAction: _isSaving ? null : handleSaveAction,
),
);
}
}