import 'package:flutter/material.dart'; import 'package:patbond_flutter/core/network/signed_network_image.dart'; import 'package:patbond_flutter/core/theme/app_theme.dart'; class RemoteImage extends StatelessWidget { const RemoteImage({ required this.url, super.key, this.width, this.height, this.fit = BoxFit.cover, this.borderRadius = BorderRadius.zero, }); final String url; final double? width; final double? height; final BoxFit fit; final BorderRadius borderRadius; @override Widget build(BuildContext context) { return ClipRRect( borderRadius: borderRadius, // 缓存 key 剥离预签名参数(T3-14):媒体 URL 每次响应现签, // 按完整 URL 缓存会同图重复下载。 child: Image( image: SignedNetworkImage(url), width: width, height: height, fit: fit, loadingBuilder: (context, child, progress) { if (progress == null) return child; return ColoredBox( color: AppColors.surfaceTint, child: SizedBox( width: width, height: height, child: const Center( child: CircularProgressIndicator(strokeWidth: 2), ), ), ); }, errorBuilder: (context, error, stackTrace) { return ColoredBox( color: AppColors.surfaceTint, child: SizedBox( width: width, height: height, child: const Center( child: Icon(Icons.pets, color: AppColors.muted, size: 34), ), ), ); }, ), ); } } class SectionCard extends StatelessWidget { const SectionCard({required this.child, super.key, this.padding}); final Widget child; final EdgeInsetsGeometry? padding; @override Widget build(BuildContext context) { return Card( child: Padding( padding: padding ?? const EdgeInsets.all(18), child: child, ), ); } } /// 胶囊标签:底色为 [color] 的 8% 淡染(`withAlpha(20)`),文字用深变体。 /// /// DEBT-1 偿还(05 号规范 §5.3):原实现文字直接用 [color],在自身淡底 /// 上仅 1.67–2.55:1 不达 WCAG AA;现文字色按内置映射取深变体 /// (primary→primaryDark 8.74:1、success→successInk 7.39:1、 /// accent→accentDark 7.07:1、error→errorDark 5.78:1、未命中→ink 兜底), /// 或经 [inkColor] 显式指定。既有调用零参数变更、仅视觉变深。 class TagPill extends StatelessWidget { const TagPill( this.label, { super.key, this.color = AppColors.primary, this.inkColor, }); final String label; /// 底色基色(淡染 8% 后作背景)。 final Color color; /// 文字色;缺省按 [color] 查内置深变体映射。 final Color? inkColor; /// 内置深变体映射(Color 重载 == 不能作 const map 键,用查表函数)。 static Color _defaultInkFor(Color color) { if (color == AppColors.primary) return AppColors.primaryDark; if (color == AppColors.success) return AppColors.successInk; if (color == AppColors.accent) return AppColors.accentDark; if (color == AppColors.error) return AppColors.errorDark; return AppColors.ink; } @override Widget build(BuildContext context) { final effectiveInk = inkColor ?? _defaultInkFor(color); return DecoratedBox( decoration: BoxDecoration( color: color.withAlpha(20), borderRadius: BorderRadius.circular(99), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), child: Text( label, style: TextStyle( color: effectiveInk, fontSize: 11, fontWeight: FontWeight.w700, ), ), ), ); } } class EmptyState extends StatelessWidget { const EmptyState({required this.message, super.key}); final String message; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 48), child: Column( children: [ const Icon( Icons.search_off_rounded, size: 42, color: AppColors.muted, ), const SizedBox(height: 12), Text(message, style: Theme.of(context).textTheme.bodySmall), ], ), ); } }