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(); }, ); } }