105 lines
2.9 KiB
Dart
105 lines
2.9 KiB
Dart
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:shadcn_ui/shadcn_ui.dart';
|
|
|
|
/// 金值统一的网络图片入口。
|
|
///
|
|
/// 图片会自动使用 [CachedNetworkImage] 的磁盘缓存;业务页面不应直接创建
|
|
/// `Image.network`,以保证加载和失败状态的视觉及缓存策略一致。
|
|
class AppNetworkImage extends StatelessWidget {
|
|
const AppNetworkImage({
|
|
super.key,
|
|
required this.imageUrl,
|
|
this.width,
|
|
this.height,
|
|
this.fit = BoxFit.cover,
|
|
this.alignment = Alignment.center,
|
|
this.borderRadius,
|
|
this.cacheKey,
|
|
this.httpHeaders,
|
|
this.placeholder,
|
|
this.errorWidget,
|
|
this.semanticLabel,
|
|
});
|
|
|
|
final String imageUrl;
|
|
final double? width;
|
|
final double? height;
|
|
final BoxFit fit;
|
|
final Alignment alignment;
|
|
final BorderRadius? borderRadius;
|
|
final String? cacheKey;
|
|
final Map<String, String>? httpHeaders;
|
|
final Widget? placeholder;
|
|
final Widget? errorWidget;
|
|
final String? semanticLabel;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final child = imageUrl.trim().isEmpty
|
|
? _ImageFallback(errorWidget: errorWidget)
|
|
: CachedNetworkImage(
|
|
imageUrl: imageUrl,
|
|
cacheKey: cacheKey,
|
|
httpHeaders: httpHeaders,
|
|
width: width,
|
|
height: height,
|
|
fit: fit,
|
|
alignment: alignment,
|
|
fadeInDuration: const Duration(milliseconds: 150),
|
|
placeholder: (_, _) =>
|
|
placeholder ?? const _ImageLoadingPlaceholder(),
|
|
errorWidget: (_, _, _) => _ImageFallback(errorWidget: errorWidget),
|
|
);
|
|
|
|
final clipped = borderRadius == null
|
|
? child
|
|
: ClipRRect(borderRadius: borderRadius!, child: child);
|
|
if (semanticLabel == null) return clipped;
|
|
return Semantics(image: true, label: semanticLabel, child: clipped);
|
|
}
|
|
}
|
|
|
|
class _ImageLoadingPlaceholder extends StatelessWidget {
|
|
const _ImageLoadingPlaceholder();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final colors = ShadTheme.of(context).colorScheme;
|
|
return ColoredBox(
|
|
color: colors.muted,
|
|
child: Center(
|
|
child: SizedBox(
|
|
width: 20,
|
|
height: 20,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
color: colors.mutedForeground,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ImageFallback extends StatelessWidget {
|
|
const _ImageFallback({required this.errorWidget});
|
|
|
|
final Widget? errorWidget;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (errorWidget != null) return errorWidget!;
|
|
final colors = ShadTheme.of(context).colorScheme;
|
|
return ColoredBox(
|
|
color: colors.muted,
|
|
child: Center(
|
|
child: Icon(
|
|
Icons.image_not_supported_outlined,
|
|
color: colors.mutedForeground,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|