import 'package:flutter/material.dart'; /// 强宝健康主页上的一个卡片外壳。 /// /// 三个卡片(减肥进度 / 当前体重 / 今日饮食)结构完全一样: /// 标题 + 右上角可选的附属信息 + 内容区 + 底部按钮。抽出来是为了 /// **高度和间距自动一致** —— 各自手写一遍迟早会差几个像素。 class HealthCard extends StatelessWidget { const HealthCard({ super.key, required this.title, required this.child, this.trailing, this.onTap, this.action, }); final String title; /// 右上角的小字,如「已减 1.8kg」 final Widget? trailing; final Widget child; /// 点卡片整体的行为(一般是进详情页) final VoidCallback? onTap; /// 底部按钮(如「+ 记一笔」)。为空时不占高度。 final Widget? action; @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Container( width: double.infinity, padding: const EdgeInsets.fromLTRB(16, 14, 16, 14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: const Color(0xFF5D4037).withValues(alpha: 0.07), blurRadius: 10, offset: const Offset(0, 3), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF5D4037), ), ), ), if (trailing != null) // Flexible:右侧小字是「锦上添花」,空间不够时它该先让位, // 而不是把标题挤到溢出 Flexible(child: trailing!), if (onTap != null) const Padding( padding: EdgeInsets.only(left: 2), child: Icon(Icons.chevron_right, size: 18, color: Color(0xFFBCAAA4)), ), ], ), const SizedBox(height: 12), child, if (action != null) ...[ const SizedBox(height: 12), action!, ], ], ), ), ); } } /// 一条横向的进度条(减肥进度 / 时间进度共用) /// /// [value] 会被夹到 0~1:后端虽然已经 Clamp 过,但这里再兜一次, /// 免得某天后端改了什么导致宽度溢出。 class HealthProgressBar extends StatelessWidget { const HealthProgressBar({ super.key, required this.value, this.color = const Color(0xFFC8956E), this.height = 8, this.background = const Color(0xFFF0EAE4), }); final double value; final Color color; final double height; final Color background; @override Widget build(BuildContext context) { final safe = value.isFinite ? value.clamp(0.0, 1.0) : 0.0; return ClipRRect( borderRadius: BorderRadius.circular(height / 2), child: LinearProgressIndicator( value: safe, minHeight: height, backgroundColor: background, valueColor: AlwaysStoppedAnimation(color), ), ); } } /// 一个「标签 + 数值」的小块,卡片里成排用 class HealthStat extends StatelessWidget { const HealthStat({ super.key, required this.label, required this.value, this.unit, this.valueColor = const Color(0xFF5D4037), }); final String label; final String value; final String? unit; final Color valueColor; @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle(fontSize: 11.5, color: Color(0xFF8D6E63))), const SizedBox(height: 3), Row( crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [ Text( value, style: TextStyle( fontSize: 17, fontWeight: FontWeight.bold, color: valueColor, ), ), if (unit != null) ...[ const SizedBox(width: 2), Text(unit!, style: const TextStyle( fontSize: 11.5, color: Color(0xFF8D6E63))), ], ], ), ], ); } }