import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/journal_entry.dart'; import '../models/drink.dart'; import '../models/bean.dart'; import '../models/machine.dart'; import '../models/recipe.dart'; import '../providers/app_state.dart'; import 'searchable_selection.dart'; import 'bean_dialog.dart'; import 'machine_dialog.dart'; import 'recipe_dialog.dart'; class JournalEntryDialog extends StatefulWidget { final JournalEntry? journalEntry; // null for add, non-null for edit const JournalEntryDialog({super.key, this.journalEntry}); @override State createState() => _JournalEntryDialogState(); } class _JournalEntryDialogState extends State { final _formKey = GlobalKey(); late final TextEditingController _drinkNameController; late final TextEditingController _drinkDetailsController; late final TextEditingController _drinkNotesController; late final TextEditingController _drinkSizeController; late final TextEditingController _journalNotesController; late final TextEditingController _moodController; late final TextEditingController _weatherController; late DateTime _selectedDate; late double _rating; late bool _isPreferred; Bean? _selectedBean; Machine? _selectedMachine; Recipe? _selectedRecipe; @override void initState() { super.initState(); final journalEntry = widget.journalEntry; final drink = journalEntry?.drink; _drinkNameController = TextEditingController(text: drink?.name ?? ''); _drinkDetailsController = TextEditingController(text: drink?.details ?? ''); _drinkNotesController = TextEditingController(text: drink?.notes ?? ''); _drinkSizeController = TextEditingController(text: drink?.size ?? ''); _journalNotesController = TextEditingController(text: journalEntry?.notes ?? ''); _moodController = TextEditingController(text: journalEntry?.mood ?? ''); _weatherController = TextEditingController(text: journalEntry?.weather ?? ''); _selectedDate = journalEntry?.date ?? DateTime.now(); _rating = drink?.rating ?? 3.0; _isPreferred = drink?.preferred ?? false; _selectedBean = drink?.bean; _selectedMachine = drink?.machine; _selectedRecipe = drink?.recipe; } @override void dispose() { _drinkNameController.dispose(); _drinkDetailsController.dispose(); _drinkNotesController.dispose(); _drinkSizeController.dispose(); _journalNotesController.dispose(); _moodController.dispose(); _weatherController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Dialog( child: Container( width: MediaQuery.of(context).size.width > 600 ? 600 : double.infinity, height: MediaQuery.of(context).size.height * 0.9, padding: const EdgeInsets.all(24), child: Column( children: [ // Header Row( children: [ Icon( Icons.book, color: Theme.of(context).colorScheme.primary, size: 28, ), const SizedBox(width: 12), Expanded( child: Text( widget.journalEntry == null ? 'Add Journal Entry' : 'Edit Journal Entry', style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.w600, ), ), ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ), ], ), const Divider(), // Form Expanded( child: Form( key: _formKey, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDateSection(), const SizedBox(height: 24), _buildDrinkInfoSection(), const SizedBox(height: 24), _buildRatingSection(), const SizedBox(height: 24), _buildReferencesSection(), const SizedBox(height: 24), _buildJournalSection(), ], ), ), ), ), // Actions const Divider(), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), const SizedBox(width: 12), ElevatedButton( onPressed: _saveJournalEntry, child: Text(widget.journalEntry == null ? 'Add Entry' : 'Update Entry'), ), ], ), ], ), ), ); } Widget _buildDateSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Entry Date', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 16), InkWell( onTap: _selectDate, child: InputDecorator( decoration: const InputDecoration( labelText: 'Date', border: OutlineInputBorder(), suffixIcon: Icon(Icons.calendar_today), ), child: Text(_formatDate(_selectedDate)), ), ), ], ); } Widget _buildDrinkInfoSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Drink Information', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 16), TextFormField( controller: _drinkNameController, decoration: const InputDecoration( labelText: 'Drink Name *', hintText: 'e.g., Morning Latte, Afternoon Espresso', border: OutlineInputBorder(), ), validator: (value) { if (value == null || value.trim().isEmpty) { return 'Drink name is required'; } return null; }, ), const SizedBox(height: 16), Row( children: [ Expanded( child: TextFormField( controller: _drinkSizeController, decoration: const InputDecoration( labelText: 'Size', hintText: 'e.g., 8oz, 12oz, Large', border: OutlineInputBorder(), ), ), ), const SizedBox(width: 16), Expanded( child: SwitchListTile( title: const Text('Preferred'), value: _isPreferred, onChanged: (value) { setState(() { _isPreferred = value; }); }, contentPadding: EdgeInsets.zero, ), ), ], ), const SizedBox(height: 16), TextFormField( controller: _drinkDetailsController, decoration: const InputDecoration( labelText: 'Details', hintText: 'Additional details about the drink', border: OutlineInputBorder(), ), maxLines: 2, ), const SizedBox(height: 16), TextFormField( controller: _drinkNotesController, decoration: const InputDecoration( labelText: 'Drink Notes', hintText: 'Notes about the drink preparation or taste', border: OutlineInputBorder(), ), maxLines: 2, ), ], ); } Widget _buildRatingSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Rating', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 16), Row( children: [ const Text('Rating:'), const SizedBox(width: 16), Expanded( child: Slider( value: _rating, min: 1.0, max: 5.0, divisions: 8, label: '${_rating.toStringAsFixed(1)} stars', onChanged: (value) { setState(() { _rating = value; }); }, ), ), const SizedBox(width: 16), Row( children: List.generate(5, (index) { return Icon( index < _rating.floor() ? Icons.star : index < _rating ? Icons.star_half : Icons.star_border, color: Colors.amber, size: 20, ); }), ), ], ), ], ); } Widget _buildReferencesSection() { return Consumer( builder: (context, appState, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'References (Optional)', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 16), // Bean Selection _buildSelectionField( label: 'Bean Used', value: _selectedBean?.name ?? 'No bean selected', onTap: () => _selectBean(context), icon: Icons.coffee_outlined, ), const SizedBox(height: 16), // Machine Selection _buildSelectionField( label: 'Machine Used', value: _selectedMachine != null ? '${_selectedMachine!.manufacturer} ${_selectedMachine!.model}' : 'No machine selected', onTap: () => _selectMachine(context), icon: Icons.coffee_maker, ), const SizedBox(height: 16), // Recipe Selection _buildSelectionField( label: 'Recipe Used', value: _selectedRecipe?.name ?? 'No recipe selected', onTap: () => _selectRecipe(context), icon: Icons.menu_book, ), ], ); }, ); } Widget _buildJournalSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Journal Notes', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 16), TextFormField( controller: _journalNotesController, decoration: const InputDecoration( labelText: 'Journal Notes', hintText: 'Your thoughts, feelings, or observations about this coffee experience...', border: OutlineInputBorder(), alignLabelWithHint: true, ), maxLines: 4, ), const SizedBox(height: 16), Row( children: [ Expanded( child: TextFormField( controller: _moodController, decoration: const InputDecoration( labelText: 'Mood', hintText: 'e.g., Energetic, Relaxed', border: OutlineInputBorder(), ), ), ), const SizedBox(width: 16), Expanded( child: TextFormField( controller: _weatherController, decoration: const InputDecoration( labelText: 'Weather', hintText: 'e.g., Sunny, Rainy', border: OutlineInputBorder(), ), ), ), ], ), ], ); } String _formatDate(DateTime date) { return '${date.day}/${date.month}/${date.year}'; } Future _selectDate() async { final picked = await showDatePicker( context: context, initialDate: _selectedDate, firstDate: DateTime.now().subtract(const Duration(days: 365 * 2)), lastDate: DateTime.now(), ); if (picked != null) { setState(() { _selectedDate = picked; }); } } void _saveJournalEntry() async { if (!_formKey.currentState!.validate()) { return; } try { // Create the drink final drink = Drink( id: widget.journalEntry?.drink.id ?? DateTime.now().millisecondsSinceEpoch.toString(), name: _drinkNameController.text.trim(), details: _drinkDetailsController.text.trim(), notes: _drinkNotesController.text.trim(), preferred: _isPreferred, rating: _rating, size: _drinkSizeController.text.trim(), bean: _selectedBean, machine: _selectedMachine, recipe: _selectedRecipe, dateCreated: _selectedDate, ); // Create the journal entry final journalEntry = JournalEntry( id: widget.journalEntry?.id ?? DateTime.now().millisecondsSinceEpoch.toString(), date: _selectedDate, drink: drink, notes: _journalNotesController.text.trim().isEmpty ? null : _journalNotesController.text.trim(), mood: _moodController.text.trim().isEmpty ? null : _moodController.text.trim(), weather: _weatherController.text.trim().isEmpty ? null : _weatherController.text.trim(), ); final appState = Provider.of(context, listen: false); // Save or update the drink first if (widget.journalEntry == null) { await appState.addDrink(drink); await appState.addJournalEntry(journalEntry); } else { await appState.updateDrink(drink); await appState.updateJournalEntry(journalEntry); } if (mounted) { Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(widget.journalEntry == null ? 'Journal entry added successfully!' : 'Journal entry updated successfully!'), backgroundColor: Theme.of(context).colorScheme.primary, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error saving journal entry: $e'), backgroundColor: Colors.red, ), ); } } } Widget _buildSelectionField({ required String label, required String value, required VoidCallback onTap, required IconData icon, }) { return InkWell( onTap: onTap, child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(4), ), padding: const EdgeInsets.all(12), child: Row( children: [ Icon(icon, size: 20), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), const SizedBox(height: 2), Text( value, style: const TextStyle(fontSize: 16), ), ], ), ), const Icon(Icons.arrow_forward_ios, size: 16), ], ), ), ); } void _selectBean(BuildContext context) { final appState = Provider.of(context, listen: false); Navigator.of(context).push( MaterialPageRoute( builder: (context) => SearchableSelection( items: appState.beans, title: 'Select Bean', searchHint: 'Search beans...', displayText: (bean) => bean.name, onItemSelected: (bean) { setState(() { _selectedBean = bean; }); Navigator.of(context).pop(); }, onAddCustom: () { Navigator.of(context).pop(); showDialog( context: context, builder: (context) => const BeanDialog(), ).then((_) { // Refresh the state after adding a new bean if (mounted) { setState(() {}); } }); }, ), ), ); } void _selectMachine(BuildContext context) { final appState = Provider.of(context, listen: false); Navigator.of(context).push( MaterialPageRoute( builder: (context) => SearchableSelection( items: appState.machines, title: 'Select Machine', searchHint: 'Search machines...', displayText: (machine) => '${machine.manufacturer} ${machine.model}', onItemSelected: (machine) { setState(() { _selectedMachine = machine; }); Navigator.of(context).pop(); }, onAddCustom: () { Navigator.of(context).pop(); showDialog( context: context, builder: (context) => const MachineDialog(), ).then((_) { // Refresh the state after adding a new machine if (mounted) { setState(() {}); } }); }, ), ), ); } void _selectRecipe(BuildContext context) { final appState = Provider.of(context, listen: false); Navigator.of(context).push( MaterialPageRoute( builder: (context) => SearchableSelection( items: appState.recipes, title: 'Select Recipe', searchHint: 'Search recipes...', displayText: (recipe) => recipe.name, onItemSelected: (recipe) { setState(() { _selectedRecipe = recipe; }); Navigator.of(context).pop(); }, onAddCustom: () { Navigator.of(context).pop(); showDialog( context: context, builder: (context) => const RecipeDialog(), ).then((_) { // Refresh the state after adding a new recipe if (mounted) { setState(() {}); } }); }, ), ), ); } }