import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/forms.dart'; import 'package:diameter/config.dart'; import 'package:diameter/models/accuracy.dart'; import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_meal.dart'; import 'package:diameter/models/meal.dart'; import 'package:diameter/models/meal_category.dart'; import 'package:diameter/models/meal_portion_type.dart'; import 'package:diameter/models/meal_source.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/settings.dart'; import 'package:flutter/material.dart'; class LogMealDetailScreen extends StatefulWidget { static const String routeName = '/log-meal'; final LogEntry logEntry; final LogMeal? logMeal; const LogMealDetailScreen({Key? key, required this.logEntry, this.logMeal}) : super(key: key); @override _LogMealDetailScreenState createState() => _LogMealDetailScreenState(); } class _LogMealDetailScreenState extends State { final GlobalKey _logMealForm = GlobalKey(); final _valueController = TextEditingController(text: ''); final _carbsRatioController = TextEditingController(text: ''); final _portionSizeController = TextEditingController(text: ''); final _carbsPerPortionController = TextEditingController(text: ''); final _bolusController = TextEditingController(text: ''); final _delayedBolusRateController = TextEditingController(text: ''); final _delayedBolusDurationController = TextEditingController(text: ''); final _notesController = TextEditingController(text: ''); String? _meal; String? _source; String? _category; String? _portionType; String? _portionSizeAccuracy; String? _carbsRatioAccuracy; late Future> _meals; late Future> _mealCategories; late Future> _mealPortionTypes; late Future> _mealSources; late Future> _portionSizeAccuracies; late Future> _carbsRatioAccuracies; @override void initState() { super.initState(); if (widget.logMeal != null) { _valueController.text = widget.logMeal!.value; _carbsRatioController.text = (widget.logMeal!.carbsRatio ?? '').toString(); _portionSizeController.text = (widget.logMeal!.portionSize ?? '').toString(); _carbsPerPortionController.text = (widget.logMeal!.carbsPerPortion ?? '').toString(); _bolusController.text = (widget.logMeal!.bolus ?? '').toString(); _delayedBolusRateController.text = (widget.logMeal!.delayedBolusRate ?? '').toString(); _delayedBolusDurationController.text = (widget.logMeal!.delayedBolusDuration ?? '').toString(); _notesController.text = widget.logMeal!.notes ?? ''; _meal = widget.logMeal!.meal; _source = widget.logMeal!.source; _category = widget.logMeal!.category; _portionType = widget.logMeal!.portionType; _portionSizeAccuracy = widget.logMeal!.portionSizeAccuracy; _carbsRatioAccuracy = widget.logMeal!.carbsRatioAccuracy; } _meals = Meal.fetchAll(); _mealCategories = MealCategory.fetchAll(); _mealPortionTypes = MealPortionType.fetchAll(); _mealSources = MealSource.fetchAll(); _portionSizeAccuracies = Accuracy.fetchAllForPortionSize(); _carbsRatioAccuracies = Accuracy.fetchAllForCarbsRatio(); } void handleSaveAction() async { if (_logMealForm.currentState!.validate()) { bool isNew = widget.logMeal == null; isNew ? await LogMeal.save( logEntry: widget.logEntry.objectId!, meal: _meal, value: _valueController.text, source: _source, category: _category, portionType: _portionType, carbsRatio: double.tryParse(_carbsRatioController.text), portionSize: double.tryParse(_portionSizeController.text), carbsPerPortion: double.tryParse(_carbsPerPortionController.text), portionSizeAccuracy: _portionSizeAccuracy, carbsRatioAccuracy: _carbsRatioAccuracy, delayedBolusDuration: int.tryParse(_delayedBolusDurationController.text), delayedBolusRate: double.tryParse(_delayedBolusRateController.text), notes: _notesController.text, ) : await LogMeal.update( widget.logMeal!.objectId!, meal: _meal, value: _valueController.text, source: _source, category: _category, portionType: _portionType, carbsRatio: double.tryParse(_carbsRatioController.text), portionSize: double.tryParse(_portionSizeController.text), carbsPerPortion: double.tryParse(_carbsPerPortionController.text), portionSizeAccuracy: _portionSizeAccuracy, carbsRatioAccuracy: _carbsRatioAccuracy, delayedBolusDuration: int.tryParse(_delayedBolusDurationController.text), delayedBolusRate: double.tryParse(_delayedBolusRateController.text), notes: _notesController.text, ); Navigator.pop(context, '${isNew ? 'New' : ''} Meal Saved'); } } void handleCancelAction() { bool isNew = widget.logMeal == null; if (showConfirmationDialogOnCancel && ((isNew && (_valueController.text != '' || _meal != null || _source != null || _category != null || _portionType != null || double.tryParse(_carbsRatioController.text) != null || double.tryParse(_portionSizeController.text) != null || double.tryParse(_carbsPerPortionController.text) != null || _carbsRatioAccuracy != null || _portionSizeAccuracy != null || int.tryParse(_delayedBolusDurationController.text) != null || double.tryParse(_delayedBolusRateController.text) != null || _notesController.text != '')) || (!isNew && (_valueController.text != widget.logMeal!.value || _meal != widget.logMeal!.meal || _source != widget.logMeal!.source || _category != widget.logMeal!.category || _portionType != widget.logMeal!.portionType || double.tryParse(_carbsRatioController.text) != widget.logMeal!.carbsRatio || double.tryParse(_portionSizeController.text) != widget.logMeal!.portionSize || double.tryParse(_carbsPerPortionController.text) != widget.logMeal!.carbsPerPortion || _carbsRatioAccuracy != widget.logMeal!.carbsRatioAccuracy || _portionSizeAccuracy != widget.logMeal!.portionSizeAccuracy || int.tryParse(_delayedBolusDurationController.text) != widget.logMeal!.delayedBolusDuration || double.tryParse(_delayedBolusRateController.text) != widget.logMeal!.delayedBolusRate || _notesController.text != (widget.logMeal!.notes ?? ''))))) { Dialogs.showCancelConfirmationDialog( context: context, isNew: isNew, onSave: handleSaveAction, ); } else { Navigator.pop(context); } } @override Widget build(BuildContext context) { bool isNew = widget.logMeal == null; return Scaffold( appBar: AppBar( title: Text(isNew ? 'New Meal' : widget.logMeal!.value), ), drawer: const Navigation(currentLocation: LogMealDetailScreen.routeName), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ StyledForm( formState: _logMealForm, fields: [ // TODO: autofill all associated fields on selecting a meal TextFormField( controller: _valueController, decoration: const InputDecoration( labelText: 'Name', ), validator: (value) { if (value!.trim().isEmpty) { return 'Empty name'; } return null; }, ), StyledFutureDropdownButton( selectedItem: _meal, label: 'Meal', items: _meals, getItemValue: (item) => item.objectId, renderItem: (item) => Text(item.value), onChanged: (value) { setState(() { _meal = value; }); }, ), StyledFutureDropdownButton( selectedItem: _source, label: 'Meal Source', items: _mealSources, getItemValue: (item) => item.objectId, renderItem: (item) => Text(item.value), onChanged: (value) { setState(() { _source = value; }); }, ), StyledFutureDropdownButton( selectedItem: _category, label: 'Meal Category', items: _mealCategories, getItemValue: (item) => item.objectId, renderItem: (item) => Text(item.value), onChanged: (value) { setState(() { _category = value; }); }, ), StyledFutureDropdownButton( selectedItem: _portionType, label: 'Meal Portion Type', items: _mealPortionTypes, getItemValue: (item) => item.objectId, renderItem: (item) => Text(item.value), onChanged: (value) { setState(() { _portionType = value; }); }, ), // TODO: if 2 out of the 3 following fields are given, calc 3rd TextFormField( decoration: const InputDecoration( labelText: 'Carbs ratio', suffixText: '%', ), controller: _carbsRatioController, keyboardType: const TextInputType.numberWithOptions(decimal: true), ), TextFormField( decoration: InputDecoration( labelText: 'Portion size', suffixText: nutritionMeasurement == NutritionMeasurement.grams ? 'g' : nutritionMeasurement == NutritionMeasurement.ounces ? 'oz' : '', ), controller: _portionSizeController, keyboardType: const TextInputType.numberWithOptions(decimal: true), ), TextFormField( decoration: InputDecoration( labelText: 'Carbs per portion', suffixText: nutritionMeasurement == NutritionMeasurement.grams ? 'g' : nutritionMeasurement == NutritionMeasurement.ounces ? 'oz' : '', ), controller: _carbsPerPortionController, keyboardType: const TextInputType.numberWithOptions(decimal: true), ), StyledFutureDropdownButton( selectedItem: _carbsRatioAccuracy, label: 'Carbs Ratio Accuracy', items: _carbsRatioAccuracies, getItemValue: (item) => item.objectId, renderItem: (item) => Text(item.value), onChanged: (value) { setState(() { _carbsRatioAccuracy = value; }); }, ), TextFormField( decoration: const InputDecoration( labelText: 'Bolus Units', suffixText: ' U', ), controller: _bolusController, keyboardType: const TextInputType.numberWithOptions(decimal: true), ), TextFormField( decoration: const InputDecoration( labelText: 'Delayed Bolus Duration', suffixText: ' min', ), controller: _delayedBolusDurationController, keyboardType: const TextInputType.numberWithOptions(), ), TextFormField( decoration: const InputDecoration( labelText: 'Delayed Bolus Units', suffixText: ' U', alignLabelWithHint: true, ), controller: _delayedBolusRateController, keyboardType: const TextInputType.numberWithOptions(decimal: true), ), // TODO: autofill the following fields on selecting a source StyledFutureDropdownButton( selectedItem: _portionSizeAccuracy, label: 'Portion Size Accuracy', items: _portionSizeAccuracies, getItemValue: (item) => item.objectId, renderItem: (item) => Text(item.value), onChanged: (value) { setState(() { _portionSizeAccuracy = value; }); }, ), TextFormField( controller: _notesController, decoration: const InputDecoration( labelText: 'Notes', alignLabelWithHint: true, ), keyboardType: TextInputType.multiline, ), ], ), ], ), ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, onSave: handleSaveAction, ), ); } }