fix: 用药模块重做 + 运动计划不限7天 + 打卡可切换 + 删除修复

- 用药: 重写列表页(标签筛选+详情弹窗+打卡切换+滑动删除)
- 用药: 重写编辑页(选择器替代输入+同列布局+频率改为每天几次)
- 用药: 加Notes字段+DELETE端点+修复重复端点
- 运动: 不限7天, startDate+索引推算当天
- 打卡: 药/运动打卡可切换(点✓取消)
- 删除: 修复滑动删除热区, 一次点击即删除
- 侧边栏: 一键清理历史对话
- 饮食/运动/用药/问诊 API路径加/api/前缀
- 通知时机修正: 新建计划不再提前弹窗
This commit is contained in:
MingNian
2026-06-04 21:15:23 +08:00
parent b944a31983
commit cd65bc4d65
11 changed files with 1069 additions and 1242 deletions

View File

@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import '../../core/navigation_provider.dart';
import '../../core/app_theme.dart';
import '../../providers/auth_provider.dart';
import '../../utils/sse_handler.dart';
@@ -164,6 +165,11 @@ class DietNotifier extends Notifier<DietState> {
state = state.copyWith(foods: foods);
}
void updateFoodPortion(String id, String portion) {
final foods = state.foods.map((f) => f.id == id ? FoodItem(id: f.id, name: f.name, portion: portion, calories: f.calories, selected: f.selected) : f).toList();
state = state.copyWith(foods: foods);
}
void updateFoodCalories(String id, int calories) {
final foods = state.foods.map((f) => f.id == id ? FoodItem(id: f.id, name: f.name, portion: f.portion, calories: calories, selected: f.selected) : f).toList();
state = state.copyWith(foods: foods);
@@ -304,300 +310,425 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
}
}
// ─────────── 设计常量(与项目整体色调一致)───────────
static const _kPrimary = AppTheme.primary; // #6C5CE7
static const _kPrimaryLight = AppTheme.primaryLight; // #EDEAFF
static const _kPageBg = AppTheme.bg; // #F8F9FC
static const _kSurface = AppTheme.surface; // white
static const _kText = AppTheme.text; // #2D2B32
static const _kSubText = AppTheme.textSub; // #8A8892
static const _kBorder = AppTheme.border; // #EAEAF0
static const _kWarning = AppTheme.warning; // #F5A623
Widget _buildResultView(BuildContext context, WidgetRef ref) {
final state = ref.watch(dietProvider);
final totalCalories = state.foods.where((f) => f.selected).fold(0, (sum, f) => sum + f.calories);
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(children: [
_buildImagePreview(state.imagePath!),
const SizedBox(height: 16),
_buildMealSelector(context, ref),
const SizedBox(height: 16),
if (state.isAnalyzing) _buildAnalyzingIndicator(state) else ...[
_buildFoodList(context, ref),
if (state.foods.isNotEmpty) ...[
const SizedBox(height: 16),
_buildNutritionSummary(totalCalories),
if (state.commentary != null && state.commentary!.isNotEmpty) ...[
return Container(
color: _kPageBg,
child: SingleChildScrollView(
child: Column(children: [
_buildImagePreview(state.imagePath!),
const SizedBox(height: 20),
_buildMealSelector(ref),
const SizedBox(height: 16),
if (state.isAnalyzing)
_buildAnalyzingIndicator(state)
else ...[
if (state.foods.isNotEmpty) ...[
_buildFoodList(ref),
const SizedBox(height: 16),
_buildAiCommentary(state.commentary!),
_buildNutritionSummary(totalCalories),
if (state.commentary != null && state.commentary!.isNotEmpty) ...[
const SizedBox(height: 16),
_buildAiCommentary(state.commentary!),
],
],
const SizedBox(height: 24),
_buildSubmitButton(context, ref),
const SizedBox(height: 16),
],
],
]),
);
}
Widget _buildImagePreview(String path) {
return Container(
height: 200,
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(20),
image: DecorationImage(image: FileImage(File(path)), fit: BoxFit.cover),
]),
),
);
}
Widget _buildMealSelector(BuildContext context, WidgetRef ref) {
final state = ref.watch(dietProvider);
final meals = [
{'type': 'breakfast', 'label': '早餐', 'icon': '🌅'},
{'type': 'lunch', 'label': '午餐', 'icon': '☀️'},
{'type': 'dinner', 'label': '晚餐', 'icon': '🌙'},
{'type': 'snack', 'label': '加餐', 'icon': '🍪'},
];
return Column(children: [
const Text('选择餐次', style: TextStyle(fontSize: 14, color: Color(0xFF666666))),
const SizedBox(height: 12),
Row(children: meals.map((meal) {
final isSelected = state.mealType == meal['type'];
return Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: ElevatedButton(
onPressed: () => ref.read(dietProvider.notifier).setMealType(meal['type']!),
child: Column(children: [
Text(meal['icon']!, style: const TextStyle(fontSize: 20)),
const SizedBox(height: 4),
Text(meal['label']!, style: TextStyle(fontSize: 12, color: isSelected ? Colors.white : const Color(0xFF8B9CF7))),
]),
style: ElevatedButton.styleFrom(
backgroundColor: isSelected ? const Color(0xFF8B9CF7) : const Color(0xFFF0F2FF),
foregroundColor: isSelected ? Colors.white : const Color(0xFF8B9CF7),
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
padding: const EdgeInsets.symmetric(vertical: 12),
// ─────────── 图片预览 ───────────
Widget _buildImagePreview(String path) {
return Stack(
children: [
Container(
height: 220,
decoration: BoxDecoration(
color: const Color(0xFFE0E0E0),
image: DecorationImage(image: FileImage(File(path)), fit: BoxFit.cover),
),
),
Positioned(
left: 0, right: 0, bottom: 0,
child: Container(
height: 60,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.transparent, Color(0x40000000)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
);
}).toList()),
),
],
);
}
// ─────────── 餐次选择器 ───────────
Widget _buildMealSelector(WidgetRef ref) {
final state = ref.watch(dietProvider);
final meals = [
_MealData(Icons.wb_sunny_outlined, '早餐', 'breakfast'),
_MealData(Icons.wb_cloudy_outlined, '午餐', 'lunch'),
_MealData(Icons.nightlight_round, '晚餐', 'dinner'),
_MealData(Icons.local_cafe_outlined, '加餐', 'snack'),
];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: meals.map((m) {
final isSelected = state.mealType == m.type;
return Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: GestureDetector(
onTap: () => ref.read(dietProvider.notifier).setMealType(m.type),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: isSelected ? _kPrimary : _kSurface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: isSelected ? _kPrimary : _kBorder),
boxShadow: isSelected
? [BoxShadow(color: _kPrimary.withAlpha(30), blurRadius: 6, offset: const Offset(0, 2))]
: null,
),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Icon(m.icon, size: 20, color: isSelected ? Colors.white : _kSubText),
const SizedBox(height: 4),
Text(m.label, style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: isSelected ? Colors.white : _kSubText)),
]),
),
),
),
);
}).toList(),
),
);
}
// ─────────── 分析中指示器 ───────────
Widget _buildAnalyzingIndicator(DietState state) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 40),
child: Center(
child: Column(children: [
SizedBox(
width: 56, height: 56,
child: Stack(
alignment: Alignment.center,
children: [
const CircularProgressIndicator(strokeWidth: 3, color: _kPrimary),
const Icon(Icons.restaurant, size: 22, color: _kPrimary),
],
),
),
const SizedBox(height: 20),
const Text('正在识别食物...', style: TextStyle(fontSize: 15, color: _kSubText)),
if (state.errorMessage != null) ...[
const SizedBox(height: 8),
Text(state.errorMessage!, style: const TextStyle(fontSize: 13, color: AppTheme.error), textAlign: TextAlign.center),
],
]),
),
);
}
// ─────────── 食物列表 ───────────
Widget _buildFoodList(WidgetRef ref) {
final state = ref.watch(dietProvider);
final totalCal = state.foods.where((f) => f.selected).fold(0, (s, f) => s + f.calories);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
decoration: BoxDecoration(
color: _kSurface,
borderRadius: BorderRadius.circular(16),
boxShadow: [AppTheme.shadowLight],
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 14, 8, 0),
child: Row(children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(color: _kPrimaryLight, borderRadius: BorderRadius.circular(8)),
child: const Text('识别结果', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: _kPrimary)),
),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(color: const Color(0xFFFFF8EE), borderRadius: BorderRadius.circular(8)),
child: Text('$totalCal kcal', style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: _kWarning)),
),
IconButton(
icon: const Icon(Icons.add_circle_outline, size: 22, color: _kPrimary),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () => ref.read(dietProvider.notifier).addFood(),
),
]),
),
const SizedBox(height: 8),
...state.foods.map((food) => _buildFoodItem(ref, food)),
const SizedBox(height: 12),
]),
),
);
}
Widget _buildFoodItem(WidgetRef ref, FoodItem food) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
color: food.selected ? const Color(0xFFF6F4FF) : const Color(0xFFFAFAFA),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: food.selected ? _kPrimary.withAlpha(30) : const Color(0xFFEEEEEE)),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 6, 10),
child: Row(children: [
GestureDetector(
onTap: () => ref.read(dietProvider.notifier).toggleFood(food.id),
child: Container(
width: 22, height: 22,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: food.selected ? _kPrimary : Colors.white,
border: Border.all(color: food.selected ? _kPrimary : const Color(0xFFCCCCCC), width: 2),
),
child: food.selected ? const Icon(Icons.check, size: 14, color: Colors.white) : null,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
TextField(
controller: TextEditingController(text: food.name),
onChanged: (v) => ref.read(dietProvider.notifier).updateFoodName(food.id, v),
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: _kText),
decoration: const InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
),
),
const SizedBox(height: 2),
Row(children: [
Expanded(
flex: 3,
child: TextField(
controller: TextEditingController(text: food.portion),
onChanged: (v) => ref.read(dietProvider.notifier).updateFoodPortion(food.id, v),
style: const TextStyle(fontSize: 12, color: _kSubText),
decoration: const InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
hintText: '份量',
hintStyle: TextStyle(fontSize: 12, color: AppTheme.textHint),
),
),
),
const SizedBox(width: 8),
Expanded(
flex: 2,
child: TextField(
controller: TextEditingController(text: food.calories.toString()),
onChanged: (v) => ref.read(dietProvider.notifier).updateFoodCalories(food.id, int.tryParse(v) ?? 0),
keyboardType: TextInputType.number,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: _kWarning),
textAlign: TextAlign.right,
decoration: const InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
hintText: '0',
hintStyle: TextStyle(fontSize: 12),
),
),
),
const SizedBox(width: 2),
const Text('kcal', style: TextStyle(fontSize: 11, color: _kSubText)),
]),
]),
),
IconButton(
icon: const Icon(Icons.close, size: 16, color: Color(0xFFBBBBBB)),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () => ref.read(dietProvider.notifier).removeFood(food.id),
),
]),
),
),
);
}
// ─────────── 营养摘要 ───────────
Widget _buildNutritionSummary(int totalCalories) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: const LinearGradient(colors: [_kPrimary, Color(0xFF8B7CF0)], begin: Alignment.topLeft, end: Alignment.bottomRight),
borderRadius: BorderRadius.circular(16),
boxShadow: [BoxShadow(color: _kPrimary.withAlpha(40), blurRadius: 10, offset: const Offset(0, 4))],
),
child: Row(children: [
SizedBox(
width: 56, height: 56,
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 56, height: 56,
child: CircularProgressIndicator(
value: (totalCalories / 700).clamp(0.0, 1.0),
strokeWidth: 4,
backgroundColor: Colors.white24,
color: Colors.white,
),
),
Column(mainAxisSize: MainAxisSize.min, children: [
Text('$totalCalories', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: Colors.white)),
Text('kcal', style: const TextStyle(fontSize: 10, color: Colors.white70)),
]),
],
),
),
const SizedBox(width: 16),
Expanded(
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
const Text('本餐热量', style: TextStyle(fontSize: 14, color: Colors.white70)),
const SizedBox(height: 4),
Row(children: [
Expanded(child: _macroBar('碳水', 0.55, const Color(0xFFFFF9C4))),
const SizedBox(width: 8),
Expanded(child: _macroBar('蛋白', 0.25, const Color(0xFFD4C8FC))),
const SizedBox(width: 8),
Expanded(child: _macroBar('脂肪', 0.20, const Color(0xFFFFCDD2))),
]),
]),
),
]),
),
);
}
Widget _macroBar(String label, double ratio, Color color) {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Container(width: 6, height: 6, decoration: BoxDecoration(color: color, shape: BoxShape.circle)),
const SizedBox(width: 4),
Text(label, style: const TextStyle(fontSize: 10, color: Colors.white70)),
]),
const SizedBox(height: 3),
ClipRRect(
borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator(value: ratio, minHeight: 4, backgroundColor: Colors.white24, color: color),
),
]);
}
Widget _buildAnalyzingIndicator(DietState state) {
return Center(
child: Column(children: [
Container(
width: 60,
height: 60,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFEDEAFF),
borderRadius: BorderRadius.circular(30),
),
child: const CircularProgressIndicator(strokeWidth: 3, color: Color(0xFF6C5CE7)),
),
const SizedBox(height: 16),
const Text('AI 正在识别食物...', style: TextStyle(fontSize: 16, color: Color(0xFF666666))),
if (state.errorMessage != null) ...[
const SizedBox(height: 8),
Text(state.errorMessage!, style: const TextStyle(fontSize: 13, color: Color(0xFFE53935)), textAlign: TextAlign.center),
],
]),
);
}
Widget _buildFoodList(BuildContext context, WidgetRef ref) {
final state = ref.watch(dietProvider);
return Container(
decoration: BoxDecoration(
color: const Color(0xFFFEFEFF),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFFD8DCFD), width: 1.5),
),
child: Column(children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(children: [
const Text('🍽️', style: TextStyle(fontSize: 20)),
const SizedBox(width: 8),
const Text('识别结果', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
const Spacer(),
IconButton(
icon: const Icon(Icons.add, size: 20, color: Color(0xFF8B9CF7)),
onPressed: () => ref.read(dietProvider.notifier).addFood(),
),
]),
),
...state.foods.map((food) => _buildFoodItem(context, ref, food)),
]),
);
}
Widget _buildFoodItem(BuildContext context, WidgetRef ref, FoodItem food) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: food.selected ? const Color(0xFFF0F2FF) : const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(16),
),
child: Row(children: [
Checkbox(
value: food.selected,
onChanged: (v) => ref.read(dietProvider.notifier).toggleFood(food.id),
activeColor: const Color(0xFF8B9CF7),
),
const SizedBox(width: 8),
Expanded(
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
TextField(
decoration: const InputDecoration(border: InputBorder.none, hintText: '食物名称'),
controller: TextEditingController(text: food.name),
onChanged: (v) => ref.read(dietProvider.notifier).updateFoodName(food.id, v),
style: const TextStyle(fontSize: 16),
),
if (food.portion.isNotEmpty)
Text(food.portion, style: const TextStyle(fontSize: 12, color: Color(0xFF999999))),
Row(children: [
const Text('热量:', style: TextStyle(fontSize: 12, color: Color(0xFF999999))),
SizedBox(
width: 60,
child: TextField(
decoration: const InputDecoration(border: InputBorder.none, hintText: '0'),
controller: TextEditingController(text: food.calories.toString()),
keyboardType: TextInputType.number,
onChanged: (v) => ref.read(dietProvider.notifier).updateFoodCalories(food.id, int.tryParse(v) ?? 0),
style: TextStyle(fontSize: 12, color: const Color(0xFF8B9CF7)),
),
),
const Text('kcal', style: TextStyle(fontSize: 12, color: Color(0xFF999999))),
]),
]),
),
IconButton(
icon: const Icon(Icons.delete, size: 18, color: Color(0xFFCCCCCC)),
onPressed: () => ref.read(dietProvider.notifier).removeFood(food.id),
),
]),
);
}
Widget _buildNutritionSummary(int totalCalories) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFF0F2FF),
borderRadius: BorderRadius.circular(16),
),
child: Row(children: [
const Icon(Icons.fireplace, size: 28, color: Color(0xFFFF6B35)),
const SizedBox(width: 12),
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
const Text('总热量', style: TextStyle(fontSize: 14, color: Color(0xFF666666))),
Text('$totalCalories kcal', style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600)),
]),
const Spacer(),
Column(crossAxisAlignment: CrossAxisAlignment.end, children: [
const Text('推荐摄入量', style: TextStyle(fontSize: 12, color: Color(0xFF999999))),
const Text('午餐约 500-700 kcal', style: TextStyle(fontSize: 12, color: Color(0xFF999999))),
]),
]),
);
}
Widget _buildHealthScore(int score) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFFEFEFF),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFFD8DCFD), width: 1.5),
),
child: Column(children: [
const Text('🥗 健康评分', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(5, (i) => Icon(
Icons.star,
size: 36,
color: i < score ? const Color(0xFFFFB800) : Colors.grey[200],
)),
),
const SizedBox(height: 12),
Text(_getScoreComment(score), style: TextStyle(fontSize: 14, color: _getScoreColor(score))),
]),
);
}
// ─────────── AI 点评 ───────────
Widget _buildAiCommentary(String text) {
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
gradient: const LinearGradient(colors: [Color(0xFFEDEAFF), Color(0xFFF5F3FF)], begin: Alignment.topLeft, end: Alignment.bottomRight),
borderRadius: BorderRadius.circular(16),
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: _kSurface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: _kBorder),
),
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
Container(
width: 32, height: 32,
decoration: BoxDecoration(color: _kPrimaryLight, borderRadius: BorderRadius.circular(8)),
child: const Icon(Icons.auto_awesome, size: 16, color: _kPrimary),
),
const SizedBox(width: 10),
Expanded(child: Text(text, style: const TextStyle(fontSize: 13, color: _kText, height: 1.6))),
]),
),
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
Container(width: 32, height: 32, decoration: BoxDecoration(color: const Color(0xFF6C5CE7).withAlpha(20), borderRadius: BorderRadius.circular(10)), child: const Icon(Icons.auto_awesome, size: 16, color: Color(0xFF6C5CE7))),
const SizedBox(width: 10),
Expanded(child: Text(text, style: const TextStyle(fontSize: 14, color: Color(0xFF444444), height: 1.5))),
]),
);
}
String _getScoreComment(int score) {
switch (score) {
case 1: return '饮食不太健康,建议多吃蔬菜';
case 2: return '需要改善,减少油腻食物';
case 3: return '还不错,继续保持均衡饮食';
case 4: return '很健康!营养搭配合理';
case 5: return '非常健康!饮食管理很棒';
default: return '请完善食物信息';
}
}
Color _getScoreColor(int score) {
switch (score) {
case 1: return const Color(0xFFE53935);
case 2: return const Color(0xFFF9A825);
case 3: return const Color(0xFF8B9CF7);
case 4: return const Color(0xFF43A047);
case 5: return const Color(0xFF00C853);
default: return Colors.grey[400]!;
}
}
// ─────────── 保存按钮 ───────────
Widget _buildSubmitButton(BuildContext context, WidgetRef ref) {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () async {
try {
await ref.read(dietProvider.notifier).saveRecord();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('饮食记录已保存 ✅'),
backgroundColor: Color(0xFF43A047),
));
popRoute(ref);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () async {
try {
await ref.read(dietProvider.notifier).saveRecord();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('饮食记录已保存'),
backgroundColor: _kPrimary,
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
));
popRoute(ref);
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('保存失败: $e'),
backgroundColor: AppTheme.error,
behavior: SnackBarBehavior.floating,
));
}
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('保存失败: $e'),
backgroundColor: const Color(0xFFE53935),
));
}
}
},
child: const Text('保存记录'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8B9CF7),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
padding: const EdgeInsets.symmetric(vertical: 16),
textStyle: const TextStyle(fontSize: 16),
},
style: ElevatedButton.styleFrom(
backgroundColor: _kPrimary,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.radiusMd)),
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
child: const Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.check_circle_outline, size: 20),
SizedBox(width: 8),
Text('保存记录'),
]),
),
),
);
}
}
class _MealData {
final IconData icon;
final String label;
final String type;
const _MealData(this.icon, this.label, this.type);
}