import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:wordpress/providers/api_controller.dart'; import 'package:wordpress/viewmodel/all_product_model.dart'; class AllProductScreen extends ConsumerStatefulWidget { const AllProductScreen({super.key}); @override ConsumerState createState() => _AllProductScreenState(); } class _AllProductScreenState extends ConsumerState with TickerProviderStateMixin { final ScrollController _scrollController = ScrollController(); final int _itemsPerPage = 10; int _currentPage = 1; List _displayedProducts = []; bool _isLoadingMore = false; late AnimationController _fadeController; late AnimationController _slideController; late Animation _fadeAnimation; late Animation _slideAnimation; @override void initState() { super.initState(); _initializeAnimations(); _setupScrollListener(); } void _initializeAnimations() { _fadeController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _slideController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), ); _slideAnimation = Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( CurvedAnimation(parent: _slideController, curve: Curves.easeOutBack), ); } void _setupScrollListener() { _scrollController.addListener(() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { _loadMoreProducts(); } }); } void _loadMoreProducts() { if (_isLoadingMore) return; final asyncProducts = ref.read(allProductProvider); asyncProducts.whenData((allProducts) { if (_displayedProducts.length < allProducts.length) { setState(() { _isLoadingMore = true; }); Future.delayed(const Duration(milliseconds: 500), () { setState(() { final startIndex = _currentPage * _itemsPerPage; final endIndex = ((startIndex + _itemsPerPage) > allProducts.length) ? allProducts.length : startIndex + _itemsPerPage; _displayedProducts.addAll( allProducts.sublist(startIndex, endIndex), ); _currentPage++; _isLoadingMore = false; }); }); } }); } @override void dispose() { _scrollController.dispose(); _fadeController.dispose(); _slideController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final asyncProducts = ref.watch(allProductProvider); return Scaffold( backgroundColor: Colors.grey[50], appBar: AppBar( title: const Text( 'All Products', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white), ), backgroundColor: Colors.blue[600], elevation: 0, centerTitle: true, ), body: asyncProducts.when( loading: () => _buildLoadingWidget(), error: (err, stack) => _buildErrorWidget(err), data: (products) { if (products.isEmpty) { return _buildEmptyWidget(); } // Initialize displayed products on first load if (_displayedProducts.isEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { _displayedProducts = products.take(_itemsPerPage).toList(); }); _fadeController.forward(); _slideController.forward(); }); } return _buildProductGrid(products); }, ), ); } Widget _buildLoadingWidget() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TweenAnimationBuilder( duration: const Duration(seconds: 1), tween: Tween(begin: 0.0, end: 1.0), builder: (context, value, child) { return Transform.rotate( angle: value * 2 * 3.14159, child: Container( width: 60, height: 60, decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), gradient: const LinearGradient( colors: [Colors.blue, Colors.purple], ), ), child: const Icon( Icons.shopping_bag, color: Colors.white, size: 30, ), ), ); }, ), const SizedBox(height: 16), const Text( "Loading amazing products...", style: TextStyle( fontSize: 16, color: Colors.grey, fontWeight: FontWeight.w500, ), ), ], ), ); } Widget _buildErrorWidget(Object error) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 64, color: Colors.red[400]), const SizedBox(height: 16), Text( 'Oops! Something went wrong', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.grey[800], ), ), const SizedBox(height: 8), Text( 'Error: $error', textAlign: TextAlign.center, style: TextStyle(fontSize: 14, color: Colors.grey[600]), ), const SizedBox(height: 24), ElevatedButton.icon( onPressed: () { ref.invalidate(allProductProvider); }, icon: const Icon(Icons.refresh), label: const Text('Retry'), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue[600], foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), ), ], ), ); } Widget _buildEmptyWidget() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.shopping_bag_outlined, size: 64, color: Colors.grey[400]), const SizedBox(height: 16), Text( 'No products found', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.grey[800], ), ), const SizedBox(height: 8), Text( 'Check back later for new products', style: TextStyle(fontSize: 14, color: Colors.grey[600]), ), ], ), ); } Widget _buildProductGrid(List allProducts) { return FadeTransition( opacity: _fadeAnimation, child: SlideTransition( position: _slideAnimation, child: CustomScrollView( controller: _scrollController, physics: const BouncingScrollPhysics(), slivers: [ SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverGrid( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 16, mainAxisSpacing: 16, childAspectRatio: 0.75, ), delegate: SliverChildBuilderDelegate((context, index) { return AnimatedSwitcher( duration: const Duration(milliseconds: 500), transitionBuilder: (child, animation) { return SlideTransition( position: Tween( begin: const Offset(0, 0.3), end: Offset.zero, ).animate(animation), child: FadeTransition(opacity: animation, child: child), ); }, child: ProductCard( product: _displayedProducts[index], key: ValueKey(_displayedProducts[index].id), animationDelay: Duration(milliseconds: index * 100), ), ); }, childCount: _displayedProducts.length), ), ), if (_isLoadingMore || _displayedProducts.length < allProducts.length) SliverToBoxAdapter(child: _buildLoadMoreWidget()), ], ), ), ); } Widget _buildLoadMoreWidget() { return Container( padding: const EdgeInsets.all(20), child: Center( child: _isLoadingMore ? Column( children: [ CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( Colors.blue[600]!, ), strokeWidth: 2, ), const SizedBox(height: 12), Text( 'Loading more products...', style: TextStyle(color: Colors.grey[600], fontSize: 14), ), ], ) : ElevatedButton.icon( onPressed: _loadMoreProducts, icon: const Icon(Icons.expand_more), label: const Text('Load More'), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue[600], foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), ), ), ), ), ); } } class ProductCard extends StatefulWidget { final AllProductModel product; final Duration animationDelay; const ProductCard({ super.key, required this.product, this.animationDelay = Duration.zero, }); @override State createState() => _ProductCardState(); } class _ProductCardState extends State with SingleTickerProviderStateMixin { late AnimationController _hoverController; late Animation _scaleAnimation; late Animation _elevationAnimation; bool _isVisible = false; @override void initState() { super.initState(); _hoverController = AnimationController( duration: const Duration(milliseconds: 200), vsync: this, ); _scaleAnimation = Tween(begin: 1.0, end: 1.05).animate( CurvedAnimation(parent: _hoverController, curve: Curves.easeInOut), ); _elevationAnimation = Tween(begin: 4.0, end: 12.0).animate( CurvedAnimation(parent: _hoverController, curve: Curves.easeInOut), ); // Staggered animation entrance Future.delayed(widget.animationDelay, () { if (mounted) { setState(() { _isVisible = true; }); } }); } @override void dispose() { _hoverController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedOpacity( opacity: _isVisible ? 1.0 : 0.0, duration: const Duration(milliseconds: 600), child: AnimatedBuilder( animation: _hoverController, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: GestureDetector( onTapDown: (_) => _hoverController.forward(), onTapUp: (_) => _hoverController.reverse(), onTapCancel: () => _hoverController.reverse(), child: AnimatedContainer( duration: const Duration(milliseconds: 200), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: _elevationAnimation.value, offset: Offset(0, _elevationAnimation.value / 2), spreadRadius: 1, ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [_buildProductImage(), _buildProductDetails()], ), ), ), ), ); }, ), ); } Widget _buildProductImage() { return Expanded( flex: 3, child: Container( width: double.infinity, decoration: const BoxDecoration( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Stack( children: [ Hero( tag: 'product_${widget.product.id}', child: Image.network( widget.product.images.isNotEmpty ? widget.product.images.first.src : '', width: double.infinity, height: double.infinity, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return Container( color: Colors.grey[100], child: Center( child: CircularProgressIndicator( value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, valueColor: AlwaysStoppedAnimation( Colors.blue[300]!, ), strokeWidth: 2, ), ), ); }, errorBuilder: (context, error, stackTrace) => Container( color: Colors.grey[100], child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.image_not_supported_outlined, color: Colors.grey[400], size: 32, ), const SizedBox(height: 8), Text( 'Image not available', style: TextStyle(color: Colors.grey[500], fontSize: 12), ), ], ), ), ), ), // Gradient overlay Positioned( bottom: 0, left: 0, right: 0, child: Container( height: 40, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black.withOpacity(0.15), ], ), ), ), ), ], ), ), ); } Widget _buildProductDetails() { return Expanded( flex: 3, child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( widget.product.name, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, height: 1.3, ), ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( child: Text( "₹${widget.product.price}", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.green[600], ), overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.blue[600], borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.blue.withOpacity(0.3), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: const Icon( Icons.add_shopping_cart, color: Colors.white, size: 18, ), ), ], ), ], ), ), ); } }