import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:cached_network_image/cached_network_image.dart'; import '../../../products/presentation/providers/products_provider.dart'; import '../../../products/presentation/providers/selected_category_provider.dart'; import '../../../categories/presentation/providers/categories_provider.dart'; import '../providers/cart_provider.dart'; import '../providers/cart_total_provider.dart'; import '../../domain/entities/cart_item.dart'; import '../../../../core/widgets/loading_indicator.dart'; import '../../../../core/widgets/error_widget.dart'; import '../../../../core/widgets/empty_state.dart'; import '../../../../core/config/image_cache_config.dart'; import '../../../../shared/widgets/price_display.dart'; /// Home page - Quick sale POS interface class HomePage extends ConsumerStatefulWidget { const HomePage({super.key}); @override ConsumerState createState() => _HomePageState(); } class _HomePageState extends ConsumerState { String _searchQuery = ''; @override Widget build(BuildContext context) { final productsAsync = ref.watch(productsProvider); final categoriesAsync = ref.watch(categoriesProvider); final selectedCategory = ref.watch(selectedCategoryProvider); final cartAsync = ref.watch(cartProvider); final totalData = ref.watch(cartTotalProvider); final theme = Theme.of(context); final cartItems = cartAsync.value ?? []; final itemCount = cartItems.length; return SafeArea( bottom: false, child: Scaffold( backgroundColor: theme.colorScheme.surfaceContainerLowest, body: Column( children: [ // Search bar Container( padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), color: theme.colorScheme.surface, child: TextField( onChanged: (value) { setState(() { _searchQuery = value; }); }, decoration: InputDecoration( hintText: 'Search Menu', prefixIcon: const Icon(Icons.search, size: 20), suffixIcon: _searchQuery.isNotEmpty ? IconButton( icon: const Icon(Icons.clear, size: 20), onPressed: () { setState(() { _searchQuery = ''; }); }, ) : IconButton( icon: const Icon(Icons.tune, size: 20), onPressed: () { // TODO: Show filters }, ), filled: true, fillColor: theme.colorScheme.surfaceContainerHighest, contentPadding: const EdgeInsets.symmetric(vertical: 8), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: theme.colorScheme.outline.withOpacity(0.3), ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: theme.colorScheme.outline.withOpacity(0.3), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: theme.colorScheme.primary, width: 1.5, ), ), ), ), ), // Category filter buttons categoriesAsync.when( loading: () => const SizedBox.shrink(), error: (_, __) => const SizedBox.shrink(), data: (categories) { if (categories.isEmpty) return const SizedBox.shrink(); return Container( height: 75, padding: const EdgeInsets.symmetric(vertical: 8), color: theme.colorScheme.surface, child: ListView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), children: [ // All/Favorite category _CategoryButton( icon: Icons.star, label: 'Favorite', isSelected: selectedCategory == null, onTap: () { ref .read(selectedCategoryProvider.notifier) .clearSelection(); }, ), const SizedBox(width: 12), // Category buttons ...categories.map( (category) => Padding( padding: const EdgeInsets.only(right: 12.0), child: _CategoryButton( icon: _getCategoryIcon(category.name), label: category.name, isSelected: selectedCategory == category.id, onTap: () { ref .read(selectedCategoryProvider.notifier) .selectCategory(category.id); }, ), ), ), ], ), ); }, ), // Products list Expanded( child: productsAsync.when( loading: () => const LoadingIndicator( message: 'Loading products...', ), error: (error, stack) => ErrorDisplay( message: error.toString(), onRetry: () => ref.refresh(productsProvider), ), data: (products) { // Filter available products var availableProducts = products.where((p) => p.isAvailable).toList(); // Apply category filter if (selectedCategory != null) { availableProducts = availableProducts .where((p) => p.categoryId == selectedCategory) .toList(); } // Apply search filter if (_searchQuery.isNotEmpty) { availableProducts = availableProducts.where((p) { final query = _searchQuery.toLowerCase(); return p.name.toLowerCase().contains(query) || (p.description?.toLowerCase().contains(query) ?? false); }).toList(); } if (availableProducts.isEmpty) { return EmptyState( message: _searchQuery.isNotEmpty ? 'No products found' : 'No products available', subMessage: _searchQuery.isNotEmpty ? 'Try a different search term' : 'Add products to start selling', icon: Icons.inventory_2_outlined, ); } return ListView.builder( padding: const EdgeInsets.all(16), itemCount: availableProducts.length, itemBuilder: (context, index) { final product = availableProducts[index]; // Find if product is in cart final cartItem = cartItems.firstWhere( (item) => item.productId == product.id, orElse: () => CartItem( productId: '', productName: '', price: 0, quantity: 0, addedAt: DateTime.now(), ), ); final isInCart = cartItem.productId.isNotEmpty; final quantity = isInCart ? cartItem.quantity : 0; return _ProductListItem( product: product, quantity: quantity, onAdd: () => _addToCart(product), onIncrement: isInCart ? () => ref .read(cartProvider.notifier) .updateQuantity(product.id, quantity + 1) : null, onDecrement: isInCart ? () { if (quantity > 1) { ref .read(cartProvider.notifier) .updateQuantity(product.id, quantity - 1); } else { ref .read(cartProvider.notifier) .removeItem(product.id); } } : null, ); }, ); }, ), ), ], ), // Bottom bar bottomNavigationBar: itemCount > 0 ? Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: theme.colorScheme.surface, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, -2), ), ], ), child: SafeArea( child: FilledButton( onPressed: () => _proceedToCheckout(), style: FilledButton.styleFrom( backgroundColor: theme.colorScheme.primary, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ const SizedBox(width: 8), Text( 'Proceed New Order', style: theme.textTheme.titleMedium?.copyWith( color: theme.colorScheme.onPrimary, fontWeight: FontWeight.bold, ), ), ], ), Row( children: [ Text( '$itemCount items', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onPrimary, ), ), const SizedBox(width: 8), PriceDisplay( price: totalData.total, style: theme.textTheme.titleMedium?.copyWith( color: theme.colorScheme.onPrimary, fontWeight: FontWeight.bold, ), ), const SizedBox(width: 8), const Icon(Icons.arrow_forward, color: Colors.white), ], ), ], ), ), ), ) : null, ), ); } void _addToCart(dynamic product) { final cartItem = CartItem( productId: product.id, productName: product.name, price: product.price, quantity: 1, imageUrl: product.imageUrl, addedAt: DateTime.now(), ); ref.read(cartProvider.notifier).addItem(cartItem); } void _proceedToCheckout() { // TODO: Navigate to checkout/order detail screen ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Proceeding to checkout...'), duration: Duration(seconds: 2), ), ); } IconData _getCategoryIcon(String categoryName) { final name = categoryName.toLowerCase(); if (name.contains('drink') || name.contains('beverage')) { return Icons.local_cafe; } else if (name.contains('food') || name.contains('meal')) { return Icons.restaurant; } else if (name.contains('dessert') || name.contains('sweet')) { return Icons.cake; } else { return Icons.category; } } } /// Category filter button class _CategoryButton extends StatelessWidget { final IconData icon; final String label; final bool isSelected; final VoidCallback onTap; const _CategoryButton({ required this.icon, required this.label, required this.isSelected, required this.onTap, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: isSelected ? theme.colorScheme.primaryContainer : theme.colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(12), border: Border.all( color: isSelected ? theme.colorScheme.primary : Colors.transparent, width: 1.5, ), ), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( icon, color: isSelected ? theme.colorScheme.primary : theme.colorScheme.onSurfaceVariant, size: 22, ), const SizedBox(height: 2), Text( label, style: theme.textTheme.labelSmall?.copyWith( color: isSelected ? theme.colorScheme.primary : theme.colorScheme.onSurfaceVariant, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), ], ), ), ); } } /// Immutable product image widget that won't rebuild class _ProductImage extends StatelessWidget { final String productId; final String? imageUrl; const _ProductImage({ required this.productId, required this.imageUrl, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return ClipRRect( borderRadius: BorderRadius.circular(12), child: imageUrl != null && imageUrl!.isNotEmpty ? CachedNetworkImage( key: ValueKey('product_img_$productId'), imageUrl: imageUrl!, width: 60, height: 60, fit: BoxFit.cover, cacheManager: ProductImageCacheManager(), memCacheWidth: 120, memCacheHeight: 120, maxWidthDiskCache: 240, maxHeightDiskCache: 240, fadeInDuration: Duration.zero, // No fade animation fadeOutDuration: Duration.zero, // No fade animation placeholder: (context, url) => Container( width: 60, height: 60, color: theme.colorScheme.surfaceContainerHighest, ), errorWidget: (context, url, error) => Container( width: 60, height: 60, color: theme.colorScheme.surfaceContainerHighest, child: Icon( Icons.image_not_supported, color: theme.colorScheme.onSurfaceVariant, size: 24, ), ), ) : Container( width: 60, height: 60, color: theme.colorScheme.surfaceContainerHighest, child: Icon( Icons.inventory_2, color: theme.colorScheme.onSurfaceVariant, ), ), ); } } /// Product list item class _ProductListItem extends StatelessWidget { final dynamic product; final int quantity; final VoidCallback onAdd; final VoidCallback? onIncrement; final VoidCallback? onDecrement; const _ProductListItem({ required this.product, required this.quantity, required this.onAdd, this.onIncrement, this.onDecrement, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); final isInCart = quantity > 0; return Container( margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: theme.colorScheme.surface, borderRadius: BorderRadius.circular(16), border: Border.all( color: isInCart ? theme.colorScheme.primary.withOpacity(0.3) : theme.colorScheme.outlineVariant, width: isInCart ? 2 : 1, ), ), child: Padding( padding: const EdgeInsets.all(12), child: Row( children: [ // Product image - separated into its own widget _ProductImage( productId: product.id, imageUrl: product.imageUrl, ), const SizedBox(width: 12), // Product info Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( product.name, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), PriceDisplay( price: product.price, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.primary, ), ), ], ), ), const SizedBox(width: 12), // Add/quantity controls if (!isInCart) IconButton( onPressed: onAdd, icon: const Icon(Icons.add_circle), iconSize: 32, color: theme.colorScheme.primary, ) else Container( decoration: BoxDecoration( color: theme.colorScheme.primaryContainer, borderRadius: BorderRadius.circular(24), ), child: Row( children: [ IconButton( onPressed: onDecrement, icon: const Icon(Icons.remove), iconSize: 20, color: theme.colorScheme.primary, ), Text( '$quantity', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.primary, ), ), IconButton( onPressed: onIncrement, icon: const Icon(Icons.add), iconSize: 20, color: theme.colorScheme.primary, ), ], ), ), ], ), ), ); } }