import 'package:flutter/material.dart'; import 'package:patbond_flutter/core/theme/app_theme.dart'; import 'package:patbond_flutter/widgets/common.dart'; /// 头像尺寸档(05 号规范 §3.1)。 enum PetAvatarSize { /// 96:档案页头部(收敛现 104 → 96,与表单页一致)。 xl(96), /// 64:宠物列表卡。 lg(64), /// 44:头部宠物切换器(最小触控目标)。 md(44), /// 32:记录详情等行内。 sm(32); const PetAvatarSize(this.dimension); final double dimension; /// 编辑徽标仅 xl / lg 档提供(05 §3.1)。 bool get supportsBadge => this == xl || this == lg; } /// 宠物头像(05 号规范 §3.1):统一原档案页头部 / 编辑 sheet 各写一遍的 /// 头像实现。圆形裁切 + 3px `surface` 白描边 + 轻投影(正典 /// `.patbond-avatar` 规格),可选右下编辑徽标与品牌渐变环。 /// /// M2 首版不做头像上传(ADR-010 / D2-1 裁决);T3.5-09 起 [url] 由服务端 /// `Pet.avatarUrl`(预签名 GET)驱动,[url] 为 null 时仍渲染本地占位形态 /// (`surfaceTint` 底 + `Icons.pets`)——签一个必然 404 的 URL 比返回 null /// 更糟,故服务端对非 ready 的 asset 直接给 null,客户端只有一种占位逻辑。 /// 有图时经 [RemoteImage] 加载(缓存 key 已剥签名参数;loading / 失败兜底 /// 由其内置)。 class PetAvatar extends StatelessWidget { const PetAvatar({ required this.size, super.key, this.url, this.onTap, this.showEditBadge = false, this.ring = false, this.enabled = true, this.semanticLabel, }); final PetAvatarSize size; /// 头像图 URL;null 为本地占位形态(无头像 / asset 非 ready / 存储未配置)。 final String? url; /// 可点击时整体 [InkWell] 圆形 ripple。 final VoidCallback? onTap; /// 右下编辑徽标(`primaryStrong` 底 + 白 edit 图标,白/primaryStrong /// 4.49:1 达非文字 3:1;修订现实现的 `primary` 底 2.75:1)。 final bool showEditBadge; /// 2px `brandGradient` 外环(正典 story 环),仅作「当前选中」纯装饰指示。 final bool ring; /// 禁用态 60% 不透明度(对齐 AppTextField 禁用惯例)。 final bool enabled; final String? semanticLabel; @override Widget build(BuildContext context) { final dimension = size.dimension; // 3px 白描边内的图面尺寸。 final imageDimension = dimension - 6; Widget avatar = Container( width: dimension, height: dimension, decoration: const BoxDecoration( shape: BoxShape.circle, color: AppColors.surface, boxShadow: [ BoxShadow( color: Color(0x14000000), // rgba(0,0,0,0.08) offset: Offset(0, 4), blurRadius: 10, ), ], ), padding: const EdgeInsets.all(3), child: ClipOval( child: url == null ? ColoredBox( color: AppColors.surfaceTint, child: Icon( Icons.pets, color: AppColors.muted, size: imageDimension * 0.45, ), ) : RemoteImage( url: url!, width: imageDimension, height: imageDimension, borderRadius: BorderRadius.circular(imageDimension / 2), ), ), ); if (ring) { avatar = Container( padding: const EdgeInsets.all(2), decoration: const BoxDecoration( shape: BoxShape.circle, gradient: AppColors.brandGradient, ), child: avatar, ); } if (showEditBadge && size.supportsBadge) { avatar = Stack( clipBehavior: Clip.none, children: [ avatar, const Positioned(right: 0, bottom: 0, child: _EditBadge()), ], ); } if (onTap != null) { avatar = InkWell( onTap: enabled ? onTap : null, customBorder: const CircleBorder(), child: avatar, ); } if (!enabled) { avatar = Opacity(opacity: 0.6, child: avatar); } if (semanticLabel != null) { avatar = Semantics( label: semanticLabel, button: onTap != null, child: avatar, ); } return avatar; } } /// 32 圆编辑徽标:`primaryStrong` 底 + 白 edit 15(05 §3.1 无障碍修订)。 class _EditBadge extends StatelessWidget { const _EditBadge(); @override Widget build(BuildContext context) { return Container( width: 32, height: 32, decoration: const BoxDecoration( shape: BoxShape.circle, color: AppColors.primaryStrong, border: Border.fromBorderSide( BorderSide(color: AppColors.surface, width: 2), ), ), child: const Icon(Icons.edit, color: Colors.white, size: 15), ); } }