Files
kitchen/lib/src/pages/tools/health/weight_manage_page.dart
Developer 236bffb1bc 重构4
2026-04-19 04:26:55 +08:00

1005 lines
37 KiB
Dart
Raw 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.
/*
* 文件: weight_manage_page.dart
* 名称: 体重管理页面
* 作用: 记录体重、折线图趋势、目标设定、周期统计、BMI跳转
* 更新: 2026-04-16 初始创建
*/
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:mom_kitchen/src/config/design_tokens.dart';
import 'package:mom_kitchen/src/controllers/tools/weight_controller.dart';
import 'package:mom_kitchen/src/models/user/weight_record_model.dart';
import 'package:mom_kitchen/src/widgets/charts_widgets.dart';
class WeightManagePage extends GetView<WeightController> {
const WeightManagePage({super.key});
@override
Widget build(BuildContext context) {
final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark;
return CupertinoPageScaffold(
backgroundColor: isDark
? DarkDesignTokens.background
: DesignTokens.background,
navigationBar: CupertinoNavigationBar(
middle: Text(
'⚖️ 体重管理',
style: TextStyle(
fontSize: DesignTokens.fontLg,
fontWeight: FontWeight.w600,
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
),
),
trailing: CupertinoButton(
padding: EdgeInsets.zero,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: DesignTokens.space2,
vertical: DesignTokens.space1,
),
decoration: BoxDecoration(
color: DesignTokens.dynamicPrimary.withValues(alpha: 0.1),
borderRadius: DesignTokens.borderRadiusSm,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
CupertinoIcons.chart_bar,
size: 14,
color: DesignTokens.dynamicPrimary,
),
const SizedBox(width: 4),
Text(
'BMI',
style: TextStyle(
fontSize: DesignTokens.fontSm,
fontWeight: FontWeight.w600,
color: DesignTokens.dynamicPrimary,
),
),
],
),
),
onPressed: () => Get.toNamed('/bmi-calculator'),
),
backgroundColor: isDark
? DarkDesignTokens.background.withValues(alpha: 0.9)
: DesignTokens.background.withValues(alpha: 0.9),
border: null,
),
child: SafeArea(
child: Obx(
() => SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(DesignTokens.space4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStatCards(isDark),
const SizedBox(height: DesignTokens.space4),
_buildRecordForm(isDark),
const SizedBox(height: DesignTokens.space4),
_buildChartSection(isDark),
const SizedBox(height: DesignTokens.space4),
_buildStatsSection(isDark),
const SizedBox(height: DesignTokens.space4),
_buildGoalSection(isDark),
const SizedBox(height: DesignTokens.space4),
_buildHistoryList(isDark),
const SizedBox(height: DesignTokens.space6),
],
),
),
),
),
);
}
Widget _buildStatCards(bool isDark) {
return Row(
children: [
Expanded(
child: _statCard(
'当前体重',
controller.currentWeight != null
? controller.displayWeight(controller.currentWeight!)
: '--',
isDark,
DesignTokens.dynamicPrimary,
),
),
const SizedBox(width: DesignTokens.space2),
Expanded(
child: _statCard(
'目标体重',
'${controller.goalWeight.value.toStringAsFixed(1)} kg',
isDark,
DesignTokens.orange,
),
),
const SizedBox(width: DesignTokens.space2),
Expanded(
child: Obx(() {
final change = controller.changeFromLast;
String label = '--';
Color color = DesignTokens.text3;
if (change != null) {
final absChange = change.abs();
label =
'${change > 0 ? '' : ''}${absChange.toStringAsFixed(1)}kg';
color = change > 0 ? DesignTokens.red : DesignTokens.green;
}
return _statCard('变化', label, isDark, color);
}),
),
],
);
}
Widget _statCard(String title, String value, bool isDark, Color accentColor) {
return Container(
padding: const EdgeInsets.all(DesignTokens.space3),
decoration: BoxDecoration(
color: isDark ? DarkDesignTokens.card : DesignTokens.card,
borderRadius: DesignTokens.borderRadiusMd,
border: Border.all(color: accentColor.withValues(alpha: 0.2)),
),
child: Column(
children: [
Text(
title,
style: TextStyle(
fontSize: DesignTokens.fontXs,
color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
),
),
const SizedBox(height: DesignTokens.space1),
Text(
value,
style: TextStyle(
fontSize: DesignTokens.fontLg,
fontWeight: FontWeight.w700,
color: accentColor,
),
),
],
),
);
}
Widget _buildChartSection(bool isDark) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(DesignTokens.space4),
decoration: BoxDecoration(
color: isDark ? DarkDesignTokens.card : DesignTokens.card,
borderRadius: DesignTokens.borderRadiusLg,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text('📈 ', style: TextStyle(fontSize: DesignTokens.fontMd)),
Text(
'体重趋势',
style: TextStyle(
fontSize: DesignTokens.fontMd,
fontWeight: FontWeight.w600,
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
),
),
const Spacer(),
Obx(
() => GestureDetector(
onTap: () => controller.toggleUnit(),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: DesignTokens.space2,
vertical: DesignTokens.space1,
),
decoration: BoxDecoration(
color: DesignTokens.dynamicPrimary.withValues(alpha: 0.1),
borderRadius: DesignTokens.borderRadiusFull,
),
child: Text(
controller.unitMode.value == 'kg' ? 'kg ⇄ 斤' : '斤 ⇄ kg',
style: TextStyle(
fontSize: DesignTokens.fontXs,
fontWeight: FontWeight.w600,
color: DesignTokens.dynamicPrimary,
),
),
),
),
),
],
),
const SizedBox(height: DesignTokens.space3),
Obx(
() => WeightLineChart(
data: controller.chartData,
goalValue: controller.goalWeight.value,
isDark: isDark,
unitLabel: controller.unitMode.value == 'kg' ? 'kg' : '',
),
),
],
),
);
}
Widget _buildStatsSection(bool isDark) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(DesignTokens.space4),
decoration: BoxDecoration(
color: isDark ? DarkDesignTokens.card : DesignTokens.card,
borderRadius: DesignTokens.borderRadiusLg,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'📊 趋势分析',
style: TextStyle(
fontSize: DesignTokens.fontMd,
fontWeight: FontWeight.w600,
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
),
),
const SizedBox(height: DesignTokens.space3),
Obx(() {
final weekly = controller.getWeeklyStats();
final monthly = controller.getMonthlyStats();
return _statsGrid(weekly, monthly, isDark);
}),
],
),
);
}
Widget _statsGrid(
Map<String, dynamic> weekly,
Map<String, dynamic> monthly,
bool isDark,
) {
final items = [
_StatsItem('本周平均', weekly['avg'], 'kg'),
_StatsItem('本周最高', weekly['max'], 'kg'),
_StatsItem('本周最低', weekly['min'], 'kg'),
_StatsItem('本周变化', weekly['change'], 'kg', showSign: true),
_StatsItem('本月平均', monthly['avg'], 'kg'),
_StatsItem('本月最高', monthly['max'], 'kg'),
_StatsItem('本月最低', monthly['min'], 'kg'),
_StatsItem('本月变化', monthly['change'], 'kg', showSign: true),
];
return Wrap(
spacing: DesignTokens.space2,
runSpacing: DesignTokens.space2,
children: items.map((item) {
final val = item.value;
final text = item.showSign
? (val >= 0 ? '+$val' : '$val')
: val.toStringAsFixed(1);
final color = item.showSign
? (val <= 0 ? DesignTokens.green : DesignTokens.red)
: (isDark ? DarkDesignTokens.text1 : DesignTokens.text1);
return SizedBox(
width:
(Get.width - DesignTokens.space4 * 2 - DesignTokens.space2 * 3) /
4,
child: Container(
padding: const EdgeInsets.all(DesignTokens.space2),
decoration: BoxDecoration(
color: isDark
? DarkDesignTokens.glass
: DesignTokens.text3.withValues(alpha: 0.04),
borderRadius: DesignTokens.borderRadiusSm,
),
child: Column(
children: [
Text(
item.label,
style: TextStyle(
fontSize: 10,
color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
),
),
const SizedBox(height: 2),
Text(
'$text ${item.unit}',
style: TextStyle(
fontSize: DesignTokens.fontSm,
fontWeight: FontWeight.w700,
color: color,
),
),
],
),
),
);
}).toList(),
);
}
Widget _buildGoalSection(bool isDark) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(DesignTokens.space4),
decoration: BoxDecoration(
color: isDark ? DarkDesignTokens.card : DesignTokens.card,
borderRadius: DesignTokens.borderRadiusLg,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text('⚙️ ', style: TextStyle(fontSize: DesignTokens.fontMd)),
Text(
'目标体重',
style: TextStyle(
fontSize: DesignTokens.fontMd,
fontWeight: FontWeight.w600,
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
),
),
const Spacer(),
Obx(
() => Text(
'${controller.goalWeight.value.toStringAsFixed(1)} kg',
style: TextStyle(
fontSize: DesignTokens.fontMd,
fontWeight: FontWeight.w700,
color: DesignTokens.orange,
),
),
),
],
),
const SizedBox(height: DesignTokens.space3),
Obx(
() => CupertinoSlider(
value: controller.goalWeight.value,
min: 30,
max: 200,
divisions: 170,
onChanged: (v) => controller.updateGoal(v),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: DesignTokens.space2,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'30kg',
style: TextStyle(
fontSize: DesignTokens.fontXs,
color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
),
),
Text(
'200kg',
style: TextStyle(
fontSize: DesignTokens.fontXs,
color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
),
),
],
),
),
],
),
);
}
Widget _buildRecordForm(bool isDark) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(DesignTokens.space4),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
DesignTokens.dynamicPrimary.withValues(alpha: 0.08),
DesignTokens.secondary.withValues(alpha: 0.06),
],
),
borderRadius: DesignTokens.borderRadiusLg,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
' 记录体重',
style: TextStyle(
fontSize: DesignTokens.fontMd,
fontWeight: FontWeight.w600,
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
),
),
const SizedBox(height: DesignTokens.space3),
Row(
children: [
Expanded(
flex: 3,
child: Container(
height: 44,
decoration: BoxDecoration(
color: isDark
? DarkDesignTokens.glass
: CupertinoColors.white,
borderRadius: DesignTokens.borderRadiusMd,
border: Border.all(
color: isDark
? DarkDesignTokens.glassBorder
: DesignTokens.text3.withValues(alpha: 0.15),
),
),
child: CupertinoTextField(
controller: controller.weightInput,
placeholder: '输入体重',
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
placeholderStyle: TextStyle(
fontSize: DesignTokens.fontMd,
color: isDark
? DarkDesignTokens.text3
: DesignTokens.text3,
),
style: TextStyle(
fontSize: DesignTokens.fontMd,
color: isDark
? DarkDesignTokens.text1
: DesignTokens.text1,
),
decoration: null,
),
),
),
const SizedBox(width: DesignTokens.space2),
Expanded(
child: Obx(
() => GestureDetector(
onTap: () {
if (controller.unitMode.value != 'kg') {
controller.toggleUnit();
}
},
child: Container(
height: 44,
decoration: BoxDecoration(
color: controller.unitMode.value == 'kg'
? DesignTokens.dynamicPrimary
: (isDark
? DarkDesignTokens.glass
: CupertinoColors.white),
borderRadius: DesignTokens.borderRadiusMd,
border: controller.unitMode.value != 'kg'
? Border.all(
color: isDark
? DarkDesignTokens.glassBorder
: DesignTokens.text3.withValues(
alpha: 0.15,
),
)
: null,
),
child: Center(
child: Text(
'kg',
style: TextStyle(
fontSize: DesignTokens.fontSm,
fontWeight: FontWeight.w600,
color: controller.unitMode.value == 'kg'
? CupertinoColors.white
: (isDark
? DarkDesignTokens.text2
: DesignTokens.text2),
),
),
),
),
),
),
),
const SizedBox(width: DesignTokens.space2),
Expanded(
child: Obx(
() => GestureDetector(
onTap: () {
if (controller.unitMode.value != 'jin') {
controller.toggleUnit();
}
},
child: Container(
height: 44,
decoration: BoxDecoration(
color: controller.unitMode.value == 'jin'
? DesignTokens.dynamicPrimary
: (isDark
? DarkDesignTokens.glass
: CupertinoColors.white),
borderRadius: DesignTokens.borderRadiusMd,
border: controller.unitMode.value != 'jin'
? Border.all(
color: isDark
? DarkDesignTokens.glassBorder
: DesignTokens.text3.withValues(
alpha: 0.15,
),
)
: null,
),
child: Center(
child: Text(
'',
style: TextStyle(
fontSize: DesignTokens.fontSm,
fontWeight: FontWeight.w600,
color: controller.unitMode.value == 'jin'
? CupertinoColors.white
: (isDark
? DarkDesignTokens.text2
: DesignTokens.text2),
),
),
),
),
),
),
),
],
),
const SizedBox(height: DesignTokens.space3),
Obx(
() => CupertinoSlidingSegmentedControl<String>(
groupValue: controller.selectedTiming.value,
children: {
'morning': Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text('🌅早晨'),
),
'before_meal': Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text('🍽️饭前'),
),
'after_meal': Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text('😋饭后'),
),
},
onValueChanged: (v) => controller.selectedTiming.value = v!,
),
),
const SizedBox(height: DesignTokens.space3),
Container(
height: 44,
decoration: BoxDecoration(
color: isDark ? DarkDesignTokens.glass : CupertinoColors.white,
borderRadius: DesignTokens.borderRadiusMd,
border: Border.all(
color: isDark
? DarkDesignTokens.glassBorder
: DesignTokens.text3.withValues(alpha: 0.15),
),
),
child: CupertinoTextField(
controller: controller.noteInput,
placeholder: '备注信息(可选)',
placeholderStyle: TextStyle(
fontSize: DesignTokens.fontMd,
color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
),
style: TextStyle(
fontSize: DesignTokens.fontMd,
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
),
decoration: null,
),
),
const SizedBox(height: DesignTokens.space3),
SizedBox(
width: double.infinity,
height: 44,
child: CupertinoButton.filled(
borderRadius: DesignTokens.borderRadiusMd,
onPressed: () => controller.addRecord(),
child: const Text('💾 保存记录'),
),
),
],
),
);
}
Widget _buildHistoryList(bool isDark) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'📋 历史记录',
style: TextStyle(
fontSize: DesignTokens.fontMd,
fontWeight: FontWeight.w600,
color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1,
),
),
const Spacer(),
Obx(
() => Text(
'${controller.records.length}',
style: TextStyle(
fontSize: DesignTokens.fontXs,
color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3,
),
),
),
],
),
const SizedBox(height: DesignTokens.space2),
Obx(() {
if (controller.records.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(DesignTokens.space6),
child: Column(
children: [
const Text('📝', style: TextStyle(fontSize: 40)),
const SizedBox(height: DesignTokens.space2),
Text(
'还没有记录',
style: TextStyle(
fontSize: DesignTokens.fontMd,
color: isDark
? DarkDesignTokens.text2
: DesignTokens.text2,
),
),
Text(
'添加第一条体重记录开始追踪',
style: TextStyle(
fontSize: DesignTokens.fontSm,
color: isDark
? DarkDesignTokens.text3
: DesignTokens.text3,
),
),
],
),
),
);
}
return Column(
children: controller.sortedRecords.take(20).map((record) {
return Dismissible(
key: ValueKey(record.id),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: DesignTokens.space4),
decoration: BoxDecoration(
color: DesignTokens.red,
borderRadius: DesignTokens.borderRadiusMd,
),
child: const Icon(
CupertinoIcons.trash,
color: CupertinoColors.white,
),
),
confirmDismiss: (direction) async {
return await showCupertinoDialog<bool>(
context: Get.context!,
builder: (ctx) => CupertinoAlertDialog(
title: const Text('确认删除'),
content: Text(
'删除 ${record.createdAt.toString().substring(5, 16)} 的记录?',
),
actions: [
CupertinoDialogAction(
child: const Text('取消'),
onPressed: () => Navigator.pop(ctx, false),
),
CupertinoDialogAction(
isDestructiveAction: true,
child: const Text('删除'),
onPressed: () => Navigator.pop(ctx, true),
),
],
),
) ??
false;
},
onDismissed: (_) => controller.deleteRecord(record.id),
child: GestureDetector(
onTap: () => _showEditDialog(Get.context!, record, isDark),
child: Container(
margin: const EdgeInsets.only(bottom: DesignTokens.space2),
padding: const EdgeInsets.all(DesignTokens.space3),
decoration: BoxDecoration(
color: isDark ? DarkDesignTokens.card : DesignTokens.card,
borderRadius: DesignTokens.borderRadiusMd,
),
child: Row(
children: [
Text(
WeightRecord.timingEmoji[record.timing] ?? '📝',
style: const TextStyle(fontSize: 20),
),
const SizedBox(width: DesignTokens.space2),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
record.createdAt.toString().substring(0, 16),
style: TextStyle(
fontSize: DesignTokens.fontXs,
color: isDark
? DarkDesignTokens.text3
: DesignTokens.text3,
),
),
Text(
WeightRecord.timingDisplay[record.timing] ?? '',
style: TextStyle(
fontSize: DesignTokens.fontSm,
color: isDark
? DarkDesignTokens.text2
: DesignTokens.text2,
),
),
],
),
),
Text(
controller.displayWeight(record.weightKg),
style: TextStyle(
fontSize: DesignTokens.fontLg,
fontWeight: FontWeight.w700,
color: isDark
? DarkDesignTokens.text1
: DesignTokens.text1,
),
),
const SizedBox(width: DesignTokens.space1),
Icon(
CupertinoIcons.pencil_outline,
size: 14,
color: isDark
? DarkDesignTokens.text3
: DesignTokens.text3,
),
],
),
),
),
);
}).toList(),
);
}),
],
);
}
void _showEditDialog(BuildContext context, WeightRecord record, bool isDark) {
final weightCtrl = TextEditingController(
text: controller.unitMode.value == 'kg'
? record.weightKg.toStringAsFixed(1)
: WeightRecord.kgToJin(record.weightKg).toStringAsFixed(1),
);
final noteCtrl = TextEditingController(text: record.note ?? '');
String editTiming = record.timing;
String editUnit = controller.unitMode.value;
showCupertinoDialog(
context: context,
builder: (ctx) => StatefulBuilder(
builder: (context, setDialogState) {
return CupertinoAlertDialog(
title: const Text('✏️ 编辑记录'),
content: Container(
width: 280,
constraints: const BoxConstraints(maxHeight: 320),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
flex: 3,
child: Container(
height: 36,
decoration: BoxDecoration(
color: isDark
? DarkDesignTokens.glass
: CupertinoColors.white,
borderRadius: DesignTokens.borderRadiusSm,
border: Border.all(
color: DesignTokens.dynamicPrimary.withValues(
alpha: 0.3,
),
),
),
child: CupertinoTextField(
controller: weightCtrl,
keyboardType:
const TextInputType.numberWithOptions(
decimal: true,
),
style: TextStyle(
fontSize: DesignTokens.fontSm,
color: isDark
? DarkDesignTokens.text1
: DesignTokens.text1,
),
decoration: null,
),
),
),
const SizedBox(width: DesignTokens.space2),
GestureDetector(
onTap: () {
setDialogState(() {
final val = double.tryParse(weightCtrl.text) ?? 0;
if (editUnit == 'kg') {
weightCtrl.text = WeightRecord.kgToJin(
val,
).toStringAsFixed(1);
editUnit = 'jin';
} else {
weightCtrl.text = WeightRecord.jinToKg(
val,
).toStringAsFixed(1);
editUnit = 'kg';
}
});
},
child: Container(
height: 36,
padding: const EdgeInsets.symmetric(
horizontal: DesignTokens.space2,
),
decoration: BoxDecoration(
color: DesignTokens.dynamicPrimary.withValues(
alpha: 0.1,
),
borderRadius: DesignTokens.borderRadiusSm,
),
child: Center(
child: Text(
editUnit.toUpperCase(),
style: TextStyle(
fontSize: DesignTokens.fontXs,
fontWeight: FontWeight.w600,
color: DesignTokens.dynamicPrimary,
),
),
),
),
),
],
),
const SizedBox(height: DesignTokens.space2),
CupertinoSlidingSegmentedControl<String>(
groupValue: editTiming,
children: {
'morning': Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Text(
'🌅早晨',
style: const TextStyle(fontSize: 12),
),
),
'before_meal': Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Text(
'🍽️饭前',
style: const TextStyle(fontSize: 12),
),
),
'after_meal': Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Text(
'😋饭后',
style: const TextStyle(fontSize: 12),
),
),
},
onValueChanged: (v) {
setDialogState(() {
editTiming = v!;
});
},
),
const SizedBox(height: DesignTokens.space2),
Container(
height: 36,
decoration: BoxDecoration(
color: isDark
? DarkDesignTokens.glass
: CupertinoColors.white,
borderRadius: DesignTokens.borderRadiusSm,
border: Border.all(
color: DesignTokens.text3.withValues(alpha: 0.15),
),
),
child: CupertinoTextField(
controller: noteCtrl,
placeholder: '备注(可选)',
style: TextStyle(
fontSize: DesignTokens.fontSm,
color: isDark
? DarkDesignTokens.text1
: DesignTokens.text1,
),
decoration: null,
),
),
],
),
),
),
actions: [
CupertinoDialogAction(
child: const Text('取消'),
onPressed: () {
weightCtrl.dispose();
noteCtrl.dispose();
Navigator.pop(ctx);
},
),
CupertinoDialogAction(
isDefaultAction: true,
child: const Text('保存'),
onPressed: () {
final w = double.tryParse(weightCtrl.text);
if (w == null || w <= 0 || w > 500) {
Navigator.pop(ctx);
weightCtrl.dispose();
noteCtrl.dispose();
return;
}
final kg = editUnit == 'jin' ? WeightRecord.jinToKg(w) : w;
controller.updateRecord(
record.id,
weightKg: kg,
timing: editTiming,
note: noteCtrl.text.trim().isEmpty
? null
: noteCtrl.text.trim(),
);
weightCtrl.dispose();
noteCtrl.dispose();
Navigator.pop(ctx);
},
),
],
);
},
),
);
}
}
class _StatsItem {
final String label;
final double value;
final String unit;
final bool showSign;
const _StatsItem(this.label, this.value, this.unit, {this.showSign = false});
}