import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../nutrition/domain/entities/meal_type.dart'; import '../../domain/entities/meal_plan_slot.dart'; import '../../domain/entities/recipe.dart'; import '../controllers/meal_plan_calendar_controller.dart'; /// Opens the quick-assign modal for a specific day/meal slot. /// /// Shows the saved recipes to pick from, plus a free-text override for /// meals without a saved recipe. Resolves to `true` when a slot was /// written, otherwise `null`/`false`. Future showAssignSlotDialog( BuildContext context, { required DateTime day, required MealType mealType, required List savedRecipes, MealPlanSlot? existingSlot, }) { return showDialog( context: context, builder: (context) => AssignSlotDialog( day: day, mealType: mealType, savedRecipes: savedRecipes, existingSlot: existingSlot, ), ); } /// Modal used by the weekly calendar's "+" affordance: assign a saved /// recipe (via dropdown) or type a quick custom description for the slot. class AssignSlotDialog extends ConsumerStatefulWidget { const AssignSlotDialog({ super.key, required this.day, required this.mealType, required this.savedRecipes, this.existingSlot, }); final DateTime day; final MealType mealType; final List savedRecipes; final MealPlanSlot? existingSlot; @override ConsumerState createState() => _AssignSlotDialogState(); } class _AssignSlotDialogState extends ConsumerState { final GlobalKey _formKey = GlobalKey(); late final TextEditingController _descriptionController; Recipe? _selectedRecipe; bool _isSaving = false; @override void initState() { super.initState(); _descriptionController = TextEditingController( text: widget.existingSlot?.customDescription ?? '', ); // Pre-select the currently assigned recipe, if any. final recipeId = widget.existingSlot?.recipeId; if (recipeId != null) { for (final recipe in widget.savedRecipes) { if (recipe.id == recipeId) { _selectedRecipe = recipe; break; } } } } @override Widget build(BuildContext context) { final dayLabel = MaterialLocalizations.of(context).formatCompactDate(widget.day); return AlertDialog( title: Text('Assign ${widget.mealType.displayName} — $dayLabel'), content: SizedBox( width: 420, child: Form( key: _formKey, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ DropdownButtonFormField( initialValue: _selectedRecipe, decoration: const InputDecoration( labelText: 'Saved recipe', border: OutlineInputBorder(), ), items: [ for (final recipe in widget.savedRecipes) DropdownMenuItem( value: recipe, child: Text( recipe.title, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], onChanged: (recipe) => setState(() => _selectedRecipe = recipe), ), const SizedBox(height: 16), TextFormField( controller: _descriptionController, decoration: const InputDecoration( labelText: 'Description (optional)', hintText: 'e.g. Leftover chicken & greens', border: OutlineInputBorder(), ), textCapitalization: TextCapitalization.sentences, validator: _validate, ), ], ), ), ), ), actions: [ TextButton( onPressed: _isSaving ? null : () => Navigator.of(context).pop(), child: const Text('Cancel'), ), FilledButton.icon( onPressed: _isSaving ? null : _assign, icon: _isSaving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.event_available_outlined), label: const Text('Assign'), ), ], ); } String? _validate(String? value) { // A description is only required when no saved recipe is selected. if (_selectedRecipe == null && (value == null || value.trim().isEmpty)) { return 'Select a recipe or enter a description'; } return null; } Future _assign() async { if (!(_formKey.currentState?.validate() ?? false)) return; setState(() => _isSaving = true); final controller = ref.read(mealPlanCalendarControllerProvider.notifier); final description = _descriptionController.text.trim(); final bool saved; final recipe = _selectedRecipe; if (recipe != null) { saved = await controller.assignRecipe( day: widget.day, mealType: widget.mealType, recipeId: recipe.id, note: description.isEmpty ? null : description, ); } else { saved = await controller.saveCustomEntry( day: widget.day, mealType: widget.mealType, description: description, ); } if (!mounted) return; Navigator.of(context).pop(saved); } @override void dispose() { _descriptionController.dispose(); super.dispose(); } }