import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/theme/app_colors.dart'; import '../../shared/models/saved_recipe.dart'; import 'recipe_provider.dart'; class SavedRecipesScreen extends ConsumerWidget { const SavedRecipesScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(savedRecipesProvider); return state.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) => Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.error_outline, size: 48, color: Colors.red), const SizedBox(height: 12), const Text('Не удалось загрузить сохранённые рецепты'), const SizedBox(height: 12), ElevatedButton( onPressed: () => ref.read(savedRecipesProvider.notifier).load(), child: const Text('Повторить'), ), ], ), ), data: (recipes) => recipes.isEmpty ? const _EmptyState() : _SavedList(recipes: recipes), ); } } // --------------------------------------------------------------------------- // List // --------------------------------------------------------------------------- class _SavedList extends StatelessWidget { final List recipes; const _SavedList({required this.recipes}); @override Widget build(BuildContext context) { return ListView.separated( padding: const EdgeInsets.all(16), itemCount: recipes.length, separatorBuilder: (_, __) => const SizedBox(height: 8), itemBuilder: (context, index) => _SavedRecipeItem(recipe: recipes[index]), ); } } // --------------------------------------------------------------------------- // Single item with swipe-to-delete // --------------------------------------------------------------------------- class _SavedRecipeItem extends ConsumerWidget { final SavedRecipe recipe; const _SavedRecipeItem({required this.recipe}); @override Widget build(BuildContext context, WidgetRef ref) { return Dismissible( key: ValueKey(recipe.id), direction: DismissDirection.endToStart, background: _DeleteBackground(), confirmDismiss: (_) => _confirmDelete(context), onDismissed: (_) async { final ok = await ref.read(savedRecipesProvider.notifier).delete(recipe.id); if (!ok && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Не удалось удалить рецепт')), ); } }, child: Card( clipBehavior: Clip.antiAlias, child: InkWell( onTap: () => context.push('/recipe-detail', extra: recipe), child: Row( children: [ // Thumbnail _Thumbnail(imageUrl: recipe.imageUrl), // Info Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( recipe.title, style: Theme.of(context) .textTheme .titleSmall ?.copyWith(fontWeight: FontWeight.bold), maxLines: 2, overflow: TextOverflow.ellipsis, ), if (recipe.nutrition != null) ...[ const SizedBox(height: 4), Text( '≈ ${recipe.nutrition!.calories.round()} ккал · ' '${recipe.nutrition!.proteinG.round()} б · ' '${recipe.nutrition!.fatG.round()} ж · ' '${recipe.nutrition!.carbsG.round()} у', style: const TextStyle( fontSize: 11, color: AppColors.textSecondary), ), ], if (recipe.prepTimeMin != null || recipe.cookTimeMin != null) ...[ const SizedBox(height: 4), Text( _timeLabel(recipe.prepTimeMin, recipe.cookTimeMin), style: const TextStyle( fontSize: 11, color: AppColors.textSecondary), ), ], ], ), ), ), // Delete button IconButton( icon: const Icon(Icons.delete_outline, color: AppColors.textSecondary), onPressed: () async { final confirmed = await _confirmDelete(context); if (confirmed == true && context.mounted) { final ok = await ref .read(savedRecipesProvider.notifier) .delete(recipe.id); if (!ok && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Не удалось удалить рецепт')), ); } } }, ), ], ), ), ), ); } String _timeLabel(int? prep, int? cook) { final total = (prep ?? 0) + (cook ?? 0); return total > 0 ? '$total мин' : ''; } Future _confirmDelete(BuildContext context) { return showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Удалить рецепт?'), content: Text('«${recipe.title}» будет удалён из сохранённых.'), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: const Text('Отмена'), ), TextButton( onPressed: () => Navigator.of(ctx).pop(true), style: TextButton.styleFrom(foregroundColor: Colors.red), child: const Text('Удалить'), ), ], ), ); } } class _Thumbnail extends StatelessWidget { final String? imageUrl; const _Thumbnail({this.imageUrl}); @override Widget build(BuildContext context) { if (imageUrl == null || imageUrl!.isEmpty) { return Container( width: 80, height: 80, color: AppColors.primaryLight.withValues(alpha: 0.3), child: const Icon(Icons.restaurant), ); } return CachedNetworkImage( imageUrl: imageUrl!, width: 80, height: 80, fit: BoxFit.cover, placeholder: (_, __) => Container(width: 80, height: 80, color: Colors.grey[200]), errorWidget: (_, __, ___) => Container( width: 80, height: 80, color: AppColors.primaryLight.withValues(alpha: 0.3), child: const Icon(Icons.restaurant), ), ); } } class _DeleteBackground extends StatelessWidget { @override Widget build(BuildContext context) { return Container( color: AppColors.error, alignment: Alignment.centerRight, padding: const EdgeInsets.only(right: 20), child: const Icon(Icons.delete, color: Colors.white), ); } } // --------------------------------------------------------------------------- // Empty state // --------------------------------------------------------------------------- class _EmptyState extends StatelessWidget { const _EmptyState(); @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.favorite_border, size: 64, color: Colors.grey[400], ), const SizedBox(height: 16), Text( 'Нет сохранённых рецептов', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: AppColors.textSecondary, ), ), const SizedBox(height: 8), Text( 'Сохраняйте рецепты из рекомендаций,\nнажимая на ♡', textAlign: TextAlign.center, style: TextStyle(color: Colors.grey[500]), ), ], ), ), ); } }