import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/constants/app_theme.dart'; import '../../core/models/recipe.dart'; import '../../core/providers.dart'; import '../cook_mode/cook_mode_screen.dart'; import '../scanner/scanner_screen.dart'; class RecipesScreen extends ConsumerStatefulWidget { const RecipesScreen({super.key}); @override ConsumerState createState() => _RecipesScreenState(); } class _RecipesScreenState extends ConsumerState { final TextEditingController _searchController = TextEditingController(); String _searchQuery = ''; @override void dispose() { _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final recipesAsync = ref.watch(recipeListProvider); final user = ref.watch(authProvider).user; return Scaffold( appBar: AppBar( title: Row( children: [ const Icon(Icons.fork_right_rounded, color: AppTheme.primary, size: 24), const SizedBox(width: 8), const Text('ForkFolio'), ], ), actions: [ IconButton( tooltip: 'Camera Scanner', icon: const Icon(Icons.camera_alt_outlined), onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const ScannerScreen())); }, ), IconButton( tooltip: 'Logout', icon: const Icon(Icons.logout_rounded), onPressed: () => ref.read(authProvider.notifier).logout(), ), ], ), body: RefreshIndicator( color: AppTheme.primary, onRefresh: () => ref.read(recipeListProvider.notifier).loadRecipes(), child: Column( children: [ // Search Bar Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: TextField( controller: _searchController, onChanged: (val) => setState(() => _searchQuery = val.trim().toLowerCase()), decoration: InputDecoration( hintText: 'Search recipes, ingredients...', prefixIcon: const Icon(Icons.search_rounded, size: 20), suffixIcon: _searchQuery.isNotEmpty ? IconButton( icon: const Icon(Icons.clear_rounded, size: 18), onPressed: () { _searchController.clear(); setState(() => _searchQuery = ''); }, ) : null, ), ), ), // Recipe List Expanded( child: recipesAsync.when( loading: () => const Center( child: CircularProgressIndicator(color: AppTheme.primary), ), error: (err, _) => Center( child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.cloud_off_rounded, color: AppTheme.textMuted, size: 48), const SizedBox(height: 12), Text( 'Could not load recipes: $err', textAlign: TextAlign.center, style: const TextStyle(color: AppTheme.textMuted), ), const SizedBox(height: 16), ElevatedButton.icon( onPressed: () => ref.read(recipeListProvider.notifier).loadRecipes(), icon: const Icon(Icons.refresh_rounded), label: const Text('Retry'), ), ], ), ), ), data: (recipes) { final filtered = recipes.where((r) { if (_searchQuery.isEmpty) return true; final matchesTitle = r.title.toLowerCase().contains(_searchQuery); final matchesIngredient = r.ingredients.any((i) => i.toLowerCase().contains(_searchQuery)); return matchesTitle || matchesIngredient; }).toList(); if (filtered.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.menu_book_rounded, color: AppTheme.textDisabled.withOpacity(0.5), size: 64), const SizedBox(height: 14), Text( _searchQuery.isEmpty ? 'Your Cookbook is Empty' : 'No recipes match "$_searchQuery"', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: AppTheme.textMuted), ), const SizedBox(height: 6), const Text( 'Share a link from Instagram or tap the camera icon to scan a recipe.', style: TextStyle(fontSize: 12, color: AppTheme.textDisabled), ), ], ), ); } return ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), itemCount: filtered.length, separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (ctx, index) { final recipe = filtered[index]; return _buildRecipeCard(recipe); }, ); }, ), ), ], ), ), floatingActionButton: FloatingActionButton.extended( backgroundColor: AppTheme.primary, onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const ScannerScreen())); }, icon: const Icon(Icons.camera_enhance_rounded, color: Colors.white), label: const Text('Scan Recipe', style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700)), ), ); } Widget _buildRecipeCard(Recipe recipe) { return Card( clipBehavior: Clip.antiAlias, child: InkWell( onTap: () => _openRecipeDetail(recipe), child: Row( children: [ // Thumbnail Container( width: 100, height: 100, color: AppTheme.surfaceElevated, child: recipe.image != null && recipe.image!.isNotEmpty ? (recipe.image!.startsWith('data:image') ? Image.memory(base64Decode(recipe.image!.split(',').last), fit: BoxFit.cover) : Image.network( recipe.image!, fit: BoxFit.cover, errorBuilder: (_, __, ___) => const Icon(Icons.restaurant_rounded, color: AppTheme.textDisabled), )) : const Icon(Icons.restaurant_rounded, color: AppTheme.textDisabled, size: 32), ), // Info Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( recipe.title, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700, color: AppTheme.textMain), ), const SizedBox(height: 8), Row( children: [ if (recipe.calculatedTotalTime.isNotEmpty) ...[ const Icon(Icons.schedule_rounded, size: 14, color: AppTheme.primary), const SizedBox(width: 4), Text(recipe.calculatedTotalTime, style: const TextStyle(fontSize: 12, color: AppTheme.textMuted)), const SizedBox(width: 12), ], if (recipe.servings != null && recipe.servings!.isNotEmpty) ...[ const Icon(Icons.people_outline_rounded, size: 14, color: AppTheme.secondary), const SizedBox(width: 4), Text(recipe.servings!, style: const TextStyle(fontSize: 12, color: AppTheme.textMuted)), ], ], ), ], ), ), ), // Action IconButton( icon: const Icon(Icons.outdoor_grill_rounded, color: AppTheme.primary), tooltip: 'Start Cook Mode', onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (_) => CookModeScreen(recipe: recipe))); }, ), ], ), ), ); } void _openRecipeDetail(Recipe recipe) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (ctx) => DraggableScrollableSheet( initialChildSize: 0.75, maxChildSize: 0.95, minChildSize: 0.5, builder: (_, scrollController) => Container( decoration: const BoxDecoration( color: AppTheme.surface, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: ListView( controller: scrollController, children: [ Center( child: Container( width: 44, height: 4, decoration: BoxDecoration(color: AppTheme.border, borderRadius: BorderRadius.circular(2)), ), ), const SizedBox(height: 16), Text(recipe.title, style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w800)), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: [ if (recipe.servings != null && recipe.servings!.isNotEmpty) _buildMetaChip(Icons.people_outline_rounded, '${recipe.servings} Servings', AppTheme.secondary), if (recipe.prepTime != null && recipe.prepTime!.isNotEmpty) _buildMetaChip(Icons.schedule_rounded, 'Prep: ${recipe.prepTime}', AppTheme.primary), if (recipe.cookTime != null && recipe.cookTime!.isNotEmpty) _buildMetaChip(Icons.local_fire_department_rounded, 'Cook: ${recipe.cookTime}', Colors.amber), if (recipe.calculatedTotalTime.isNotEmpty) _buildMetaChip(Icons.timer_rounded, 'Total: ${recipe.calculatedTotalTime}', Colors.tealAccent), ], ), const SizedBox(height: 16), SizedBox( width: double.infinity, height: 48, child: ElevatedButton.icon( onPressed: () { Navigator.pop(ctx); Navigator.push(context, MaterialPageRoute(builder: (_) => CookModeScreen(recipe: recipe))); }, icon: const Icon(Icons.outdoor_grill_rounded), label: const Text('Start Cook Mode'), ), ), const SizedBox(height: 24), const Text('Ingredients', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: AppTheme.primary)), const SizedBox(height: 8), ...recipe.ingredients.map((ing) => Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('• ', style: TextStyle(color: AppTheme.primary, fontWeight: FontWeight.w800)), Expanded(child: Text(ing, style: const TextStyle(fontSize: 14))), ], ), )), const SizedBox(height: 24), const Text('Instructions', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: AppTheme.primary)), const SizedBox(height: 8), ...recipe.instructions.asMap().entries.map((entry) => Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('${entry.key + 1}. ', style: const TextStyle(color: AppTheme.primary, fontWeight: FontWeight.w700)), Expanded(child: Text(entry.value, style: const TextStyle(fontSize: 14, height: 1.4))), ], ), )), ], ), ), ), ); } Widget _buildMetaChip(IconData icon, String label, Color color) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: color.withOpacity(0.12), borderRadius: BorderRadius.circular(8), border: Border.all(color: color.withOpacity(0.3)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 14, color: color), const SizedBox(width: 5), Text( label, style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: color), ), ], ), ); } }