import 'package:flutter/material.dart'; import '../../domain/entities/recipe.dart'; /// Horizontally scrolling tray of saved recipe cards. /// /// Each card is draggable onto the weekly calendar's meal slots and /// carries a "+" affordance that opens the quick-assign modal. class SavedRecipesTray extends StatelessWidget { const SavedRecipesTray({ super.key, required this.recipes, this.onAssignPressed, }); /// Saved recipes, newest first. final List recipes; /// Called when the card's quick-assign button (or the card itself) is /// tapped. final void Function(Recipe recipe)? onAssignPressed; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Text( 'Saved Recipes', style: theme.textTheme.titleSmall ?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(width: 8), Text( '${recipes.length}', style: theme.textTheme.labelMedium ?.copyWith(color: theme.colorScheme.primary), ), ], ), ), const SizedBox(height: 8), SizedBox( height: 148, child: recipes.isEmpty ? Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( 'Import a recipe above — it will appear here, ready to ' 'drag onto the weekly plan.', style: theme.textTheme.bodySmall?.copyWith( fontStyle: FontStyle.italic, ), ), ), ) : ListView.separated( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: recipes.length, separatorBuilder: (context, index) => const SizedBox(width: 12), itemBuilder: (context, index) { final recipe = recipes[index]; return _RecipeCard( recipe: recipe, onAssignPressed: onAssignPressed == null ? null : () => onAssignPressed!(recipe), ); }, ), ), ], ); } } /// Draggable card showing a recipe thumbnail, title, and protein badge. class _RecipeCard extends StatelessWidget { const _RecipeCard({required this.recipe, this.onAssignPressed}); final Recipe recipe; final VoidCallback? onAssignPressed; @override Widget build(BuildContext context) { final theme = Theme.of(context); final protein = recipe.macrosPerServing.proteinGrams; final card = SizedBox( width: 180, child: Card( clipBehavior: Clip.antiAlias, child: InkWell( onTap: onAssignPressed, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: recipe.imageUrl == null ? _ThumbnailPlaceholder(theme: theme) : Image.network( recipe.imageUrl!, width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => _ThumbnailPlaceholder(theme: theme), ), ), Padding( padding: const EdgeInsets.all(8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( recipe.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodyMedium ?.copyWith(fontWeight: FontWeight.w600), ), const SizedBox(height: 4), Row( children: [ _ProteinBadge(proteinGrams: protein), const Spacer(), if (onAssignPressed != null) Icon( Icons.add_circle_outline, size: 20, color: theme.colorScheme.primary, ), ], ), ], ), ), ], ), ), ), ); return Draggable( data: recipe, feedback: Material( elevation: 6, borderRadius: BorderRadius.circular(12), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 200), child: Padding( padding: const EdgeInsets.all(12), child: Text( recipe.title, maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ), ), childWhenDragging: Opacity(opacity: 0.4, child: card), child: card, ); } } /// Fallback tile when a recipe has no (loadable) thumbnail. class _ThumbnailPlaceholder extends StatelessWidget { const _ThumbnailPlaceholder({required this.theme}); final ThemeData theme; @override Widget build(BuildContext context) { return Container( color: theme.colorScheme.surfaceContainerHighest, child: Center( child: Icon( Icons.restaurant_menu, size: 28, color: theme.colorScheme.outline, ), ), ); } } /// Compact green badge showing the protein contribution of a recipe. class _ProteinBadge extends StatelessWidget { const _ProteinBadge({required this.proteinGrams}); final double proteinGrams; @override Widget build(BuildContext context) { final rounded = proteinGrams.roundToDouble(); final label = rounded == proteinGrams ? '${rounded.toStringAsFixed(0)}g P' : '${proteinGrams.toStringAsFixed(1)}g P'; return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: Colors.green.shade100, borderRadius: BorderRadius.circular(8), ), child: Text( label, style: TextStyle( fontSize: 11, fontWeight: FontWeight.bold, color: Colors.green.shade800, ), ), ); } }