import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../domain/entities/meal_type.dart'; import '../controllers/nutrition_controller.dart'; /// Opens the interactive food logging dialog. /// /// Resolves to `true` when the meal was persisted, `false` when saving /// failed, and `null` when the user dismissed the dialog. Future showLogFoodDialog( BuildContext context, { MealType initialMealType = MealType.breakfast, }) { return showDialog( context: context, builder: (context) => LogFoodDialog(initialMealType: initialMealType), ); } /// Form for logging a meal: slot, description, protein, calories, and /// optional carbohydrate/fat macros. class LogFoodDialog extends ConsumerStatefulWidget { const LogFoodDialog({ super.key, this.initialMealType = MealType.breakfast, }); /// Meal slot pre-selected in the dropdown. final MealType initialMealType; @override ConsumerState createState() => _LogFoodDialogState(); } class _LogFoodDialogState extends ConsumerState { late MealType _mealType = widget.initialMealType; late final TextEditingController _descriptionController = TextEditingController(); late final TextEditingController _proteinController = TextEditingController(); late final TextEditingController _caloriesController = TextEditingController(); late final TextEditingController _carbsController = TextEditingController(); late final TextEditingController _fatController = TextEditingController(); final GlobalKey _formKey = GlobalKey(); bool _isSaving = false; @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Log Food'), content: SizedBox( width: 440, child: Form( key: _formKey, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ DropdownButtonFormField( initialValue: _mealType, decoration: const InputDecoration( labelText: 'Meal', border: OutlineInputBorder(), ), items: [ for (final type in MealType.values) DropdownMenuItem( value: type, child: Text(type.displayName), ), ], onChanged: (type) { if (type == null) return; setState(() => _mealType = type); }, ), const SizedBox(height: 16), TextFormField( controller: _descriptionController, decoration: const InputDecoration( labelText: 'Description', hintText: 'e.g. Greek yogurt with berries', border: OutlineInputBorder(), ), textCapitalization: TextCapitalization.sentences, validator: (value) => (value == null || value.trim().isEmpty) ? 'Enter a description' : null, ), const SizedBox(height: 16), Row( children: [ Expanded( child: TextFormField( controller: _proteinController, decoration: const InputDecoration( labelText: 'Protein (g)', border: OutlineInputBorder(), ), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), validator: _positiveDecimalValidator('protein'), ), ), const SizedBox(width: 12), Expanded( child: TextFormField( controller: _caloriesController, decoration: const InputDecoration( labelText: 'Calories', border: OutlineInputBorder(), ), keyboardType: TextInputType.number, validator: _nonNegativeIntValidator('calories'), ), ), ], ), const SizedBox(height: 16), Row( children: [ Expanded( child: TextFormField( controller: _carbsController, decoration: const InputDecoration( labelText: 'Carbs (g, optional)', border: OutlineInputBorder(), ), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), validator: _optionalDecimalValidator('carbs'), ), ), const SizedBox(width: 12), Expanded( child: TextFormField( controller: _fatController, decoration: const InputDecoration( labelText: 'Fat (g, optional)', border: OutlineInputBorder(), ), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), validator: _optionalDecimalValidator('fat'), ), ), ], ), ], ), ), ), ), actions: [ TextButton( onPressed: _isSaving ? null : () => Navigator.of(context).pop(), child: const Text('Cancel'), ), FilledButton.icon( onPressed: _isSaving ? null : _save, icon: _isSaving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.restaurant_menu), label: const Text('Log Food'), ), ], ); } String? Function(String?) _positiveDecimalValidator(String field) => (value) { final parsed = double.tryParse((value ?? '').trim()); if (parsed == null || parsed < 0) return 'Enter a valid $field'; return null; }; String? Function(String?) _nonNegativeIntValidator(String field) => (value) { final parsed = int.tryParse((value ?? '').trim()); if (parsed == null || parsed < 0) return 'Enter a valid $field'; return null; }; String? Function(String?) _optionalDecimalValidator(String field) => (value) { final raw = (value ?? '').trim(); if (raw.isEmpty) return null; final parsed = double.tryParse(raw); if (parsed == null || parsed < 0) return 'Enter a valid $field'; return null; }; Future _save() async { if (!(_formKey.currentState?.validate() ?? false)) return; setState(() => _isSaving = true); final saved = await ref.read(nutritionControllerProvider.notifier).logFood( mealType: _mealType, description: _descriptionController.text, proteinGrams: double.parse(_proteinController.text.trim()), calories: int.parse(_caloriesController.text.trim()), carbsGrams: double.tryParse(_carbsController.text.trim()), fatGrams: double.tryParse(_fatController.text.trim()), ); if (!mounted) return; Navigator.of(context).pop(saved); } @override void dispose() { _descriptionController.dispose(); _proteinController.dispose(); _caloriesController.dispose(); _carbsController.dispose(); _fatController.dispose(); super.dispose(); } }