import 'dart:math'; import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms.dart'; import 'package:diameter/config.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/navigation.dart'; import 'package:diameter/settings.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 { LogEntry? _logEntry; LogBolus? _logBolus; bool _isNew = true; bool _isSaving = false; final GlobalKey _logBolusForm = GlobalKey(); 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: ''); bool _setManually = false; BolusType _bolusType = BolusType.meal; LogMeal? _meal; Bolus? _rate; List _logMeals = []; @override void initState() { super.initState(); reload(); _logEntry = LogEntry.get(widget.logEntryId); _logMeals = LogMeal.getAllForEntry(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; _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 ?? moderateGlucoseMgPerDl).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 ?? moderateGlucoseMmolPerL).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; } } void reload() { if (widget.id != 0) { setState(() { _logBolus = LogBolus.get(widget.id); }); } _isNew = _logBolus == null; } void onSelectMeal(LogMeal meal) { setState(() { _meal = meal; if (meal.carbsPerPortion != null) { _carbsController.text = meal.carbsPerPortion.toString(); onChangeCarbs(); } }); } void onChangeCarbs() { setState(() { if (_rate != null && !_setManually) { _unitsController.text = ((double.tryParse(_carbsController.text) ?? 0) / (_rate!.carbs / _rate!.units)) .toString(); } }); } void onChangeGlucose({GlucoseMeasurement? calculateFrom}) { int? mgPerDlCurrent; int? mgPerDlTarget; int? mgPerDlCorrection; double? mmolPerLCurrent; double? mmolPerLTarget; double? mmolPerLCorrection; if (calculateFrom != GlucoseMeasurement.mmolPerL && _mgPerDlCurrentController.text != '' && _mgPerDlTargetController.text != '') { mgPerDlCurrent = int.tryParse(_mgPerDlCurrentController.text); mgPerDlTarget = int.tryParse(_mgPerDlTargetController.text); mgPerDlCorrection = max((mgPerDlCurrent ?? 0) - (mgPerDlTarget ?? 0), 0); } if (calculateFrom != GlucoseMeasurement.mgPerDl && _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(); if (_rate != null && !_setManually) { _unitsController.text = ((mgPerDlCorrection ?? 0) / ((_rate!.mgPerDl ?? 0) / _rate!.units)) .toString(); } }); } 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(); if (_rate != null && !_setManually) { _unitsController.text = ((mmolPerLCorrection ?? 0) / ((_rate!.mmolPerL ?? 0) / _rate!.units)) .toString(); } }); } } void handleSaveAction() async { setState(() { _isSaving = true; }); if (_logBolusForm.currentState!.validate()) { LogBolus logBolus = LogBolus( id: widget.id, units: double.tryParse(_unitsController.text) ?? 0, delay: int.tryParse(_delayController.text), setManually: _setManually, notes: _notesController.text, ); if (_bolusType == BolusType.meal) { logBolus.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); } logBolus.logEntry.target = _logEntry; logBolus.meal.target = _meal; logBolus.rate.target = _rate; LogBolus.put(logBolus); Navigator.pop(context, '${_isNew ? 'New' : ''} Bolus Saved'); } setState(() { _isSaving = false; }); } void handleCancelAction() { if (showConfirmationDialogOnCancel && ((_isNew && (_unitsController.text != '' || _carbsController.text != '' || _mgPerDlCurrentController.text != '' || _mgPerDlTargetController.text != '' || _mgPerDlCorrectionController.text != '' || _mmolPerLCurrentController.text != '' || _mmolPerLTargetController.text != '' || _mmolPerLCorrectionController.text != '' || _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 ?? ''))))) { Dialogs.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: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ FormWrapper( formState: _logBolusForm, fields: [ TextFormField( decoration: const InputDecoration( labelText: 'Bolus Units', suffixText: ' U', ), controller: _unitsController, onChanged: (_) { setState(() { _setManually = true; }); }, keyboardType: const TextInputType.numberWithOptions(decimal: true), ), BooleanFormField( value: _setManually, label: 'set manually', onChanged: (value) { setState(() { _setManually = value; }); }, ), Row( children: [ Expanded( child: RadioListTile( title: const Text('for glucose'), groupValue: _bolusType, value: BolusType.glucose, onChanged: (_) { setState(() { _bolusType = BolusType.glucose; }); }), ), Expanded( child: RadioListTile( title: const Text('for meal'), groupValue: _bolusType, value: BolusType.meal, onChanged: (value) { setState(() { _bolusType = BolusType.meal; }); }), ), ], ), Column( children: _bolusType == BolusType.glucose ? [ Row( children: glucoseMeasurement == GlucoseMeasurement.mgPerDl || glucoseDisplayMode == GlucoseDisplayMode.both || glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? [ Expanded( child: Padding( padding: const EdgeInsets.only(right: 5.0), child: TextFormField( decoration: const InputDecoration( labelText: 'Current', suffixText: 'mg/dl', ), controller: _mgPerDlCurrentController, onChanged: (_) => onChangeGlucose( calculateFrom: GlucoseMeasurement.mgPerDl), keyboardType: const TextInputType .numberWithOptions(), ), ), ), Expanded( child: Padding( padding: const EdgeInsets.symmetric( horizontal: 5.0), child: TextFormField( decoration: const InputDecoration( labelText: 'Target', suffixText: 'mg/dl', ), controller: _mgPerDlTargetController, onChanged: (_) => onChangeGlucose( calculateFrom: GlucoseMeasurement.mgPerDl), keyboardType: const TextInputType .numberWithOptions(), ), ), ), Expanded( child: Padding( padding: const EdgeInsets.only(left: 5.0), child: TextFormField( decoration: const InputDecoration( labelText: 'Correction', suffixText: 'mg/dl', ), controller: _mgPerDlCorrectionController, readOnly: true, ), ), ), glucoseDisplayMode == GlucoseDisplayMode.both || glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => onChangeGlucose( calculateFrom: GlucoseMeasurement .mmolPerL), icon: const Icon(Icons.calculate), ) : Container(), ] : [], ), Row( children: glucoseMeasurement == GlucoseMeasurement.mmolPerL || glucoseDisplayMode == GlucoseDisplayMode.both || glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? [ Expanded( child: Padding( padding: const EdgeInsets.only(right: 5), child: TextFormField( decoration: const InputDecoration( labelText: 'Current', suffixText: 'mmol/l', ), controller: _mmolPerLCurrentController, onChanged: (_) => onChangeGlucose( calculateFrom: GlucoseMeasurement.mmolPerL), keyboardType: const TextInputType .numberWithOptions(), ), ), ), Expanded( child: Padding( padding: const EdgeInsets.symmetric( horizontal: 5.0), child: TextFormField( decoration: const InputDecoration( labelText: 'Target', suffixText: 'mmol/l', ), controller: _mmolPerLTargetController, onChanged: (_) => onChangeGlucose( calculateFrom: GlucoseMeasurement.mmolPerL), keyboardType: const TextInputType .numberWithOptions(), ), ), ), Expanded( child: Padding( padding: const EdgeInsets.only(left: 5.0), child: TextFormField( decoration: const InputDecoration( labelText: 'Correction', suffixText: 'mmol/l', ), controller: _mmolPerLCorrectionController, readOnly: true, ), ), ), glucoseDisplayMode == GlucoseDisplayMode.both || glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => onChangeGlucose( calculateFrom: GlucoseMeasurement.mgPerDl), icon: const Icon(Icons.calculate), ) : Container(), ] : [], ), ] : [ AutoCompleteDropdownButton( selectedItem: _meal, label: 'Meal', items: _logMeals, onChanged: (value) { if (value != null) { onSelectMeal(value); } }, ), TextFormField( decoration: InputDecoration( labelText: 'Carbs', suffixText: nutritionMeasurement == NutritionMeasurement.grams ? 'g' : nutritionMeasurement == NutritionMeasurement.ounces ? 'oz' : '', ), controller: _carbsController, onChanged: (_) => onChangeCarbs(), keyboardType: const TextInputType.numberWithOptions( decimal: true), ), ], ), TextFormField( decoration: const InputDecoration( labelText: 'Delayed Bolus Duration', suffixText: ' min', ), controller: _delayController, keyboardType: const TextInputType.numberWithOptions(), ), TextFormField( controller: _notesController, decoration: const InputDecoration( labelText: 'Notes', alignLabelWithHint: true, ), keyboardType: TextInputType.multiline, ), ], ), ], ), ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, onSave: _isSaving ? null : handleSaveAction, ), ); } }