import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:nomo/app/extensions/img_extension.dart'; import 'package:shimmer/shimmer.dart'; import 'dart:io'; import '../constants/configs.dart'; enum ImageSourceType { network, asset, file } class IXImage extends StatelessWidget { const IXImage({ super.key, required this.source, required this.width, required this.height, this.sourceType = ImageSourceType.network, this.fadeOutDuration, this.fadeInDuration, this.origAspectRatio, this.borderRadius, this.fit, this.placeholderDuration = const Duration(milliseconds: 400), }); final String source; final ImageSourceType sourceType; final double width; final double height; final Duration? fadeOutDuration; final Duration? fadeInDuration; final double? origAspectRatio; final double? borderRadius; final BoxFit? fit; final Duration placeholderDuration; @override Widget build(BuildContext context) { int? memCacheWidth, memCacheHeight; double aspectRatio = (width / height).toDouble(); void setMemCacheSizes() { if (aspectRatio > 1) { memCacheHeight = height.cacheSize(context); } else if (aspectRatio < 1) { memCacheWidth = width.cacheSize(context); } else { if (origAspectRatio != null && origAspectRatio! > 1) { memCacheWidth = width.cacheSize(context); } else if (origAspectRatio != null && origAspectRatio! < 1) { memCacheHeight = height.cacheSize(context); } else { memCacheWidth = width.cacheSize(context); memCacheHeight = height.cacheSize(context); } } } setMemCacheSizes(); if (memCacheWidth == null && memCacheHeight == null) { memCacheWidth = width.toInt(); } return ClipRRect( clipBehavior: Clip.antiAlias, borderRadius: BorderRadius.circular(borderRadius ?? 0), child: _buildImage(context, memCacheWidth, memCacheHeight), ); } Widget _buildImage( BuildContext context, int? memCacheWidth, int? memCacheHeight, ) { switch (sourceType) { case ImageSourceType.network: String url = source; if (source.indexOf("http") != 0) { url = "${Configs.assetUrl}/$source"; } return CachedNetworkImage( key: ValueKey(url), imageUrl: url, width: width, height: height, memCacheWidth: memCacheWidth, memCacheHeight: memCacheHeight, fit: fit ?? BoxFit.cover, fadeOutDuration: fadeOutDuration ?? const Duration(milliseconds: 250), fadeInDuration: fadeInDuration ?? const Duration(milliseconds: 350), filterQuality: FilterQuality.medium, errorWidget: (context, url, error) => _buildErrorWidget(context, url, error), placeholder: (context, url) => _buildPlaceholder(context, url), ); case ImageSourceType.asset: return Image.asset( source, width: width, height: height, fit: fit ?? BoxFit.cover, alignment: fit == BoxFit.fitWidth ? Alignment.topCenter : Alignment.center, errorBuilder: (context, error, stackTrace) => _buildErrorWidget(context, source, error), ); case ImageSourceType.file: return Image.file( File(source), width: width, height: height, fit: fit ?? BoxFit.cover, errorBuilder: (context, error, stackTrace) => _buildErrorWidget(context, source, error), ); } } Widget _buildErrorWidget(BuildContext context, String url, dynamic error) { return _buildSmoothPlaceholder(context, isError: false); } Widget _buildPlaceholder(BuildContext context, String url) { return _buildSmoothPlaceholder(context, isError: false); } Widget _buildSmoothPlaceholder( BuildContext context, { required bool isError, }) { return AnimatedContainer( duration: placeholderDuration, curve: Curves.easeInOut, width: width, height: height, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isError ? [ Colors.red.withValues(alpha: 0.15), Colors.red.withValues(alpha: 0.08), Colors.red.withValues(alpha: 0.15), ] : [ Colors.grey.withValues(alpha: 0.25), Colors.grey.withValues(alpha: 0.08), Colors.grey.withValues(alpha: 0.25), ], stops: const [0.0, 0.5, 1.0], ), borderRadius: BorderRadius.circular(borderRadius ?? 0), ), child: Shimmer.fromColors( baseColor: isError ? Colors.red.withValues(alpha: 0.3) : Colors.grey.withValues(alpha: 0.4), highlightColor: isError ? Colors.red.withValues(alpha: 0.1) : Colors.grey.withValues(alpha: 0.1), period: const Duration(milliseconds: 1800), child: Container( width: width, height: height, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(borderRadius ?? 0), ), ), ), ); } }