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:
@@ -3,6 +3,7 @@ import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { ROUTES } from '@/constants/Routes';
|
||||
import { useAppSelector } from '@/hooks/redux';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||
import { addDietRecord, type CreateDietRecordDto, type MealType } from '@/services/dietRecords';
|
||||
import { selectFoodRecognitionResult } from '@/store/foodRecognitionSlice';
|
||||
@@ -65,15 +66,8 @@ const mockFoodItems = [
|
||||
}
|
||||
];
|
||||
|
||||
// 餐次映射
|
||||
const MEAL_TYPE_MAP = {
|
||||
breakfast: '早餐',
|
||||
lunch: '午餐',
|
||||
dinner: '晚餐',
|
||||
snack: '加餐'
|
||||
};
|
||||
|
||||
export default function FoodAnalysisResultScreen() {
|
||||
const { t } = useI18n();
|
||||
const safeAreaTop = useSafeAreaTop()
|
||||
const router = useRouter();
|
||||
const params = useLocalSearchParams<{
|
||||
@@ -190,6 +184,15 @@ export default function FoodAnalysisResultScreen() {
|
||||
}
|
||||
};
|
||||
|
||||
// 餐次映射
|
||||
const MEAL_TYPE_MAP = {
|
||||
breakfast: t('nutritionRecords.mealTypes.breakfast'),
|
||||
lunch: t('nutritionRecords.mealTypes.lunch'),
|
||||
dinner: t('nutritionRecords.mealTypes.dinner'),
|
||||
snack: t('nutritionRecords.mealTypes.snack'),
|
||||
other: t('nutritionRecords.mealTypes.other'),
|
||||
};
|
||||
|
||||
// 计算所有食物的总营养数据
|
||||
const totalCalories = foodItems.reduce((sum, item) => sum + item.calories, 0);
|
||||
const totalProtein = foodItems.reduce((sum, item) => sum + item.protein, 0);
|
||||
@@ -253,24 +256,24 @@ export default function FoodAnalysisResultScreen() {
|
||||
|
||||
// 餐次选择选项
|
||||
const mealOptions = [
|
||||
{ key: 'breakfast' as const, label: '早餐', color: '#FF6B35' },
|
||||
{ key: 'lunch' as const, label: '午餐', color: '#4CAF50' },
|
||||
{ key: 'dinner' as const, label: '晚餐', color: '#2196F3' },
|
||||
{ key: 'snack' as const, label: '加餐', color: '#FF9800' },
|
||||
{ key: 'breakfast' as const, label: t('nutritionRecords.mealTypes.breakfast'), color: '#FF6B35' },
|
||||
{ key: 'lunch' as const, label: t('nutritionRecords.mealTypes.lunch'), color: '#4CAF50' },
|
||||
{ key: 'dinner' as const, label: t('nutritionRecords.mealTypes.dinner'), color: '#2196F3' },
|
||||
{ key: 'snack' as const, label: t('nutritionRecords.mealTypes.snack'), color: '#FF9800' },
|
||||
];
|
||||
|
||||
if (!imageUri && !recognitionResult) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<HeaderBar
|
||||
title="分析结果"
|
||||
title={t('foodAnalysisResult.title')}
|
||||
onBack={() => router.back()}
|
||||
/>
|
||||
<View style={{
|
||||
paddingTop: safeAreaTop
|
||||
}} />
|
||||
<View style={styles.errorContainer}>
|
||||
<Text style={styles.errorText}>未找到图片或识别结果</Text>
|
||||
<Text style={styles.errorText}>{t('foodAnalysisResult.error.notFound')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -287,7 +290,7 @@ export default function FoodAnalysisResultScreen() {
|
||||
/>
|
||||
|
||||
<HeaderBar
|
||||
title="分析结果"
|
||||
title={t('foodAnalysisResult.title')}
|
||||
onBack={() => router.back()}
|
||||
transparent={true}
|
||||
/>
|
||||
@@ -316,7 +319,7 @@ export default function FoodAnalysisResultScreen() {
|
||||
<View style={styles.placeholderContainer}>
|
||||
<View style={styles.placeholderContent}>
|
||||
<Ionicons name="restaurant-outline" size={48} color="#666" />
|
||||
<Text style={styles.placeholderText}>营养记录</Text>
|
||||
<Text style={styles.placeholderText}>{t('foodAnalysisResult.placeholder')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
@@ -325,8 +328,8 @@ export default function FoodAnalysisResultScreen() {
|
||||
<View style={styles.descriptionBubble}>
|
||||
<Text style={styles.descriptionText}>
|
||||
{recognitionResult ?
|
||||
`置信度: ${recognitionResult.confidence}%` :
|
||||
dayjs().format('YYYY年M月D日')
|
||||
t('foodAnalysisResult.confidence', { value: recognitionResult.confidence }) :
|
||||
dayjs().format(t('foodAnalysisResult.dateFormats.today'))
|
||||
}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -337,31 +340,31 @@ export default function FoodAnalysisResultScreen() {
|
||||
{/* 卡路里 */}
|
||||
<View style={styles.calorieSection}>
|
||||
<Text style={styles.calorieValue}>{totalCalories}</Text>
|
||||
<Text style={styles.calorieUnit}>千卡</Text>
|
||||
<Text style={styles.calorieUnit}>{t('foodAnalysisResult.nutrients.caloriesUnit')}</Text>
|
||||
</View>
|
||||
|
||||
{/* 营养圆环图 */}
|
||||
<View style={styles.nutritionRings}>
|
||||
<NutritionRing
|
||||
label="蛋白质"
|
||||
label={t('foodAnalysisResult.nutrients.protein')}
|
||||
value={totalProtein.toFixed(1)}
|
||||
unit="克"
|
||||
unit={t('foodAnalysisResult.nutrients.unit')}
|
||||
percentage={Math.min(100, proteinPercentage)}
|
||||
color="#4CAF50"
|
||||
resetToken={animationTrigger}
|
||||
/>
|
||||
<NutritionRing
|
||||
label="脂肪"
|
||||
label={t('foodAnalysisResult.nutrients.fat')}
|
||||
value={totalFat.toFixed(1)}
|
||||
unit="克"
|
||||
unit={t('foodAnalysisResult.nutrients.unit')}
|
||||
percentage={Math.min(100, fatPercentage)}
|
||||
color="#FF9800"
|
||||
resetToken={animationTrigger}
|
||||
/>
|
||||
<NutritionRing
|
||||
label="碳水"
|
||||
label={t('foodAnalysisResult.nutrients.carbs')}
|
||||
value={totalCarbohydrate.toFixed(1)}
|
||||
unit="克"
|
||||
unit={t('foodAnalysisResult.nutrients.unit')}
|
||||
percentage={Math.min(100, carbohydratePercentage)}
|
||||
color="#2196F3"
|
||||
resetToken={animationTrigger}
|
||||
@@ -372,7 +375,7 @@ export default function FoodAnalysisResultScreen() {
|
||||
{/* 食物摄入部分 */}
|
||||
<View style={styles.foodIntakeSection}>
|
||||
<Text style={styles.foodIntakeTitle}>
|
||||
{recognitionResult ? '识别结果' : '食物摄入'}
|
||||
{recognitionResult ? t('foodAnalysisResult.sections.recognitionResult') : t('foodAnalysisResult.sections.foodIntake')}
|
||||
</Text>
|
||||
{recognitionResult && recognitionResult.analysisText && (
|
||||
<Text style={styles.analysisText}>{recognitionResult.analysisText}</Text>
|
||||
@@ -384,15 +387,15 @@ export default function FoodAnalysisResultScreen() {
|
||||
<View style={styles.nonFoodIcon}>
|
||||
<Ionicons name="alert-circle-outline" size={48} color="#FF9800" />
|
||||
</View>
|
||||
<Text style={styles.nonFoodTitle}>未识别到食物</Text>
|
||||
<Text style={styles.nonFoodTitle}>{t('foodAnalysisResult.nonFood.title')}</Text>
|
||||
<Text style={styles.nonFoodMessage}>
|
||||
{recognitionResult.nonFoodMessage || recognitionResult.analysisText}
|
||||
</Text>
|
||||
<View style={styles.nonFoodSuggestions}>
|
||||
<Text style={styles.nonFoodSuggestionsTitle}>建议:</Text>
|
||||
<Text style={styles.nonFoodSuggestionItem}>• 确保图片中包含食物</Text>
|
||||
<Text style={styles.nonFoodSuggestionItem}>• 尝试更清晰的照片角度</Text>
|
||||
<Text style={styles.nonFoodSuggestionItem}>• 避免过度模糊或光线不足</Text>
|
||||
<Text style={styles.nonFoodSuggestionsTitle}>{t('foodAnalysisResult.nonFood.suggestions.title')}</Text>
|
||||
<Text style={styles.nonFoodSuggestionItem}>{t('foodAnalysisResult.nonFood.suggestions.item1')}</Text>
|
||||
<Text style={styles.nonFoodSuggestionItem}>{t('foodAnalysisResult.nonFood.suggestions.item2')}</Text>
|
||||
<Text style={styles.nonFoodSuggestionItem}>{t('foodAnalysisResult.nonFood.suggestions.item3')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
@@ -411,7 +414,7 @@ export default function FoodAnalysisResultScreen() {
|
||||
</View>
|
||||
|
||||
<View style={styles.foodIntakeCalories}>
|
||||
<Text style={styles.foodIntakeCaloriesValue}>{item.calories}千卡</Text>
|
||||
<Text style={styles.foodIntakeCaloriesValue}>{item.calories}{t('foodAnalysisResult.nutrients.caloriesUnit')}</Text>
|
||||
{shouldHideRecordBar ? null : <TouchableOpacity
|
||||
style={styles.editButton}
|
||||
onPress={() => handleEditFood(item)}
|
||||
@@ -442,7 +445,7 @@ export default function FoodAnalysisResultScreen() {
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Ionicons name="camera-outline" size={20} color={Colors.light.onPrimary} style={{ marginRight: 8 }} />
|
||||
<Text style={styles.retakePhotoButtonText}>重新拍照</Text>
|
||||
<Text style={styles.retakePhotoButtonText}>{t('foodAnalysisResult.actions.retake')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
@@ -471,7 +474,7 @@ export default function FoodAnalysisResultScreen() {
|
||||
{isRecording ? (
|
||||
<ActivityIndicator size="small" color="#FFF" />
|
||||
) : (
|
||||
<Text style={styles.recordButtonText}>记录</Text>
|
||||
<Text style={styles.recordButtonText}>{t('foodAnalysisResult.actions.record')}</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -492,7 +495,7 @@ export default function FoodAnalysisResultScreen() {
|
||||
/>
|
||||
<View style={styles.mealSelectorModal}>
|
||||
<View style={styles.mealSelectorHeader}>
|
||||
<Text style={styles.mealSelectorTitle}>选择餐次</Text>
|
||||
<Text style={styles.mealSelectorTitle}>{t('foodAnalysisResult.mealSelector.title')}</Text>
|
||||
<TouchableOpacity onPress={() => setShowMealSelector(false)}>
|
||||
<Ionicons name="close" size={24} color="#666" />
|
||||
</TouchableOpacity>
|
||||
@@ -539,8 +542,8 @@ export default function FoodAnalysisResultScreen() {
|
||||
<View style={styles.imageViewerHeader}>
|
||||
<Text style={styles.imageViewerHeaderText}>
|
||||
{recognitionResult ?
|
||||
`置信度: ${recognitionResult.confidence}%` :
|
||||
dayjs().format('YYYY年M月D日 HH:mm')
|
||||
t('foodAnalysisResult.confidence', { value: recognitionResult.confidence }) :
|
||||
dayjs().format(t('foodAnalysisResult.dateFormats.full'))
|
||||
}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -551,7 +554,7 @@ export default function FoodAnalysisResultScreen() {
|
||||
style={styles.imageViewerFooterButton}
|
||||
onPress={() => setShowImagePreview(false)}
|
||||
>
|
||||
<Text style={styles.imageViewerFooterButtonText}>关闭</Text>
|
||||
<Text style={styles.imageViewerFooterButtonText}>{t('foodAnalysisResult.actions.close')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
@@ -587,6 +590,8 @@ function FoodEditModal({
|
||||
onFormDataChange({ ...formData, [field]: value });
|
||||
};
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
@@ -598,14 +603,14 @@ function FoodEditModal({
|
||||
<View style={styles.editModalSheet}>
|
||||
<View style={styles.modalHandle} />
|
||||
|
||||
<Text style={styles.modalTitle}>编辑食物信息</Text>
|
||||
<Text style={styles.modalTitle}>{t('foodAnalysisResult.editModal.title')}</Text>
|
||||
|
||||
{/* 食物名称 */}
|
||||
<View style={styles.editFieldContainer}>
|
||||
<Text style={styles.editFieldLabel}>食物名称</Text>
|
||||
<Text style={styles.editFieldLabel}>{t('foodAnalysisResult.editModal.fields.name')}</Text>
|
||||
<TextInput
|
||||
style={styles.editInput}
|
||||
placeholder="输入食物名称"
|
||||
placeholder={t('foodAnalysisResult.editModal.fields.namePlaceholder')}
|
||||
placeholderTextColor="#999"
|
||||
value={formData.name}
|
||||
onChangeText={(value) => handleFieldChange('name', value)}
|
||||
@@ -615,10 +620,10 @@ function FoodEditModal({
|
||||
|
||||
{/* 重量/数量 */}
|
||||
<View style={styles.editFieldContainer}>
|
||||
<Text style={styles.editFieldLabel}>重量 (克)</Text>
|
||||
<Text style={styles.editFieldLabel}>{t('foodAnalysisResult.editModal.fields.amount')}</Text>
|
||||
<TextInput
|
||||
style={styles.editInput}
|
||||
placeholder="输入重量"
|
||||
placeholder={t('foodAnalysisResult.editModal.fields.amountPlaceholder')}
|
||||
placeholderTextColor="#999"
|
||||
value={formData.amount}
|
||||
onChangeText={(value) => handleFieldChange('amount', value)}
|
||||
@@ -628,10 +633,10 @@ function FoodEditModal({
|
||||
|
||||
{/* 卡路里 */}
|
||||
<View style={styles.editFieldContainer}>
|
||||
<Text style={styles.editFieldLabel}>卡路里 (千卡)</Text>
|
||||
<Text style={styles.editFieldLabel}>{t('foodAnalysisResult.editModal.fields.calories')}</Text>
|
||||
<TextInput
|
||||
style={styles.editInput}
|
||||
placeholder="输入卡路里"
|
||||
placeholder={t('foodAnalysisResult.editModal.fields.caloriesPlaceholder')}
|
||||
placeholderTextColor="#999"
|
||||
value={formData.calories}
|
||||
onChangeText={(value) => handleFieldChange('calories', value)}
|
||||
@@ -645,13 +650,13 @@ function FoodEditModal({
|
||||
onPress={onClose}
|
||||
style={styles.modalCancelBtn}
|
||||
>
|
||||
<Text style={styles.modalCancelText}>取消</Text>
|
||||
<Text style={styles.modalCancelText}>{t('foodAnalysisResult.editModal.actions.cancel')}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={onSave}
|
||||
style={[styles.modalSaveBtn, { backgroundColor: Colors.light.primary }]}
|
||||
>
|
||||
<Text style={[styles.modalSaveText, { color: Colors.light.onPrimary }]}>保存</Text>
|
||||
<Text style={[styles.modalSaveText, { color: Colors.light.onPrimary }]}>{t('foodAnalysisResult.editModal.actions.save')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||
import {
|
||||
deleteNutritionAnalysisRecord,
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
import ImageViewing from 'react-native-image-viewing';
|
||||
|
||||
export default function NutritionAnalysisHistoryScreen() {
|
||||
const { t } = useI18n();
|
||||
const safeAreaTop = useSafeAreaTop();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -95,15 +97,15 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
setHasMore(page < response.data.totalPages);
|
||||
setCurrentPage(page);
|
||||
} else {
|
||||
const errorMessage = response.message || '获取历史记录失败';
|
||||
const errorMessage = response.message || t('nutritionAnalysisHistory.errors.fetchFailed');
|
||||
setError(errorMessage);
|
||||
Alert.alert('错误', errorMessage);
|
||||
Alert.alert(t('nutritionAnalysisHistory.errors.error'), errorMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[HISTORY] 获取历史记录失败:', error);
|
||||
const errorMessage = '获取历史记录失败,请稍后重试';
|
||||
const errorMessage = t('nutritionAnalysisHistory.errors.fetchFailedRetry');
|
||||
setError(errorMessage);
|
||||
Alert.alert('错误', errorMessage);
|
||||
Alert.alert(t('nutritionAnalysisHistory.errors.error'), errorMessage);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
@@ -173,13 +175,13 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
case 'success':
|
||||
return '成功';
|
||||
return t('nutritionAnalysisHistory.status.success');
|
||||
case 'failed':
|
||||
return '失败';
|
||||
return t('nutritionAnalysisHistory.status.failed');
|
||||
case 'processing':
|
||||
return '处理中';
|
||||
return t('nutritionAnalysisHistory.status.processing');
|
||||
default:
|
||||
return '未知';
|
||||
return t('nutritionAnalysisHistory.status.unknown');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -208,15 +210,15 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
// 处理删除记录
|
||||
const handleDeleteRecord = useCallback((recordId: number) => {
|
||||
Alert.alert(
|
||||
'确认删除',
|
||||
'确定要删除这条营养分析记录吗?此操作无法撤销。',
|
||||
t('nutritionAnalysisHistory.delete.confirmTitle'),
|
||||
t('nutritionAnalysisHistory.delete.confirmMessage'),
|
||||
[
|
||||
{
|
||||
text: '取消',
|
||||
text: t('nutritionAnalysisHistory.delete.cancel'),
|
||||
style: 'cancel',
|
||||
},
|
||||
{
|
||||
text: '删除',
|
||||
text: t('nutritionAnalysisHistory.delete.delete'),
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
try {
|
||||
@@ -231,10 +233,10 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
triggerLightHaptic();
|
||||
|
||||
// 显示成功提示
|
||||
Alert.alert('成功', '记录已删除');
|
||||
Alert.alert(t('nutritionAnalysisHistory.delete.successTitle'), t('nutritionAnalysisHistory.delete.successMessage'));
|
||||
} catch (error) {
|
||||
console.error('[HISTORY] 删除记录失败:', error);
|
||||
Alert.alert('错误', '删除失败,请稍后重试');
|
||||
Alert.alert(t('nutritionAnalysisHistory.errors.error'), t('nutritionAnalysisHistory.errors.deleteFailed'));
|
||||
} finally {
|
||||
setDeletingId(null);
|
||||
}
|
||||
@@ -256,11 +258,11 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
<View style={styles.recordInfo}>
|
||||
{isSuccess && (
|
||||
<Text style={styles.recordTitle}>
|
||||
识别 {item.nutritionCount} 项营养素
|
||||
{t('nutritionAnalysisHistory.recognized', { count: item.nutritionCount })}
|
||||
</Text>
|
||||
)}
|
||||
<Text style={styles.recordDate}>
|
||||
{dayjs(item.createdAt).format('YYYY年M月D日 HH:mm')}
|
||||
{dayjs(item.createdAt).format(t('nutritionAnalysisHistory.dateFormat'))}
|
||||
</Text>
|
||||
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(item.status) }]}>
|
||||
<Text style={styles.statusText}>{getStatusText(item.status)}</Text>
|
||||
@@ -327,25 +329,25 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
<>
|
||||
{mainNutrients.energy && (
|
||||
<View style={styles.nutritionItem}>
|
||||
<Text style={styles.nutritionLabel}>热量</Text>
|
||||
<Text style={styles.nutritionLabel}>{t('nutritionAnalysisHistory.nutrients.energy')}</Text>
|
||||
<Text style={styles.nutritionValue}>{mainNutrients.energy}</Text>
|
||||
</View>
|
||||
)}
|
||||
{mainNutrients.protein && (
|
||||
<View style={styles.nutritionItem}>
|
||||
<Text style={styles.nutritionLabel}>蛋白质</Text>
|
||||
<Text style={styles.nutritionLabel}>{t('nutritionAnalysisHistory.nutrients.protein')}</Text>
|
||||
<Text style={styles.nutritionValue}>{mainNutrients.protein}</Text>
|
||||
</View>
|
||||
)}
|
||||
{mainNutrients.carbs && (
|
||||
<View style={styles.nutritionItem}>
|
||||
<Text style={styles.nutritionLabel}>碳水</Text>
|
||||
<Text style={styles.nutritionLabel}>{t('nutritionAnalysisHistory.nutrients.carbs')}</Text>
|
||||
<Text style={styles.nutritionValue}>{mainNutrients.carbs}</Text>
|
||||
</View>
|
||||
)}
|
||||
{mainNutrients.fat && (
|
||||
<View style={styles.nutritionItem}>
|
||||
<Text style={styles.nutritionLabel}>脂肪</Text>
|
||||
<Text style={styles.nutritionLabel}>{t('nutritionAnalysisHistory.nutrients.fat')}</Text>
|
||||
<Text style={styles.nutritionValue}>{mainNutrients.fat}</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -371,7 +373,7 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.expandButtonText}>
|
||||
{isExpanded ? '收起详情' : '展开详情'}
|
||||
{isExpanded ? t('nutritionAnalysisHistory.actions.collapse') : t('nutritionAnalysisHistory.actions.expand')}
|
||||
</Text>
|
||||
<Ionicons
|
||||
name={isExpanded ? 'chevron-up-outline' : 'chevron-down-outline'}
|
||||
@@ -383,7 +385,7 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
{/* 详细信息 */}
|
||||
{isExpanded && isSuccess && item.analysisResult && item.analysisResult.data && (
|
||||
<View style={styles.detailsContainer}>
|
||||
<Text style={styles.detailsTitle}>详细营养成分</Text>
|
||||
<Text style={styles.detailsTitle}>{t('nutritionAnalysisHistory.details.title')}</Text>
|
||||
{item.analysisResult.data.map((nutritionItem: NutritionItem) => (
|
||||
<View key={nutritionItem.key} style={styles.detailItem}>
|
||||
<View style={styles.nutritionInfo}>
|
||||
@@ -397,8 +399,8 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
))}
|
||||
|
||||
<View style={styles.metaInfo}>
|
||||
<Text style={styles.metaText}>AI 模型: {item.aiModel}</Text>
|
||||
<Text style={styles.metaText}>服务提供商: {item.aiProvider}</Text>
|
||||
<Text style={styles.metaText}>{t('nutritionAnalysisHistory.details.aiModel')}: {item.aiModel}</Text>
|
||||
<Text style={styles.metaText}>{t('nutritionAnalysisHistory.details.provider')}: {item.aiProvider}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
@@ -410,8 +412,8 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
const renderEmptyState = () => (
|
||||
<View style={styles.emptyState}>
|
||||
<Ionicons name="document-text-outline" size={64} color="#CCC" />
|
||||
<Text style={styles.emptyStateText}>暂无历史记录</Text>
|
||||
<Text style={styles.emptyStateSubtext}>开始识别营养成分表吧</Text>
|
||||
<Text style={styles.emptyStateText}>{t('nutritionAnalysisHistory.empty.title')}</Text>
|
||||
<Text style={styles.emptyStateSubtext}>{t('nutritionAnalysisHistory.empty.subtitle')}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -419,8 +421,8 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
const renderErrorState = () => (
|
||||
<View style={styles.errorState}>
|
||||
<Ionicons name="alert-circle-outline" size={64} color="#F44336" />
|
||||
<Text style={styles.errorStateText}>加载失败</Text>
|
||||
<Text style={styles.errorStateSubtext}>{error || '未知错误'}</Text>
|
||||
<Text style={styles.errorStateText}>{t('nutritionAnalysisHistory.errors.loadFailed')}</Text>
|
||||
<Text style={styles.errorStateSubtext}>{error || t('nutritionAnalysisHistory.errors.unknownError')}</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.retryButton}
|
||||
onPress={() => {
|
||||
@@ -428,7 +430,7 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
fetchRecords(1, true);
|
||||
}}
|
||||
>
|
||||
<Text style={styles.retryButtonText}>重试</Text>
|
||||
<Text style={styles.retryButtonText}>{t('nutritionAnalysisHistory.actions.retry')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
@@ -440,7 +442,7 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
return (
|
||||
<View style={styles.loadingFooter}>
|
||||
<ActivityIndicator size="small" color={Colors.light.primary} />
|
||||
<Text style={styles.loadingFooterText}>加载更多...</Text>
|
||||
<Text style={styles.loadingFooterText}>{t('nutritionAnalysisHistory.loadingMore')}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -456,7 +458,7 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
/>
|
||||
|
||||
<HeaderBar
|
||||
title="历史记录"
|
||||
title={t('nutritionAnalysisHistory.title')}
|
||||
onBack={() => router.back()}
|
||||
transparent={true}
|
||||
/>
|
||||
@@ -477,7 +479,7 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={[styles.filterButtonText, !statusFilter && styles.filterButtonTextActive]}>
|
||||
全部
|
||||
{t('nutritionAnalysisHistory.filter.all')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
@@ -494,7 +496,7 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={[styles.filterButtonText, statusFilter === 'success' && styles.filterButtonTextActive]}>
|
||||
成功
|
||||
{t('nutritionAnalysisHistory.status.success')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
@@ -511,7 +513,7 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={[styles.filterButtonText, statusFilter === 'failed' && styles.filterButtonTextActive]}>
|
||||
失败
|
||||
{t('nutritionAnalysisHistory.status.failed')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -520,7 +522,7 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
{loading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={Colors.light.primary} />
|
||||
<Text style={styles.loadingText}>加载历史记录...</Text>
|
||||
<Text style={styles.loadingText}>{t('nutritionAnalysisHistory.loading')}</Text>
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
@@ -555,7 +557,7 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
HeaderComponent={() => (
|
||||
<View style={styles.imageViewerHeader}>
|
||||
<Text style={styles.imageViewerHeaderText}>
|
||||
{dayjs().format('YYYY年M月D日 HH:mm')}
|
||||
{dayjs().format(t('nutritionAnalysisHistory.dateFormat'))}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -565,7 +567,7 @@ export default function NutritionAnalysisHistoryScreen() {
|
||||
style={styles.imageViewerFooterButton}
|
||||
onPress={() => setShowImagePreview(false)}
|
||||
>
|
||||
<Text style={styles.imageViewerFooterButtonText}>关闭</Text>
|
||||
<Text style={styles.imageViewerFooterButtonText}>{t('nutritionLabelAnalysis.actions.close')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { useCosUpload } from '@/hooks/useCosUpload';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||
import {
|
||||
analyzeNutritionImage,
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
import ImageViewing from 'react-native-image-viewing';
|
||||
|
||||
export default function NutritionLabelAnalysisScreen() {
|
||||
const { t } = useI18n();
|
||||
const safeAreaTop = useSafeAreaTop();
|
||||
const router = useRouter();
|
||||
const { pushIfAuthedElseLogin, ensureLoggedIn } = useAuthGuard();
|
||||
@@ -77,7 +79,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
const requestCameraPermission = async () => {
|
||||
const { status } = await ImagePicker.requestCameraPermissionsAsync();
|
||||
if (status !== 'granted') {
|
||||
Alert.alert('权限不足', '需要相机权限才能拍摄成分表');
|
||||
Alert.alert(t('nutritionLabelAnalysis.camera.permissionDenied'), t('nutritionLabelAnalysis.camera.permissionMessage'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -153,7 +155,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
// 直接使用服务端返回的数据,不做任何转换
|
||||
setNewAnalysisResult(analysisResponse);
|
||||
} else {
|
||||
throw new Error(analysisResponse.message || '分析失败');
|
||||
throw new Error(analysisResponse.message || t('nutritionLabelAnalysis.errors.analysisFailed.defaultMessage'));
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('[NUTRITION_ANALYSIS] 新API分析失败:', error);
|
||||
@@ -162,8 +164,8 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
|
||||
// 显示错误提示
|
||||
Alert.alert(
|
||||
'分析失败',
|
||||
error.message || '无法识别成分表,请尝试拍摄更清晰的照片'
|
||||
t('nutritionLabelAnalysis.errors.analysisFailed.title'),
|
||||
error.message || t('nutritionLabelAnalysis.errors.analysisFailed.message')
|
||||
);
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
@@ -182,7 +184,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
/>
|
||||
|
||||
<HeaderBar
|
||||
title="成分表分析"
|
||||
title={t('nutritionLabelAnalysis.title')}
|
||||
onBack={() => router.back()}
|
||||
transparent={true}
|
||||
right={
|
||||
@@ -253,7 +255,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Ionicons name="search-outline" size={20} color="#FFF" />
|
||||
<Text style={styles.analyzeButtonText}>开始分析</Text>
|
||||
<Text style={styles.analyzeButtonText}>{t('nutritionLabelAnalysis.actions.startAnalysis')}</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
@@ -274,7 +276,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
<View style={styles.placeholderContainer}>
|
||||
<View style={styles.placeholderContent}>
|
||||
<Ionicons name="document-text-outline" size={48} color="#666" />
|
||||
<Text style={styles.placeholderText}>拍摄或选择成分表照片</Text>
|
||||
<Text style={styles.placeholderText}>{t('nutritionLabelAnalysis.placeholder.text')}</Text>
|
||||
</View>
|
||||
{/* 操作按钮区域 */}
|
||||
<View style={styles.imageActionButtonsContainer}>
|
||||
@@ -284,7 +286,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Ionicons name="camera-outline" size={20} color={Colors.light.onPrimary} />
|
||||
<Text style={styles.imageActionButtonText}>拍摄</Text>
|
||||
<Text style={styles.imageActionButtonText}>{t('nutritionLabelAnalysis.actions.takePhoto')}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.imageActionButton, styles.imageActionButtonSecondary]}
|
||||
@@ -292,7 +294,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Ionicons name="image-outline" size={20} color={Colors.light.primary} />
|
||||
<Text style={[styles.imageActionButtonText, { color: Colors.light.primary }]}>相册</Text>
|
||||
<Text style={[styles.imageActionButtonText, { color: Colors.light.primary }]}>{t('nutritionLabelAnalysis.actions.selectFromAlbum')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@@ -307,7 +309,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
<View style={styles.analysisSectionHeaderIcon}>
|
||||
<Ionicons name="document-text-outline" size={18} color="#6B6ED6" />
|
||||
</View>
|
||||
<Text style={styles.analysisSectionTitle}>营养成分详细分析</Text>
|
||||
<Text style={styles.analysisSectionTitle}>{t('nutritionLabelAnalysis.results.title')}</Text>
|
||||
</View>
|
||||
<View style={styles.analysisCardsWrapper}>
|
||||
{newAnalysisResult.data.map((item, index) => (
|
||||
@@ -352,7 +354,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={Colors.light.primary} />
|
||||
<Text style={styles.loadingText}>
|
||||
正在上传图片... {uploadProgress > 0 ? `${Math.round(uploadProgress)}%` : ''}
|
||||
{t('nutritionLabelAnalysis.status.uploading')} {uploadProgress > 0 ? `${Math.round(uploadProgress)}%` : ''}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -361,7 +363,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
{isAnalyzing && !newAnalysisResult && !isUploading && (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={Colors.light.primary} />
|
||||
<Text style={styles.loadingText}>正在分析成分表...</Text>
|
||||
<Text style={styles.loadingText}>{t('nutritionLabelAnalysis.status.analyzing')}</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
@@ -377,7 +379,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
HeaderComponent={() => (
|
||||
<View style={styles.imageViewerHeader}>
|
||||
<Text style={styles.imageViewerHeaderText}>
|
||||
{dayjs().format('YYYY年M月D日 HH:mm')}
|
||||
{dayjs().format(t('nutritionLabelAnalysis.imageViewer.dateFormat'))}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -387,7 +389,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
style={styles.imageViewerFooterButton}
|
||||
onPress={() => setShowImagePreview(false)}
|
||||
>
|
||||
<Text style={styles.imageViewerFooterButtonText}>关闭</Text>
|
||||
<Text style={styles.imageViewerFooterButtonText}>{t('nutritionLabelAnalysis.actions.close')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
@@ -514,7 +516,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
imageActionButtonText: {
|
||||
color: Colors.light.onPrimary,
|
||||
fontSize: 14,
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
marginLeft: 6,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user