/// Home tab: greeting, stats, and a [ListView.builder] of catalog items. library; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/item_provider.dart'; import '../widgets/empty_state.dart'; import '../widgets/item_card.dart'; import '../widgets/loading_view.dart'; import '../widgets/responsive_body.dart'; import 'add_item_screen.dart'; class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context) { final controller = context.watch(); final theme = Theme.of(context); return Scaffold( appBar: AppBar(title: const Text('Home')), floatingActionButton: FloatingActionButton.extended( onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (_) => const AddItemScreen()), ); }, icon: const Icon(Icons.add), label: const Text('Add item'), ), body: RefreshIndicator( onRefresh: controller.loadItems, child: ResponsiveBody( child: _HomeBody(controller: controller, theme: theme), ), ), ); } } class _HomeBody extends StatelessWidget { const _HomeBody({ required this.controller, required this.theme, }); final ItemController controller; final ThemeData theme; @override Widget build(BuildContext context) { if (controller.isLoading && controller.items.isEmpty) { return const LoadingView(message: 'Fetching catalog…'); } if (controller.error != null && controller.items.isEmpty) { return ListView( physics: const AlwaysScrollableScrollPhysics(), children: [ EmptyState( icon: Icons.error_outline, title: 'Something went wrong', message: controller.error!, actionLabel: 'Retry', onAction: controller.loadItems, ), ], ); } if (controller.items.isEmpty) { return ListView( physics: const AlwaysScrollableScrollPhysics(), children: const [ EmptyState( icon: Icons.inbox_outlined, title: 'No items yet', message: 'Add your first item with the button below.', ), ], ); } // Header occupies the first slot; remaining slots are catalog cards. return ListView.builder( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.only(top: 8, bottom: 96), itemCount: controller.items.length + 1, itemBuilder: (context, index) { if (index == 0) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _WelcomeBanner(itemCount: controller.count), const SizedBox(height: 20), Text('Featured items', style: theme.textTheme.titleMedium), const SizedBox(height: 12), ], ); } final item = controller.items[index - 1]; return Padding( padding: const EdgeInsets.only(bottom: 12), child: ItemCard(item: item, heroScope: 'home'), ); }, ); } } class _WelcomeBanner extends StatelessWidget { const _WelcomeBanner({required this.itemCount}); final int itemCount; @override Widget build(BuildContext context) { final theme = Theme.of(context); final colors = theme.colorScheme; return Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: LinearGradient( colors: [colors.primary, colors.tertiary], ), boxShadow: [ BoxShadow( color: colors.primary.withValues(alpha: 0.28), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Welcome back', style: theme.textTheme.labelMedium?.copyWith( color: colors.onPrimary.withValues(alpha: 0.8), ), ), const SizedBox(height: 4), Text( 'Your catalog at a glance', style: theme.textTheme.headlineMedium?.copyWith( color: colors.onPrimary, ), ), const SizedBox(height: 16), Row( children: [ _StatPill( label: 'Items', value: '$itemCount', icon: Icons.collections_bookmark_outlined, ), const SizedBox(width: 10), const _StatPill( label: 'Theme', value: 'M3', icon: Icons.palette_outlined, ), ], ), ], ), ); } } class _StatPill extends StatelessWidget { const _StatPill({ required this.label, required this.value, required this.icon, }); final String label; final String value; final IconData icon; @override Widget build(BuildContext context) { final onPrimary = Theme.of(context).colorScheme.onPrimary; return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: onPrimary.withValues(alpha: 0.14), borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Icon(icon, size: 16, color: onPrimary), const SizedBox(width: 8), Text( '$value $label', style: Theme.of(context).textTheme.labelMedium?.copyWith( color: onPrimary, ), ), ], ), ); } }