runable
This commit is contained in:
294
lib/core/widgets/optimized_cached_image.dart
Normal file
294
lib/core/widgets/optimized_cached_image.dart
Normal file
@@ -0,0 +1,294 @@
|
||||
/// 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user