Files

80 lines
2.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import '../core/network/api_env.dart';
import 'cute_icon.dart';
/// 圆形头像。全 App 只有这一个头像组件 —— 人设、用户、「我的」页面、聊天气泡都用它。
///
/// 三种输入都要兜住:
/// · [assetPath] 为空 → 画默认图标([fallbackIcon])
/// · 图片加载中 → 显示一个淡淡的占位圆
/// · 加载失败(后端文件被删了 / 网络断了)→ 退回默认图标,不显示裂图
///
/// 为什么不用 `CircleAvatar`:它的 backgroundImage 出错时不好接管,
/// 会有「裂图 + 背景色」的难看状态。自己用 [ClipOval] + [Image.network] 能完全控制。
class Avatar extends StatelessWidget {
const Avatar({
super.key,
this.assetPath,
this.size = 40,
this.fallbackIcon = 'user',
this.fallbackColor = const Color(0xFFC8956E),
this.backgroundColor = const Color(0xFFFFF3E6),
});
/// 后端返回的**相对路径**(如 `persona/2026/09/29/xxx.jpg`)。
/// 传空或 null → 用默认图标。
final String? assetPath;
final double size;
/// 没有头像时显示哪个小图标(`CuteIcon` 的名字)
final String fallbackIcon;
/// 默认图标的颜色
final Color fallbackColor;
/// 头像底圈的颜色
final Color backgroundColor;
@override
Widget build(BuildContext context) {
final path = assetPath?.trim() ?? '';
return Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: backgroundColor,
),
clipBehavior: Clip.antiAlias,
child: path.isEmpty ? _fallback() : _image(path),
);
}
Widget _fallback() {
return Center(
child: CuteIcon(fallbackIcon, size: size * 0.58),
);
}
Widget _image(String path) {
return Image.network(
resolveAssetUrl('/uploads/$path'),
fit: BoxFit.cover,
// 头像显示尺寸很小,限一下解码尺寸省内存 —— 原图可能是 400px
cacheWidth: (size * 3).round(),
loadingBuilder: (context, child, progress) {
// 还没加载出来时给个纯色圆,比空白或转圈干净
if (progress == null) return child;
return const SizedBox.shrink();
},
errorBuilder: (context, _, _) {
// 后端文件被删了也会走到这 —— 退回默认图标,不留裂图
return _fallback();
},
);
}
}