import { AnimatedNumber } from '@/components/AnimatedNumber'; import { FloatingFoodOverlay } from '@/components/FloatingFoodOverlay'; import { ROUTES } from '@/constants/Routes'; import { NutritionSummary } from '@/services/dietRecords'; import { triggerLightHaptic } from '@/utils/haptics'; import { NutritionGoals, calculateRemainingCalories } from '@/utils/nutrition'; import dayjs from 'dayjs'; import { router } from 'expo-router'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Animated, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import Svg, { Circle } from 'react-native-svg'; const AnimatedCircle = Animated.createAnimatedComponent(Circle); export type NutritionRadarCardProps = { nutritionSummary: NutritionSummary | null; /** 营养目标 */ nutritionGoals?: NutritionGoals; /** 基础代谢消耗的卡路里 */ burnedCalories?: number; /** 基础代谢率 */ basalMetabolism?: number; /** 运动消耗卡路里 */ activeCalories?: number; /** 动画重置令牌 */ resetToken?: number; /** 餐次点击回调 */ onMealPress?: (mealType: 'breakfast' | 'lunch' | 'dinner' | 'snack') => void; }; // 简化的圆环进度组件 const SimpleRingProgress = ({ remainingCalories, totalAvailable }: { remainingCalories: number; totalAvailable: number; }) => { const animatedProgress = useRef(new Animated.Value(0)).current; const radius = 32; const strokeWidth = 8; // 增加圆环厚度 const center = radius + strokeWidth; const circumference = 2 * Math.PI * radius; // 计算进度:已消耗 / 总可用,进度越高表示剩余越少 const consumedAmount = totalAvailable - remainingCalories; const calorieProgress = totalAvailable > 0 ? Math.min((consumedAmount / totalAvailable) * 100, 100) : 0; useEffect(() => { Animated.timing(animatedProgress, { toValue: calorieProgress, duration: 600, useNativeDriver: false, }).start(); }, [calorieProgress]); const strokeDashoffset = animatedProgress.interpolate({ inputRange: [0, 100], outputRange: [circumference, 0], extrapolate: 'clamp', }); return ( 80 ? "#FF6B6B" : "#4ECDC4"} strokeWidth={strokeWidth} fill="none" strokeDasharray={`${circumference}`} strokeDashoffset={strokeDashoffset} strokeLinecap="round" transform={`rotate(-90 ${center} ${center})`} /> {Math.round(remainingCalories)} 还能吃 ); }; export function NutritionRadarCard({ nutritionSummary, nutritionGoals, burnedCalories = 1618, basalMetabolism, activeCalories, resetToken, onMealPress }: NutritionRadarCardProps) { const [currentMealType] = useState<'breakfast' | 'lunch' | 'dinner' | 'snack'>('breakfast'); const [showFoodOverlay, setShowFoodOverlay] = useState(false); // 计算营养目标 const calorieGoal = nutritionGoals?.calories ?? 2000; const proteinGoal = nutritionGoals?.proteinGoal ?? 50; const nutritionStats = useMemo(() => { return [ { label: '热量', value: nutritionSummary ? `${Math.round(nutritionSummary.totalCalories)} 千卡` : '0 千卡', color: '#FF6B6B' }, { label: '蛋白质', value: nutritionSummary ? `${nutritionSummary.totalProtein.toFixed(1)} g` : '0.0 g', color: '#4ECDC4' }, { label: '碳水', value: nutritionSummary ? `${nutritionSummary.totalCarbohydrate.toFixed(1)} g` : '0.0 g', color: '#45B7D1' }, { label: '脂肪', value: nutritionSummary ? `${nutritionSummary.totalFat.toFixed(1)} g` : '0.0 g', color: '#FFA07A' }, { label: '纤维', value: nutritionSummary ? `${nutritionSummary.totalFiber.toFixed(1)} g` : '0.0 g', color: '#98D8C8' }, { label: '钠', value: nutritionSummary ? `${Math.round(nutritionSummary.totalSodium)} mg` : '0 mg', color: '#F7DC6F' }, ]; }, [nutritionSummary]); // 计算还能吃的卡路里 const consumedCalories = nutritionSummary?.totalCalories || 0; // 使用分离的代谢和运动数据,如果没有提供则从burnedCalories推算 const effectiveBasalMetabolism = basalMetabolism ?? (burnedCalories * 0.7); // 假设70%是基础代谢 const effectiveActiveCalories = activeCalories ?? (burnedCalories * 0.3); // 假设30%是运动消耗 const remainingCalories = calculateRemainingCalories({ basalMetabolism: effectiveBasalMetabolism, activeCalories: effectiveActiveCalories, consumedCalories, }); const handleNavigateToRecords = () => { triggerLightHaptic(); router.push(ROUTES.NUTRITION_RECORDS); }; const handleAddFood = () => { triggerLightHaptic(); setShowFoodOverlay(true); }; return ( 饮食分析 更新: {dayjs(nutritionSummary?.updatedAt).format('MM-DD HH:mm')} {/* */} 添加+ {nutritionStats.map((stat) => ( {stat.label} {stat.value} ))} {/* 卡路里计算区域 */} 还能吃 Math.round(v).toString()} /> 千卡 = 基代 Math.round(v).toString()} /> + 运动 Math.round(v).toString()} /> - 饮食 Math.round(v).toString()} /> {/* 食物添加悬浮窗 */} setShowFoodOverlay(false)} mealType={currentMealType} /> ); } const styles = StyleSheet.create({ card: { backgroundColor: '#FFFFFF', borderRadius: 22, padding: 18, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.1, shadowRadius: 3.84, elevation: 5, marginTop: 12 }, cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, }, cardTitle: { fontSize: 14, color: '#192126', }, cardRightContainer: { flexDirection: 'row', alignItems: 'center', gap: 4, }, cardSubtitle: { fontSize: 10, color: '#9AA3AE', fontWeight: '600', }, contentContainer: { flexDirection: 'row', alignItems: 'center', }, radarContainer: { alignItems: 'center', justifyContent: 'center', marginRight: 8, width: 78, // Fixed width to match ring chart size height: 78, // Fixed height to match ring chart size }, statsContainer: { flex: 1, marginLeft: 4 }, statsBackground: { backgroundColor: 'rgba(248, 250, 252, 0.8)', // 毛玻璃背景色 borderRadius: 12, padding: 12, flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', shadowColor: '#000', shadowOffset: { width: 0, height: 1, }, shadowOpacity: 0.06, shadowRadius: 3, elevation: 1, // 添加边框增强毛玻璃效果 borderWidth: 0.5, borderColor: 'rgba(255, 255, 255, 0.8)', }, statItem: { flexDirection: 'row', alignItems: 'center', width: '48%', marginBottom: 8, }, statDot: { width: 8, height: 8, borderRadius: 4, marginRight: 8, }, statLabel: { fontSize: 10, color: '#9AA3AE', flex: 1, }, statValue: { fontSize: 12, color: '#192126', fontWeight: '600', }, // 卡路里相关样式 calorieSection: { marginTop: 6, }, calorieTitleContainer: { flexDirection: 'row', alignItems: 'center', }, calorieIcon: { fontSize: 16, marginRight: 8, }, calorieTitle: { fontSize: 16, fontWeight: '800', color: '#192126', }, calorieContent: { }, calorieSubtitle: { fontSize: 10, color: '#64748B', fontWeight: '600', marginRight: 4, }, calculationRow: { flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', gap: 4, }, mainValue: { fontSize: 14, fontWeight: '600', color: '#192126', }, calculationText: { fontSize: 10, fontWeight: '600', color: '#64748B', }, calculationItem: { flexDirection: 'row', alignItems: 'center', gap: 2, }, calculationLabel: { fontSize: 9, color: '#64748B', fontWeight: '500', }, calculationValue: { fontSize: 11, fontWeight: '700', color: '#192126', }, remainingCaloriesContainer: { flexDirection: 'row', alignItems: 'baseline', gap: 2, }, calorieUnit: { fontSize: 10, color: '#64748B', fontWeight: '500', }, mealsContainer: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 12, borderTopWidth: 1, borderTopColor: '#F1F5F9', }, mealItem: { alignItems: 'center', flex: 1, }, mealIconContainer: { position: 'relative', marginBottom: 6, }, mealEmoji: { fontSize: 24, }, addButton: { width: 52, height: 26, borderRadius: 16, backgroundColor: '#7b7be2ff', marginLeft: 8, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 2, }, mealName: { fontSize: 10, color: '#64748B', fontWeight: '600', }, });