Files
retail/lib/core/widgets/optimized_cached_image.dart
Phuoc Nguyen b94c158004 runable
2025-10-10 16:38:07 +07:00

295 lines
7.0 KiB
Dart

/// 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<ShimmerPlaceholder> createState() => _ShimmerPlaceholderState();
}
class _ShimmerPlaceholderState extends State<ShimmerPlaceholder>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(
milliseconds: PerformanceConstants.shimmerDuration,
),
vsync: this,
)..repeat();
_animation = Tween<double>(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,
);
}
}