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/models/accuracy.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/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/accuracy_detail.dart'; import 'package:diameter/screens/meal/meal_category_detail.dart'; import 'package:diameter/screens/meal/meal_portion_type_detail.dart'; import 'package:diameter/screens/meal/meal_source_detail.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; class MealDetailScreen extends StatefulWidget { static const String routeName = '/meal'; final int id; const MealDetailScreen({Key? key, this.id = 0}) : super(key: key); @override _MealDetailScreenState createState() => _MealDetailScreenState(); } class _MealDetailScreenState extends State { Meal? _meal; bool _isNew = true; bool _isSaving = false; bool _isExpanded = false; final GlobalKey _mealForm = GlobalKey(); final ScrollController _scrollController = ScrollController(); final _valueController = TextEditingController(text: ''); final _carbsRatioController = TextEditingController(text: ''); final _portionSizeController = TextEditingController(text: ''); final _carbsPerPortionController = TextEditingController(text: ''); final _delayedBolusDurationController = TextEditingController(text: ''); final _notesController = TextEditingController(text: ''); double _delayedBolusPercentage = 0; MealSource? _mealSource; MealCategory? _mealCategory; MealPortionType? _mealPortionType; Accuracy? _portionSizeAccuracy; Accuracy? _carbsRatioAccuracy; final _mealSourceController = TextEditingController(text: ''); final _mealCategoryController = TextEditingController(text: ''); final _mealPortionTypeController = TextEditingController(text: ''); final _portionSizeAccuracyController = TextEditingController(text: ''); final _carbsRatioAccuracyController = TextEditingController(text: ''); List _mealCategories = []; List _mealPortionTypes = []; List _mealSources = []; List _portionSizeAccuracies = []; List _carbsRatioAccuracies = []; @override void initState() { super.initState(); reload(); _portionSizeAccuracies = Accuracy.getAllForPortionSize(); _carbsRatioAccuracies = Accuracy.getAllForCarbsRatio(); _mealCategories = MealCategory.getAll(); _mealPortionTypes = MealPortionType.getAll(); _mealSources = MealSource.getAll(); if (_meal != null) { _valueController.text = _meal!.value; _carbsRatioController.text = (_meal!.carbsRatio ?? '').toString(); _portionSizeController.text = (_meal!.portionSize ?? '').toString(); _carbsPerPortionController.text = (_meal!.carbsPerPortion ?? '').toString(); _delayedBolusPercentage = _meal!.delayedBolusPercentage ?? 0; _delayedBolusDurationController.text = (_meal!.delayedBolusDuration ?? '').toString(); _notesController.text = _meal!.notes ?? ''; _mealSource = _meal!.mealSource.target; _mealSourceController.text = (_mealSource ?? '').toString(); _mealCategory = _meal!.mealCategory.target; _mealCategoryController.text = (_mealCategory ?? '').toString(); _mealPortionType = _meal!.mealPortionType.target; _mealPortionTypeController.text = (_mealPortionType ?? '').toString(); _portionSizeAccuracy = _meal!.portionSizeAccuracy.target; _portionSizeAccuracyController.text = (_portionSizeAccuracy ?? '').toString(); _carbsRatioAccuracy = _meal!.carbsRatioAccuracy.target; _carbsRatioAccuracyController.text = (_carbsRatioAccuracy ?? '').toString(); } } void reload({String? message}) { if (widget.id != 0) { setState(() { _meal = Meal.get(widget.id); }); } _isNew = _meal == null; setState(() { if (message != null) { var snackBar = SnackBar( content: Text(message), duration: const Duration(seconds: 2), ); ScaffoldMessenger.of(context) ..removeCurrentSnackBar() ..showSnackBar(snackBar); } }); } void updateCarbsRatioAccuracy(Accuracy? value) { setState(() { _carbsRatioAccuracy = value; _carbsRatioAccuracyController.text = (_carbsRatioAccuracy ?? '').toString(); }); } void updatePortionSizeAccuracy(Accuracy? value) { setState(() { _portionSizeAccuracy = value; _portionSizeAccuracyController.text = (_portionSizeAccuracy ?? '').toString(); }); } void updateMealCategory(MealCategory? value) { setState(() { _mealCategory = value; _mealCategoryController.text = (_mealCategory ?? '').toString(); }); } void updateMealPortionType(MealPortionType? value) { setState(() { _mealPortionType = value; _mealPortionTypeController.text = (_mealPortionType ?? '').toString(); }); } void handleSaveAction() async { setState(() { _isSaving = true; }); if (_mealForm.currentState!.validate()) { Meal meal = Meal( id: widget.id, value: _valueController.text, carbsRatio: double.tryParse(_carbsRatioController.text), portionSize: double.tryParse(_portionSizeController.text), carbsPerPortion: double.tryParse(_carbsPerPortionController.text), delayedBolusDuration: int.tryParse(_delayedBolusDurationController.text), delayedBolusPercentage: _delayedBolusPercentage, notes: _notesController.text, ); meal.mealSource.target = _mealSource; meal.mealCategory.target = _mealCategory; meal.mealPortionType.target = _mealPortionType; meal.portionSizeAccuracy.target = _portionSizeAccuracy; meal.carbsRatioAccuracy.target = _carbsRatioAccuracy; Meal.put(meal); Navigator.pop(context, ['${_isNew ? 'New' : ''} Meal Saved', meal]); } setState(() { _isSaving = false; }); } void handleCancelAction() { if (Settings.get().showConfirmationDialogOnCancel && ((_isNew && (_valueController.text != '' || _mealSource != null || _mealCategory != null || _mealPortionType != 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 || _delayedBolusPercentage != 0 || _notesController.text != '')) || (!_isNew && (_valueController.text != _meal!.value || _mealSource != _meal!.mealSource.target || _mealCategory != _meal!.mealCategory.target || _mealPortionType != _meal!.mealPortionType.target || double.tryParse(_carbsRatioController.text) != _meal!.carbsRatio || double.tryParse(_portionSizeController.text) != _meal!.portionSize || double.tryParse(_carbsPerPortionController.text) != _meal!.carbsPerPortion || _carbsRatioAccuracy != _meal!.carbsRatioAccuracy.target || _portionSizeAccuracy != _meal!.portionSizeAccuracy.target || int.tryParse(_delayedBolusDurationController.text) != _meal!.delayedBolusDuration || _delayedBolusPercentage != _meal!.delayedBolusPercentage || _notesController.text != (_meal!.notes ?? ''))))) { Dialogs.showCancelConfirmationDialog( context: context, isNew: _isNew, onSave: handleSaveAction, ); } else { Navigator.pop(context); } } Future onSelectMealSource(MealSource? mealSource) async { setState(() { _mealSource = mealSource; _mealSourceController.text = (_mealSource ?? '').toString(); }); if (mealSource != null) { if (mealSource.defaultCarbsRatioAccuracy.hasValue) { updateCarbsRatioAccuracy(mealSource.defaultCarbsRatioAccuracy.target); } if (mealSource.defaultPortionSizeAccuracy.hasValue) { updatePortionSizeAccuracy(mealSource.defaultPortionSizeAccuracy.target); } if (mealSource.defaultMealCategory.hasValue) { updateMealCategory(mealSource.defaultMealCategory.target); } if (mealSource.defaultMealPortionType.hasValue) { updateMealPortionType(mealSource.defaultMealPortionType.target); } } } void calculateThirdMeasurementOfPortionCarbsRelation( {PortionCarbsParameter? changedParameter}) { double? carbsRatio; double? portionSize; double? carbsPerPortion; if (_carbsRatioController.text != '') { carbsRatio = double.tryParse(_carbsRatioController.text); } if (_portionSizeController.text != '') { portionSize = double.tryParse(_portionSizeController.text); } if (_carbsRatioController.text != '') { carbsPerPortion = double.tryParse(_carbsPerPortionController.text); } if (carbsRatio != null && portionSize != null && carbsPerPortion == null) { setState(() { _carbsPerPortionController.text = Utils.calculateCarbsPerPortion(carbsRatio!, portionSize!) .toString(); }); } if (carbsRatio == null && portionSize != null && carbsPerPortion != null) { setState(() { _carbsRatioController.text = Utils.calculateCarbsRatio(carbsPerPortion!, portionSize!) .toString(); }); } if (carbsRatio != null && portionSize == null && carbsPerPortion != null) { setState(() { _portionSizeController.text = Utils.calculatePortionSize(carbsRatio!, carbsPerPortion!) .toString(); }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_isNew ? 'New Meal' : _meal!.value), ), drawer: const Navigation(currentLocation: MealDetailScreen.routeName), body: Scrollbar( controller: _scrollController, child: SingleChildScrollView( controller: _scrollController, child: Column( children: [ FormWrapper( formState: _mealForm, fields: [ TextFormField( controller: _valueController, decoration: const InputDecoration( labelText: 'Name', ), validator: (value) { if (value!.trim().isEmpty) { return 'Empty name'; } return null; }, ), Row( children: [ Expanded( child: AutoCompleteDropdownButton( controller: _mealSourceController, selectedItem: _mealSource, label: 'Meal Source', items: _mealSources, onChanged: onSelectMealSource, ), ), IconButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => _mealSource == null ? const MealSourceDetailScreen() : MealSourceDetailScreen(id: _mealSource!.id), ), ).then((result) { onSelectMealSource(result?[1]); reload(message: result?[0]); }); }, icon: Icon(_mealSource == null ? Icons.add : Icons.edit), ), ], ), Row( children: [ Expanded( child: AutoCompleteDropdownButton( controller: _mealPortionTypeController, selectedItem: _mealPortionType, label: 'Meal Portion Type', items: _mealPortionTypes, onChanged: updateMealPortionType, ), ), IconButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => _mealPortionType == null ? const MealPortionTypeDetailScreen() : MealPortionTypeDetailScreen( id: _mealPortionType!.id), ), ).then((result) { updateMealPortionType(result?[1]); reload(message: result?[0]); }); }, icon: Icon( _mealPortionType == null ? Icons.add : Icons.edit), ), ], ), Row( children: [ Expanded( child: TextFormField( decoration: const InputDecoration( labelText: 'Carbs ratio', suffixText: '%', ), controller: _carbsRatioController, keyboardType: const TextInputType.numberWithOptions( decimal: true), onChanged: (_) async { await Future.delayed(const Duration(seconds: 1)); calculateThirdMeasurementOfPortionCarbsRelation(); }, ), ), const SizedBox(width: 10), Expanded( child: TextFormField( decoration: InputDecoration( labelText: 'Portion size', suffixText: Settings.nutritionMeasurementSuffix, ), controller: _portionSizeController, keyboardType: const TextInputType.numberWithOptions( decimal: true), onChanged: (_) async { await Future.delayed(const Duration(seconds: 1)); calculateThirdMeasurementOfPortionCarbsRelation(); }, ), ), const SizedBox(width: 10), Expanded( child: TextFormField( decoration: InputDecoration( labelText: 'Carbs per portion', suffixText: Settings.nutritionMeasurementSuffix, ), controller: _carbsPerPortionController, keyboardType: const TextInputType.numberWithOptions( decimal: true), onChanged: (_) async { await Future.delayed(const Duration(seconds: 1)); calculateThirdMeasurementOfPortionCarbsRelation(); }, ), ), ], ), TextFormField( controller: _notesController, decoration: const InputDecoration( labelText: 'Notes', ), keyboardType: TextInputType.multiline, minLines: 2, maxLines: 5, ), const Divider(), Padding( padding: const EdgeInsets.only(bottom: 10.0), child: Row( children: [ Text( 'BOLUS DELAY', style: Theme.of(context).textTheme.subtitle2, ), const Spacer(), ], ), ), // ignore: todo // TODO: display according to time format Row( children: [ Expanded( child: TextFormField( decoration: const InputDecoration( labelText: 'Duration', suffixText: ' min', ), controller: _delayedBolusDurationController, onChanged: (value) => setState(() {}), keyboardType: const TextInputType.numberWithOptions(), ), ), Expanded( child: Slider( label: '${_delayedBolusPercentage.floor().toString()}%', divisions: 100, value: _delayedBolusPercentage, min: 0, max: 100, onChanged: _delayedBolusDurationController.text != '' ? (value) { setState(() { _delayedBolusPercentage = value; }); } : null ), ), const Text('%', textScaleFactor: 1.5), ], ), const Divider(), GestureDetector( onTap: () => setState(() { _isExpanded = !_isExpanded; }), child: Row( mainAxisSize: MainAxisSize.max, children: [ Text( 'ADDITIONAL FIELDS', style: Theme.of(context).textTheme.subtitle2, ), const Spacer(), Icon(_isExpanded ? Icons.expand_less : Icons.expand_more), ], ), ), Column( children: _isExpanded ? [ Padding( padding: const EdgeInsets.symmetric(vertical: 5.0), child: Row( children: [ Expanded( child: AutoCompleteDropdownButton< MealCategory>( controller: _mealCategoryController, selectedItem: _mealCategory, label: 'Meal Category', items: _mealCategories, onChanged: updateMealCategory, ), ), IconButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => _mealCategory == null ? const MealCategoryDetailScreen() : MealCategoryDetailScreen( id: _mealCategory!.id), ), ).then((result) { updateMealCategory(result?[1]); reload(message: result?[0]); }); }, icon: Icon(_mealCategory == null ? Icons.add : Icons.edit), ), ], ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 5.0), child: Row( children: [ Expanded( child: AutoCompleteDropdownButton( controller: _portionSizeAccuracyController, selectedItem: _portionSizeAccuracy, label: 'Portion Size Accuracy', items: _portionSizeAccuracies, onChanged: updatePortionSizeAccuracy, ), ), IconButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => _portionSizeAccuracy == null ? const AccuracyDetailScreen() : AccuracyDetailScreen( id: _portionSizeAccuracy! .id), ), ).then((result) { updatePortionSizeAccuracy(result?[1]); reload(message: result?[0]); }); }, icon: Icon(_portionSizeAccuracy == null ? Icons.add : Icons.edit), ), ], ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 5.0), child: Row( children: [ Expanded( child: AutoCompleteDropdownButton( controller: _carbsRatioAccuracyController, selectedItem: _carbsRatioAccuracy, label: 'Carbs Ratio Accuracy', items: _carbsRatioAccuracies, onChanged: updateCarbsRatioAccuracy, ), ), IconButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => _carbsRatioAccuracy == null ? const AccuracyDetailScreen() : AccuracyDetailScreen( id: _carbsRatioAccuracy! .id), ), ).then((result) { updateCarbsRatioAccuracy(result?[1]); reload(message: result?[0]); }); }, icon: Icon(_carbsRatioAccuracy == null ? Icons.add : Icons.edit), ), ], ), ), ] : [], ), ], ), ], ), ), ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, onAction: _isSaving ? null : handleSaveAction, ), ); } }