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 '../../shared/models/home_summary.dart'; import 'home_provider.dart'; class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(homeProvider); return Scaffold( body: state.when( loading: () => const Center(child: CircularProgressIndicator()), error: (_, __) => Center( child: FilledButton( onPressed: () => ref.read(homeProvider.notifier).load(), child: const Text('Повторить'), ), ), data: (summary) => RefreshIndicator( onRefresh: () => ref.read(homeProvider.notifier).load(), child: CustomScrollView( slivers: [ _AppBar(summary: summary), SliverPadding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 100), sliver: SliverList( delegate: SliverChildListDelegate([ const SizedBox(height: 16), _CaloriesCard(today: summary.today), const SizedBox(height: 16), _TodayMealsCard(plan: summary.today.plan), if (summary.expiringSoon.isNotEmpty) ...[ const SizedBox(height: 16), _ExpiringBanner(items: summary.expiringSoon), ], const SizedBox(height: 16), _QuickActionsRow(date: summary.today.date), if (summary.recommendations.isNotEmpty) ...[ const SizedBox(height: 20), _SectionTitle('Рекомендуем приготовить'), const SizedBox(height: 12), _RecommendationsRow(recipes: summary.recommendations), ], ]), ), ), ], ), ), ), ); } } // ── App bar ─────────────────────────────────────────────────── class _AppBar extends StatelessWidget { final HomeSummary summary; const _AppBar({required this.summary}); String get _greeting { final hour = DateTime.now().hour; if (hour < 12) return 'Доброе утро'; if (hour < 18) return 'Добрый день'; return 'Добрый вечер'; } String get _dateLabel { final now = DateTime.now(); const months = [ 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря', ]; return '${now.day} ${months[now.month - 1]}'; } @override Widget build(BuildContext context) { final theme = Theme.of(context); return SliverAppBar( pinned: false, floating: true, title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(_greeting, style: theme.textTheme.titleMedium), Text( _dateLabel, style: theme.textTheme.bodySmall ?.copyWith(color: theme.colorScheme.onSurfaceVariant), ), ], ), ); } } // ── Calories card ───────────────────────────────────────────── class _CaloriesCard extends StatelessWidget { final TodaySummary today; const _CaloriesCard({required this.today}); @override Widget build(BuildContext context) { final theme = Theme.of(context); if (today.dailyGoal == 0) return const SizedBox.shrink(); final logged = today.loggedCalories.toInt(); final goal = today.dailyGoal; final remaining = today.remainingCalories.toInt(); final progress = today.progress; return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Калории сегодня', style: theme.textTheme.titleSmall), const SizedBox(height: 12), Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '$logged ккал', style: theme.textTheme.headlineSmall ?.copyWith(fontWeight: FontWeight.bold), ), Text( 'из $goal ккал', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), ], ), ), Text( 'осталось $remaining', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), ], ), const SizedBox(height: 10), ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: progress, minHeight: 8, backgroundColor: theme.colorScheme.surfaceContainerHighest, ), ), ], ), ), ); } } // ── Today meals card ────────────────────────────────────────── class _TodayMealsCard extends StatelessWidget { final List plan; const _TodayMealsCard({required this.plan}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(bottom: 8), child: Text('Приёмы пищи сегодня', style: theme.textTheme.titleSmall), ), Card( child: Column( children: plan.asMap().entries.map((entry) { final i = entry.key; final meal = entry.value; return Column( children: [ _MealRow(meal: meal), if (i < plan.length - 1) const Divider(height: 1, indent: 16), ], ); }).toList(), ), ), ], ); } } class _MealRow extends StatelessWidget { final TodayMealPlan meal; const _MealRow({required this.meal}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return ListTile( leading: Text(meal.mealEmoji, style: const TextStyle(fontSize: 24)), title: Text(meal.mealLabel, style: theme.textTheme.labelMedium), subtitle: meal.hasRecipe ? Text(meal.recipeTitle!, style: theme.textTheme.bodyMedium) : Text( 'Не запланировано', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, fontStyle: FontStyle.italic, ), ), trailing: meal.calories != null ? Text( '≈${meal.calories!.toInt()} ккал', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant), ) : const Icon(Icons.chevron_right), onTap: () => context.push('/menu'), ); } } // ── Expiring banner ─────────────────────────────────────────── class _ExpiringBanner extends StatelessWidget { final List items; const _ExpiringBanner({required this.items}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final color = theme.colorScheme.errorContainer; final onColor = theme.colorScheme.onErrorContainer; return GestureDetector( onTap: () => context.push('/products'), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Icon(Icons.warning_amber_rounded, color: onColor, size: 20), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Истекает срок годности', style: theme.textTheme.labelMedium ?.copyWith(color: onColor, fontWeight: FontWeight.w600), ), Text( items .take(3) .map((e) => '${e.name} — ${e.expiryLabel}') .join(', '), style: theme.textTheme.bodySmall?.copyWith(color: onColor), ), ], ), ), Icon(Icons.chevron_right, color: onColor), ], ), ), ); } } // ── Quick actions ───────────────────────────────────────────── class _QuickActionsRow extends StatelessWidget { final String date; const _QuickActionsRow({required this.date}); @override Widget build(BuildContext context) { return Row( children: [ Expanded( child: _ActionButton( icon: Icons.document_scanner_outlined, label: 'Сканировать', onTap: () => context.push('/scan'), ), ), const SizedBox(width: 8), Expanded( child: _ActionButton( icon: Icons.calendar_month_outlined, label: 'Меню', onTap: () => context.push('/menu'), ), ), const SizedBox(width: 8), Expanded( child: _ActionButton( icon: Icons.book_outlined, label: 'Дневник', onTap: () => context.push('/menu/diary', extra: date), ), ), ], ); } } class _ActionButton extends StatelessWidget { final IconData icon; final String label; final VoidCallback onTap; const _ActionButton( {required this.icon, required this.label, required this.onTap}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Card( child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.symmetric(vertical: 14), child: Column( children: [ Icon(icon, size: 24), const SizedBox(height: 6), Text(label, style: theme.textTheme.labelSmall, textAlign: TextAlign.center), ], ), ), ), ); } } // ── Section title ───────────────────────────────────────────── class _SectionTitle extends StatelessWidget { final String text; const _SectionTitle(this.text); @override Widget build(BuildContext context) => Text(text, style: Theme.of(context).textTheme.titleSmall); } // ── Recommendations row ─────────────────────────────────────── class _RecommendationsRow extends StatelessWidget { final List recipes; const _RecommendationsRow({required this.recipes}); @override Widget build(BuildContext context) { return SizedBox( height: 168, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: recipes.length, separatorBuilder: (_, __) => const SizedBox(width: 12), itemBuilder: (context, index) => _RecipeCard(recipe: recipes[index]), ), ); } } class _RecipeCard extends StatelessWidget { final HomeRecipe recipe; const _RecipeCard({required this.recipe}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return SizedBox( width: 140, child: Card( clipBehavior: Clip.antiAlias, child: InkWell( onTap: () {}, // recipes detail can be added later child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ recipe.imageUrl.isNotEmpty ? CachedNetworkImage( imageUrl: recipe.imageUrl, height: 96, width: double.infinity, fit: BoxFit.cover, errorWidget: (_, __, ___) => _imagePlaceholder(), ) : _imagePlaceholder(), Padding( padding: const EdgeInsets.fromLTRB(8, 6, 8, 4), child: Text( recipe.title, style: theme.textTheme.bodySmall ?.copyWith(fontWeight: FontWeight.w600), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), if (recipe.calories != null) Padding( padding: const EdgeInsets.fromLTRB(8, 0, 8, 6), child: Text( '≈${recipe.calories!.toInt()} ккал', style: theme.textTheme.labelSmall?.copyWith( color: theme.colorScheme.onSurfaceVariant), ), ), ], ), ), ), ); } Widget _imagePlaceholder() => Container( height: 96, color: Colors.grey.shade200, child: const Icon(Icons.restaurant, color: Colors.grey), ); }