Add Chinese translations for medication management and personal settings
- 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.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useThemeColor } from '@/hooks/useThemeColor';
|
||||
import { DietRecord } from '@/services/dietRecords';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
@@ -15,14 +16,6 @@ export type NutritionRecordCardProps = {
|
||||
onDelete?: () => void;
|
||||
};
|
||||
|
||||
const MEAL_TYPE_LABELS = {
|
||||
breakfast: '早餐',
|
||||
lunch: '午餐',
|
||||
dinner: '晚餐',
|
||||
snack: '加餐',
|
||||
other: '其他',
|
||||
} as const;
|
||||
|
||||
const MEAL_TYPE_ICONS = {
|
||||
breakfast: 'sunny-outline',
|
||||
lunch: 'partly-sunny-outline',
|
||||
@@ -44,46 +37,40 @@ export function NutritionRecordCard({
|
||||
onPress,
|
||||
onDelete
|
||||
}: NutritionRecordCardProps) {
|
||||
const surfaceColor = useThemeColor({}, 'surface');
|
||||
const { t } = useI18n();
|
||||
const textColor = useThemeColor({}, 'text');
|
||||
const textSecondaryColor = useThemeColor({}, 'textSecondary');
|
||||
|
||||
// Popover 状态管理
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const popoverRef = useRef<any>(null);
|
||||
|
||||
// 左滑删除相关
|
||||
const swipeableRef = useRef<Swipeable>(null);
|
||||
|
||||
// 添加滑动状态管理,防止滑动时触发点击事件
|
||||
const [isSwiping, setIsSwiping] = useState(false);
|
||||
|
||||
// 营养数据统计
|
||||
const nutritionStats = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
label: '蛋白质',
|
||||
value: record.proteinGrams ? `${record.proteinGrams.toFixed(1)}g` : '-',
|
||||
icon: '🥩',
|
||||
color: '#FF6B6B'
|
||||
label: t('nutritionRecords.nutrients.protein'),
|
||||
value: record.proteinGrams ? `${Math.round(record.proteinGrams)}` : '-',
|
||||
unit: t('nutritionRecords.nutrients.unit'),
|
||||
color: '#64748B'
|
||||
},
|
||||
{
|
||||
label: '脂肪',
|
||||
value: record.fatGrams ? `${record.fatGrams.toFixed(1)}g` : '-',
|
||||
icon: '🥑',
|
||||
color: '#FFB366'
|
||||
label: t('nutritionRecords.nutrients.fat'),
|
||||
value: record.fatGrams ? `${Math.round(record.fatGrams)}` : '-',
|
||||
unit: t('nutritionRecords.nutrients.unit'),
|
||||
color: '#64748B'
|
||||
},
|
||||
{
|
||||
label: '碳水',
|
||||
value: record.carbohydrateGrams ? `${record.carbohydrateGrams.toFixed(1)}g` : '-',
|
||||
icon: '🍞',
|
||||
color: '#4ECDC4'
|
||||
label: t('nutritionRecords.nutrients.carbs'),
|
||||
value: record.carbohydrateGrams ? `${Math.round(record.carbohydrateGrams)}` : '-',
|
||||
unit: t('nutritionRecords.nutrients.unit'),
|
||||
color: '#64748B'
|
||||
},
|
||||
];
|
||||
}, [record]);
|
||||
}, [record, t]);
|
||||
|
||||
const mealTypeColor = MEAL_TYPE_COLORS[record.mealType];
|
||||
const mealTypeLabel = MEAL_TYPE_LABELS[record.mealType];
|
||||
const mealTypeLabel = t(`nutritionRecords.mealTypes.${record.mealType}`);
|
||||
|
||||
// 处理点击事件,只有在非滑动状态下才触发
|
||||
const handlePress = () => {
|
||||
@@ -92,31 +79,17 @@ export function NutritionRecordCard({
|
||||
}
|
||||
};
|
||||
|
||||
// 处理滑动开始
|
||||
const handleSwipeableWillOpen = () => {
|
||||
setIsSwiping(true);
|
||||
};
|
||||
const handleSwipeableWillOpen = () => setIsSwiping(true);
|
||||
const handleSwipeableClose = () => setTimeout(() => setIsSwiping(false), 100);
|
||||
|
||||
// 处理滑动结束
|
||||
const handleSwipeableClose = () => {
|
||||
// 延迟重置滑动状态,防止滑动结束时立即触发点击
|
||||
setTimeout(() => {
|
||||
setIsSwiping(false);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// 处理删除操作
|
||||
const handleDelete = () => {
|
||||
Alert.alert(
|
||||
'确认删除',
|
||||
`确定要删除这条营养记录吗?此操作无法撤销。`,
|
||||
t('nutritionRecords.delete.title'),
|
||||
t('nutritionRecords.delete.message'),
|
||||
[
|
||||
{ text: t('nutritionRecords.delete.cancel'), style: 'cancel' },
|
||||
{
|
||||
text: '取消',
|
||||
style: 'cancel',
|
||||
},
|
||||
{
|
||||
text: '删除',
|
||||
text: t('nutritionRecords.delete.confirm'),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
onDelete?.();
|
||||
@@ -127,7 +100,6 @@ export function NutritionRecordCard({
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染删除按钮
|
||||
const renderRightActions = () => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
@@ -136,7 +108,6 @@ export function NutritionRecordCard({
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Ionicons name="trash" size={20} color="#FFFFFF" />
|
||||
<Text style={styles.deleteButtonText}>删除</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
@@ -152,239 +123,228 @@ export function NutritionRecordCard({
|
||||
onSwipeableClose={handleSwipeableClose}
|
||||
>
|
||||
<RectButton
|
||||
style={[
|
||||
styles.card,
|
||||
|
||||
]}
|
||||
style={styles.card}
|
||||
onPress={handlePress}
|
||||
// activeOpacity={0.7}
|
||||
>
|
||||
{/* 主要内容区域 - 水平布局 */}
|
||||
<View style={styles.mainContent}>
|
||||
{/* 左侧:食物图片 */}
|
||||
<View style={[styles.foodImageContainer, !record.imageUrl && styles.foodImagePlaceholder]}>
|
||||
{record.imageUrl ? (
|
||||
<Image
|
||||
source={{ uri: record.imageUrl }}
|
||||
style={styles.foodImage}
|
||||
cachePolicy={'memory-disk'}
|
||||
/>
|
||||
) : (
|
||||
<Ionicons name="restaurant" size={28} color={textSecondaryColor} />
|
||||
)}
|
||||
{/* 左侧:时间线和图标 */}
|
||||
<View style={styles.leftSection}>
|
||||
<View style={styles.mealIconContainer}>
|
||||
<Image
|
||||
source={require('@/assets/images/icons/icon-food.png')}
|
||||
style={styles.mealIcon}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 中间:食物信息 */}
|
||||
<View style={styles.foodInfoContainer}>
|
||||
{/* 食物名称 */}
|
||||
<ThemedText style={[styles.foodName, { color: textColor }]}>
|
||||
{record.foodName}
|
||||
</ThemedText>
|
||||
|
||||
{/* 时间 */}
|
||||
<ThemedText style={[styles.mealTime, { color: textSecondaryColor }]}>
|
||||
{record.mealTime ? dayjs(record.mealTime).format('HH:mm') : '--:--'}
|
||||
</ThemedText>
|
||||
|
||||
{/* 营养信息 - 水平排列 */}
|
||||
<View style={styles.nutritionContainer}>
|
||||
{/* 中间:主要信息 */}
|
||||
<View style={styles.centerSection}>
|
||||
<View style={styles.titleRow}>
|
||||
<ThemedText style={styles.foodName} numberOfLines={1}>
|
||||
{record.foodName}
|
||||
</ThemedText>
|
||||
<View style={[styles.mealTag, { backgroundColor: `${mealTypeColor}15` }]}>
|
||||
<Text style={[styles.mealTagText, { color: mealTypeColor }]}>{mealTypeLabel}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.metaRow}>
|
||||
<Ionicons name="time-outline" size={12} color="#94A3B8" />
|
||||
<Text style={styles.timeText}>
|
||||
{record.mealTime ? dayjs(record.mealTime).format('HH:mm') : '--:--'}
|
||||
</Text>
|
||||
{record.portionDescription && (
|
||||
<>
|
||||
<Text style={styles.dotSeparator}>·</Text>
|
||||
<Text style={styles.portionText} numberOfLines={1}>{record.portionDescription}</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 营养微缩信息 */}
|
||||
<View style={styles.nutritionRow}>
|
||||
{nutritionStats.map((stat, index) => (
|
||||
<View key={stat.label} style={styles.nutritionItem}>
|
||||
<ThemedText style={styles.nutritionIcon}>{stat.icon}</ThemedText>
|
||||
<ThemedText style={[styles.nutritionValue, { color: textColor }]}>
|
||||
{stat.value}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<View key={index} style={styles.nutritionItem}>
|
||||
<Text style={styles.nutritionValue}>{stat.value}<Text style={styles.nutritionUnit}>{stat.unit}</Text></Text>
|
||||
<Text style={styles.nutritionLabel}>{stat.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 右侧:热量和餐次标签 */}
|
||||
{/* 右侧:热量 */}
|
||||
<View style={styles.rightSection}>
|
||||
{/* 热量显示 */}
|
||||
<View style={styles.caloriesContainer}>
|
||||
<ThemedText style={[styles.caloriesText]}>
|
||||
{record.estimatedCalories ? `${Math.round(record.estimatedCalories)} kcal` : '- kcal'}
|
||||
</ThemedText>
|
||||
</View>
|
||||
|
||||
{/* 餐次标签 */}
|
||||
<View style={[styles.mealTypeBadge]}>
|
||||
<ThemedText style={[styles.mealTypeText, { color: mealTypeColor }]}>
|
||||
{mealTypeLabel}
|
||||
</ThemedText>
|
||||
</View>
|
||||
|
||||
<Text style={styles.caloriesValue}>
|
||||
{record.estimatedCalories ? Math.round(record.estimatedCalories) : '-'}
|
||||
</Text>
|
||||
<Text style={styles.caloriesUnit}>{t('nutritionRecords.nutrients.caloriesUnit')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 如果有图片,显示图片缩略图 */}
|
||||
{record.imageUrl && (
|
||||
<View style={styles.imageSection}>
|
||||
<Image
|
||||
source={{ uri: record.imageUrl }}
|
||||
style={styles.foodImage}
|
||||
contentFit="cover"
|
||||
transition={200}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</RectButton>
|
||||
</Swipeable>
|
||||
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginBottom: 16,
|
||||
// iOS 阴影效果 - 更自然的阴影
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
marginBottom: 12,
|
||||
marginHorizontal: 24,
|
||||
shadowColor: 'rgba(30, 41, 59, 0.05)',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
// Android 阴影效果
|
||||
elevation: 3,
|
||||
shadowRadius: 12,
|
||||
elevation: 2,
|
||||
},
|
||||
card: {
|
||||
flex: 1,
|
||||
minHeight: 100,
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 16,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 14,
|
||||
borderRadius: 24,
|
||||
padding: 16,
|
||||
},
|
||||
mainContent: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
leftSection: {
|
||||
marginRight: 12,
|
||||
alignItems: 'center',
|
||||
},
|
||||
foodImageContainer: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 12,
|
||||
marginRight: 16,
|
||||
mealIconContainer: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#F8FAFC',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
mealIcon: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
opacity: 0.8,
|
||||
},
|
||||
centerSection: {
|
||||
flex: 1,
|
||||
marginRight: 12,
|
||||
},
|
||||
titleRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 4,
|
||||
gap: 8,
|
||||
},
|
||||
foodName: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#1E293B',
|
||||
fontFamily: 'AliBold',
|
||||
flexShrink: 1,
|
||||
},
|
||||
mealTag: {
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 6,
|
||||
},
|
||||
mealTagText: {
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
metaRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
timeText: {
|
||||
fontSize: 12,
|
||||
color: '#94A3B8',
|
||||
marginLeft: 4,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
dotSeparator: {
|
||||
marginHorizontal: 4,
|
||||
color: '#CBD5E1',
|
||||
},
|
||||
portionText: {
|
||||
fontSize: 12,
|
||||
color: '#64748B',
|
||||
fontFamily: 'AliRegular',
|
||||
flex: 1,
|
||||
},
|
||||
nutritionRow: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
nutritionItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'baseline',
|
||||
gap: 2,
|
||||
},
|
||||
nutritionValue: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
color: '#475569',
|
||||
fontFamily: 'AliBold',
|
||||
},
|
||||
nutritionUnit: {
|
||||
fontSize: 10,
|
||||
fontWeight: '500',
|
||||
color: '#94A3B8',
|
||||
marginLeft: 1,
|
||||
},
|
||||
nutritionLabel: {
|
||||
fontSize: 10,
|
||||
color: '#94A3B8',
|
||||
marginLeft: 2,
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
rightSection: {
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'flex-start',
|
||||
paddingTop: 2,
|
||||
},
|
||||
caloriesValue: {
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
color: '#1E293B',
|
||||
fontFamily: 'AliBold',
|
||||
lineHeight: 22,
|
||||
},
|
||||
caloriesUnit: {
|
||||
fontSize: 10,
|
||||
color: '#94A3B8',
|
||||
fontWeight: '500',
|
||||
fontFamily: 'AliRegular',
|
||||
},
|
||||
imageSection: {
|
||||
marginTop: 12,
|
||||
height: 120,
|
||||
width: '100%',
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#F1F5F9',
|
||||
},
|
||||
foodImage: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: 8,
|
||||
},
|
||||
foodImagePlaceholder: {
|
||||
backgroundColor: '#F8F9FA',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
foodInfoContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
gap: 4,
|
||||
},
|
||||
foodName: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#333333',
|
||||
lineHeight: 20,
|
||||
},
|
||||
mealTime: {
|
||||
fontSize: 12,
|
||||
fontWeight: '400',
|
||||
color: '#999999',
|
||||
lineHeight: 16,
|
||||
},
|
||||
nutritionContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 16,
|
||||
marginTop: 2,
|
||||
},
|
||||
nutritionItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
},
|
||||
nutritionIcon: {
|
||||
fontSize: 14,
|
||||
},
|
||||
nutritionValue: {
|
||||
fontSize: 13,
|
||||
fontWeight: '500',
|
||||
color: '#666666',
|
||||
},
|
||||
rightSection: {
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
minHeight: 60,
|
||||
},
|
||||
caloriesContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
caloriesText: {
|
||||
fontSize: 14,
|
||||
color: '#333333',
|
||||
fontWeight: '600',
|
||||
},
|
||||
mealTypeBadge: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 12,
|
||||
backgroundColor: 'rgba(0,0,0,0.05)',
|
||||
},
|
||||
mealTypeText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
},
|
||||
moreButton: {
|
||||
padding: 2,
|
||||
},
|
||||
notesSection: {
|
||||
marginTop: 8,
|
||||
paddingTop: 12,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: 'rgba(0,0,0,0.06)',
|
||||
},
|
||||
notesText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '500',
|
||||
lineHeight: 18,
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
popoverContainer: {
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#FFFFFF',
|
||||
// iOS 阴影效果
|
||||
shadowColor: '#000000',
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowRadius: 12,
|
||||
// Android 阴影效果
|
||||
elevation: 8,
|
||||
// 添加边框
|
||||
borderWidth: 0.5,
|
||||
borderColor: 'rgba(0, 0, 0, 0.08)',
|
||||
},
|
||||
popoverBackground: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
||||
},
|
||||
popoverContent: {
|
||||
minWidth: 140,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
popoverItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
gap: 12,
|
||||
},
|
||||
popoverText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
},
|
||||
deleteButton: {
|
||||
backgroundColor: '#EF4444',
|
||||
backgroundColor: '#FF6B6B',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 80,
|
||||
borderRadius: 12,
|
||||
marginLeft: 8,
|
||||
},
|
||||
deleteButtonText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
marginTop: 4,
|
||||
width: 70,
|
||||
height: '100%',
|
||||
borderRadius: 24,
|
||||
marginLeft: 12,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user