/// Performance-optimized cached network image widget /// /// Features: /// - Automatic memory and disk caching /// - Optimized image sizing to reduce memory usage /// - Smooth fade-in animations /// - Shimmer loading placeholders /// - Graceful error handling /// - RepaintBoundary for isolation import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import '../config/image_cache_config.dart'; import '../constants/performance_constants.dart'; /// Optimized cached network image with performance enhancements class OptimizedCachedImage extends StatelessWidget { final String? imageUrl; final ImageContext context; final BoxFit fit; final double? width; final double? height; final Widget? placeholder; final Widget? errorWidget; final bool useRepaintBoundary; const OptimizedCachedImage({ super.key, required this.imageUrl, this.context = ImageContext.gridThumbnail, this.fit = BoxFit.cover, this.width, this.height, this.placeholder, this.errorWidget, this.useRepaintBoundary = true, }); @override Widget build(BuildContext context) { final image = _buildImage(); // Wrap in RepaintBoundary for better performance return useRepaintBoundary ? RepaintBoundary(child: image) : image; } Widget _buildImage() { if (imageUrl == null || imageUrl!.isEmpty) { return _buildErrorWidget(); } // Get optimal dimensions for this context final dimensions = ImageOptimization.getOptimalDimensions( screenWidth: width ?? 300, context: this.context, ); // Choose appropriate cache manager final cacheManager = this.context == ImageContext.categoryCard ? CategoryImageCacheManager() : ProductImageCacheManager(); return CachedNetworkImage( imageUrl: imageUrl!, cacheManager: cacheManager, // Performance optimization: resize in memory memCacheWidth: dimensions.width, memCacheHeight: dimensions.height, // Performance optimization: resize on disk maxWidthDiskCache: dimensions.width * 2, maxHeightDiskCache: dimensions.height * 2, // Sizing width: width, height: height, fit: fit, // Smooth fade-in animation fadeInDuration: Duration( milliseconds: PerformanceConstants.imageFadeDuration, ), fadeOutDuration: Duration( milliseconds: PerformanceConstants.fastAnimationDuration, ), // Placeholder while loading placeholder: (context, url) => placeholder ?? _buildPlaceholder(), // Error widget if loading fails errorWidget: (context, url, error) => errorWidget ?? _buildErrorWidget(), ); } Widget _buildPlaceholder() { return Container( width: width, height: height, decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(8), ), child: const Center( child: ShimmerPlaceholder(), ), ); } Widget _buildErrorWidget() { return Container( width: width, height: height, decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.image_not_supported_outlined, color: Colors.grey[400], size: 48, ), ); } } /// Shimmer loading placeholder for better UX class ShimmerPlaceholder extends StatefulWidget { final double? width; final double? height; final BorderRadius? borderRadius; const ShimmerPlaceholder({ super.key, this.width, this.height, this.borderRadius, }); @override State createState() => _ShimmerPlaceholderState(); } class _ShimmerPlaceholderState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: Duration( milliseconds: PerformanceConstants.shimmerDuration, ), vsync: this, )..repeat(); _animation = Tween(begin: -2, end: 2).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (context, child) { return Container( width: widget.width, height: widget.height, decoration: BoxDecoration( borderRadius: widget.borderRadius ?? BorderRadius.circular(8), gradient: LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ Colors.grey[200]!, Colors.grey[100]!, Colors.grey[200]!, ], stops: const [0.0, 0.5, 1.0], transform: GradientRotation(_animation.value), ), ), ); }, ); } } /// Product grid image - optimized for grid display class ProductGridImage extends StatelessWidget { final String? imageUrl; final double size; const ProductGridImage({ super.key, required this.imageUrl, this.size = 150, }); @override Widget build(BuildContext context) { return OptimizedCachedImage( imageUrl: imageUrl, context: ImageContext.gridThumbnail, width: size, height: size, fit: BoxFit.cover, ); } } /// Category card image - optimized for category display class CategoryCardImage extends StatelessWidget { final String? imageUrl; final double size; const CategoryCardImage({ super.key, required this.imageUrl, this.size = 120, }); @override Widget build(BuildContext context) { return OptimizedCachedImage( imageUrl: imageUrl, context: ImageContext.categoryCard, width: size, height: size, fit: BoxFit.cover, ); } } /// Cart item thumbnail - very small optimized image class CartItemThumbnail extends StatelessWidget { final String? imageUrl; final double size; const CartItemThumbnail({ super.key, required this.imageUrl, this.size = 60, }); @override Widget build(BuildContext context) { return OptimizedCachedImage( imageUrl: imageUrl, context: ImageContext.cartThumbnail, width: size, height: size, fit: BoxFit.cover, ); } } /// Product detail image - larger but still optimized class ProductDetailImage extends StatelessWidget { final String? imageUrl; final double? width; final double? height; const ProductDetailImage({ super.key, required this.imageUrl, this.width, this.height, }); @override Widget build(BuildContext context) { return OptimizedCachedImage( imageUrl: imageUrl, context: ImageContext.detail, width: width, height: height, fit: BoxFit.contain, ); } }