import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import '../../core/navigation_provider.dart'; final dietProvider = NotifierProvider(DietNotifier.new); class DietState { final String? imagePath; final List foods; final String mealType; final bool isAnalyzing; final int? healthScore; DietState({ this.imagePath, this.foods = const [], this.mealType = 'lunch', this.isAnalyzing = false, this.healthScore, }); DietState copyWith({ String? imagePath, List? foods, String? mealType, bool? isAnalyzing, int? healthScore, }) { return DietState( imagePath: imagePath ?? this.imagePath, foods: foods ?? this.foods, mealType: mealType ?? this.mealType, isAnalyzing: isAnalyzing ?? this.isAnalyzing, healthScore: healthScore ?? this.healthScore, ); } } class FoodItem { final String id; String name; int calories; bool selected; FoodItem({ required this.id, required this.name, required this.calories, this.selected = true, }); } class DietNotifier extends Notifier { @override DietState build() => DietState(); void setImage(String path) { state = state.copyWith(imagePath: path); } void analyzeImage() async { state = state.copyWith(isAnalyzing: true); await Future.delayed(const Duration(seconds: 2)); final mockFoods = [ FoodItem(id: '1', name: '米饭', calories: 150), FoodItem(id: '2', name: '番茄炒蛋', calories: 200), FoodItem(id: '3', name: '红烧肉', calories: 350), FoodItem(id: '4', name: '青菜', calories: 50), ]; state = state.copyWith(foods: mockFoods, isAnalyzing: false, healthScore: 3); } void updateFoodName(String id, String name) { final foods = state.foods.map((f) => f.id == id ? FoodItem(id: f.id, name: name, 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, calories: calories, selected: f.selected) : f).toList(); state = state.copyWith(foods: foods); } void toggleFood(String id) { final foods = state.foods.map((f) => f.id == id ? FoodItem(id: f.id, name: f.name, calories: f.calories, selected: !f.selected) : f).toList(); state = state.copyWith(foods: foods); } void addFood() { final newId = '${DateTime.now().millisecondsSinceEpoch}'; final foods = [...state.foods, FoodItem(id: newId, name: '新食物', calories: 100)]; state = state.copyWith(foods: foods); } void removeFood(String id) { final foods = state.foods.where((f) => f.id != id).toList(); state = state.copyWith(foods: foods); } void setMealType(String type) { state = state.copyWith(mealType: type); } void reset() { state = DietState(); } } class DietCapturePage extends ConsumerWidget { const DietCapturePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(dietProvider); return Scaffold( appBar: AppBar( title: const Text('拍饮食'), centerTitle: true, ), body: state.imagePath == null ? _buildCaptureView(context, ref) : _buildResultView(context, ref), ); } Widget _buildCaptureView(BuildContext context, WidgetRef ref) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 180, height: 180, decoration: BoxDecoration( color: const Color(0xFFF2FAF9), borderRadius: BorderRadius.circular(90), border: Border.all(color: const Color(0xFF14B8A6), width: 2), ), child: const Icon(Icons.camera_alt, size: 48, color: Color(0xFF14B8A6)), ), const SizedBox(height: 24), const Text('拍摄或上传您的餐食照片', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500)), const SizedBox(height: 8), const Text('AI将识别食物并分析营养成分', style: TextStyle(fontSize: 14, color: Color(0xFF999999))), const SizedBox(height: 40), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _captureBtn(context, ref, Icons.camera_alt, '拍照', ImageSource.camera), const SizedBox(width: 24), _captureBtn(context, ref, Icons.photo_library, '相册', ImageSource.gallery), ], ), ], ), ); } Widget _captureBtn(BuildContext context, WidgetRef ref, IconData icon, String label, ImageSource source) { return Column( children: [ Container( width: 80, height: 80, decoration: BoxDecoration( color: const Color(0xFFFEFEFF), borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: const Color(0xFF14B8A6).withAlpha(20), blurRadius: 8, offset: const Offset(0, 2))], ), child: IconButton( icon: Icon(icon, size: 32, color: const Color(0xFF14B8A6)), onPressed: () => _pickImage(context, ref, source), ), ), const SizedBox(height: 8), Text(label, style: const TextStyle(fontSize: 14, color: Color(0xFF666666))), ], ); } Future _pickImage(BuildContext context, WidgetRef ref, ImageSource source) async { final picker = ImagePicker(); final picked = await picker.pickImage(source: source, imageQuality: 85); if (picked != null) { ref.read(dietProvider.notifier).setImage(picked.path); ref.read(dietProvider.notifier).analyzeImage(); } } 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: 20), _buildMealSelector(context, ref), const SizedBox(height: 20), if (state.isAnalyzing) _buildAnalyzingIndicator() else _buildFoodList(context, ref), if (!state.isAnalyzing && state.foods.isNotEmpty) ...[ const SizedBox(height: 20), _buildNutritionSummary(totalCalories), const SizedBox(height: 20), _buildHealthScore(state.healthScore ?? 0), const SizedBox(height: 30), _buildSubmitButton(context, ref), ], ]), ); } 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(0xFF14B8A6))), ]), style: ElevatedButton.styleFrom( backgroundColor: isSelected ? const Color(0xFF14B8A6) : const Color(0xFFF2FAF9), foregroundColor: isSelected ? Colors.white : const Color(0xFF14B8A6), elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), ); }).toList()), ]); } Widget _buildAnalyzingIndicator() { return Center( child: Column(children: [ Container( width: 60, height: 60, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFE6FAF6), borderRadius: BorderRadius.circular(30), ), child: const CircularProgressIndicator(strokeWidth: 3, color: Color(0xFF14B8A6)), ), const SizedBox(height: 16), const Text('AI 正在识别食物...', style: TextStyle(fontSize: 16, color: Color(0xFF666666))), ]), ); } 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(0xFFD4EDE8), 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(0xFF14B8A6)), 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(0xFFF2FAF9) : 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(0xFF14B8A6), ), 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), ), 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(0xFF14B8A6)), ), ), 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(0xFFF2FAF9), 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(0xFFD4EDE8), 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))), ]), ); } 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(0xFF14B8A6); 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: () { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('饮食记录已保存 ✅'), backgroundColor: Color(0xFF14B8A6), )); popRoute(ref); }, child: const Text('保存记录'), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF14B8A6), foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), padding: const EdgeInsets.symmetric(vertical: 16), textStyle: const TextStyle(fontSize: 16), ), ), ); } }