- Introduced new translation files for medication, personal, and weight management in Chinese. - Updated the main index file to include the new translation modules. - Enhanced the medication type definitions to include 'ointment'. - Refactored workout type labels to utilize i18n for better localization support. - Improved sleep quality descriptions and recommendations with i18n integration.
305 lines
8.8 KiB
TypeScript
305 lines
8.8 KiB
TypeScript
import { ThemedText } from '@/components/ThemedText';
|
|
import { useI18n } from '@/hooks/useI18n';
|
|
import { useThemeColor } from '@/hooks/useThemeColor';
|
|
import React, { useEffect, useRef } from 'react';
|
|
import { Animated, StyleSheet, View } from 'react-native';
|
|
import Svg, { Circle, Defs, Stop, LinearGradient as SvgLinearGradient } from 'react-native-svg';
|
|
|
|
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
|
|
|
|
export type CalorieRingChartProps = {
|
|
metabolism: number;
|
|
exercise: number;
|
|
consumed: number;
|
|
protein: number;
|
|
fat: number;
|
|
carbs: number;
|
|
proteinGoal: number;
|
|
fatGoal: number;
|
|
carbsGoal: number;
|
|
};
|
|
|
|
export function CalorieRingChart({
|
|
metabolism,
|
|
exercise,
|
|
consumed,
|
|
protein,
|
|
fat,
|
|
carbs,
|
|
}: CalorieRingChartProps) {
|
|
const { t } = useI18n();
|
|
const textColor = useThemeColor({}, 'text');
|
|
const textSecondaryColor = useThemeColor({}, 'textSecondary');
|
|
|
|
// 动画值
|
|
const animatedProgress = useRef(new Animated.Value(0)).current;
|
|
|
|
// 计算还能吃的卡路里:代谢 + 运动 - 饮食
|
|
const remainingCalories = metabolism + exercise - consumed;
|
|
const canEat = Math.max(0, remainingCalories);
|
|
|
|
// 计算进度百分比 (用于圆环显示)
|
|
const totalAvailable = metabolism + exercise;
|
|
const progressPercentage = totalAvailable > 0 ? Math.min((consumed / totalAvailable) * 100, 100) : 0;
|
|
|
|
// 圆环参数 - 缩小尺寸
|
|
const radius = 42;
|
|
const strokeWidth = 8;
|
|
const center = radius + strokeWidth;
|
|
const circumference = 2 * Math.PI * radius;
|
|
const strokeDasharray = circumference;
|
|
|
|
// 动画效果
|
|
useEffect(() => {
|
|
Animated.timing(animatedProgress, {
|
|
toValue: progressPercentage,
|
|
duration: 600,
|
|
useNativeDriver: false,
|
|
}).start();
|
|
}, [progressPercentage]);
|
|
|
|
// 使用动画值计算strokeDashoffset
|
|
const strokeDashoffset = animatedProgress.interpolate({
|
|
inputRange: [0, 100],
|
|
outputRange: [circumference, 0],
|
|
extrapolate: 'clamp',
|
|
});
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<View style={styles.mainContent}>
|
|
{/* 左侧圆环图 */}
|
|
<View style={styles.chartContainer}>
|
|
<Svg width={center * 2} height={center * 2}>
|
|
<Defs>
|
|
<SvgLinearGradient id="progressGradient" x1="0" y1="0" x2="1" y2="1">
|
|
<Stop offset="0" stopColor={progressPercentage > 80 ? "#FF9966" : "#4facfe"} stopOpacity="1" />
|
|
<Stop offset="1" stopColor={progressPercentage > 80 ? "#FF5E62" : "#00f2fe"} stopOpacity="1" />
|
|
</SvgLinearGradient>
|
|
</Defs>
|
|
{/* 背景圆环 */}
|
|
<Circle
|
|
cx={center}
|
|
cy={center}
|
|
r={radius}
|
|
stroke="#F5F7FA"
|
|
strokeWidth={strokeWidth}
|
|
fill="none"
|
|
/>
|
|
{/* 进度圆环 */}
|
|
<AnimatedCircle
|
|
cx={center}
|
|
cy={center}
|
|
r={radius}
|
|
stroke="url(#progressGradient)"
|
|
strokeWidth={strokeWidth}
|
|
fill="none"
|
|
strokeDasharray={`${strokeDasharray}`}
|
|
strokeDashoffset={strokeDashoffset}
|
|
strokeLinecap="round"
|
|
transform={`rotate(-90 ${center} ${center})`}
|
|
/>
|
|
</Svg>
|
|
|
|
{/* 中心内容 */}
|
|
<View style={styles.centerContent}>
|
|
<ThemedText style={styles.centerLabel}>
|
|
{t('nutritionRecords.chart.remaining')}
|
|
</ThemedText>
|
|
<ThemedText style={styles.centerValue}>
|
|
{Math.round(canEat)}
|
|
</ThemedText>
|
|
<ThemedText style={styles.centerUnit}>
|
|
{t('nutritionRecords.nutrients.caloriesUnit')}
|
|
</ThemedText>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 右侧数据展示 - 优化布局 */}
|
|
<View style={styles.dataContainer}>
|
|
{/* 公式 */}
|
|
<View style={styles.formulaContainer}>
|
|
<ThemedText style={styles.formulaText}>
|
|
{t('nutritionRecords.chart.formula')}
|
|
</ThemedText>
|
|
</View>
|
|
|
|
{/* 代谢 & 运动 & 饮食 */}
|
|
<View style={styles.statsGroup}>
|
|
<View style={styles.statRowCompact}>
|
|
<View style={styles.labelWithDot}>
|
|
<View style={styles.dotMetabolism} />
|
|
<ThemedText style={styles.statLabel}>{t('nutritionRecords.chart.metabolism')}</ThemedText>
|
|
</View>
|
|
<ThemedText style={styles.statValue}>{Math.round(metabolism)}</ThemedText>
|
|
</View>
|
|
<View style={styles.statRowCompact}>
|
|
<View style={styles.labelWithDot}>
|
|
<View style={styles.dotExercise} />
|
|
<ThemedText style={styles.statLabel}>{t('nutritionRecords.chart.exercise')}</ThemedText>
|
|
</View>
|
|
<ThemedText style={styles.statValue}>{Math.round(exercise)}</ThemedText>
|
|
</View>
|
|
<View style={styles.statRowCompact}>
|
|
<View style={styles.labelWithDot}>
|
|
<View style={styles.dotConsumed} />
|
|
<ThemedText style={styles.statLabel}>{t('nutritionRecords.chart.diet')}</ThemedText>
|
|
</View>
|
|
<ThemedText style={styles.statValue}>{Math.round(consumed)}</ThemedText>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.divider} />
|
|
|
|
{/* 营养素 - 水平排布 */}
|
|
<View style={styles.nutritionRow}>
|
|
<View style={styles.nutritionItem}>
|
|
<ThemedText style={styles.statValueSmall}>{Math.round(protein)}{t('nutritionRecords.nutrients.unit')}</ThemedText>
|
|
<ThemedText style={styles.statLabelSmall}>{t('nutritionRecords.nutrients.protein')}</ThemedText>
|
|
</View>
|
|
<View style={styles.nutritionItem}>
|
|
<ThemedText style={styles.statValueSmall}>{Math.round(fat)}{t('nutritionRecords.nutrients.unit')}</ThemedText>
|
|
<ThemedText style={styles.statLabelSmall}>{t('nutritionRecords.nutrients.fat')}</ThemedText>
|
|
</View>
|
|
<View style={styles.nutritionItem}>
|
|
<ThemedText style={styles.statValueSmall}>{Math.round(carbs)}{t('nutritionRecords.nutrients.unit')}</ThemedText>
|
|
<ThemedText style={styles.statLabelSmall}>{t('nutritionRecords.nutrients.carbs')}</ThemedText>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
backgroundColor: '#FFFFFF',
|
|
borderRadius: 24,
|
|
padding: 16,
|
|
marginHorizontal: 20,
|
|
shadowColor: 'rgba(30, 41, 59, 0.08)',
|
|
shadowOffset: { width: 0, height: 8 },
|
|
shadowOpacity: 0.12,
|
|
shadowRadius: 16,
|
|
elevation: 6,
|
|
},
|
|
formulaContainer: {
|
|
marginBottom: 12,
|
|
},
|
|
formulaText: {
|
|
fontSize: 10,
|
|
fontWeight: '500',
|
|
color: '#94A3B8',
|
|
fontFamily: 'AliRegular',
|
|
},
|
|
mainContent: {
|
|
flexDirection: 'row',
|
|
alignItems: 'flex-start',
|
|
},
|
|
chartContainer: {
|
|
position: 'relative',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
width: 100,
|
|
height: 100,
|
|
marginTop: 8,
|
|
},
|
|
centerContent: {
|
|
position: 'absolute',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
centerLabel: {
|
|
fontSize: 10,
|
|
fontWeight: '500',
|
|
color: '#94A3B8',
|
|
marginBottom: 1,
|
|
fontFamily: 'AliRegular',
|
|
},
|
|
centerValue: {
|
|
fontSize: 20,
|
|
fontWeight: '800',
|
|
color: '#1E293B',
|
|
lineHeight: 24,
|
|
fontFamily: 'AliBold',
|
|
},
|
|
centerUnit: {
|
|
fontSize: 10,
|
|
fontWeight: '600',
|
|
color: '#64748B',
|
|
fontFamily: 'AliRegular',
|
|
},
|
|
dataContainer: {
|
|
flex: 1,
|
|
marginLeft: 20,
|
|
},
|
|
statsGroup: {
|
|
gap: 6,
|
|
},
|
|
statRowCompact: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
},
|
|
labelWithDot: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
},
|
|
dotMetabolism: {
|
|
width: 6,
|
|
height: 6,
|
|
borderRadius: 3,
|
|
backgroundColor: '#94A3B8',
|
|
marginRight: 6,
|
|
},
|
|
dotExercise: {
|
|
width: 6,
|
|
height: 6,
|
|
borderRadius: 3,
|
|
backgroundColor: '#4facfe',
|
|
marginRight: 6,
|
|
},
|
|
dotConsumed: {
|
|
width: 6,
|
|
height: 6,
|
|
borderRadius: 3,
|
|
backgroundColor: '#FF9966',
|
|
marginRight: 6,
|
|
},
|
|
statLabel: {
|
|
fontSize: 12,
|
|
color: '#64748B',
|
|
fontFamily: 'AliRegular',
|
|
},
|
|
statValue: {
|
|
fontSize: 13,
|
|
fontWeight: '600',
|
|
color: '#334155',
|
|
fontFamily: 'AliBold',
|
|
},
|
|
divider: {
|
|
height: 1,
|
|
backgroundColor: '#F1F5F9',
|
|
marginVertical: 10,
|
|
},
|
|
nutritionRow: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
},
|
|
nutritionItem: {
|
|
alignItems: 'center',
|
|
},
|
|
statLabelSmall: {
|
|
fontSize: 10,
|
|
color: '#94A3B8',
|
|
marginTop: 2,
|
|
fontFamily: 'AliRegular',
|
|
},
|
|
statValueSmall: {
|
|
fontSize: 13,
|
|
fontWeight: '600',
|
|
color: '#475569',
|
|
fontFamily: 'AliBold',
|
|
},
|
|
}); |