- 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.
904 lines
27 KiB
TypeScript
904 lines
27 KiB
TypeScript
import { HeaderBar } from '@/components/ui/HeaderBar';
|
|
import { Colors } from '@/constants/Colors';
|
|
import { useI18n } from '@/hooks/useI18n';
|
|
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
|
import {
|
|
deleteNutritionAnalysisRecord,
|
|
getNutritionAnalysisRecords,
|
|
type GetNutritionRecordsParams,
|
|
type NutritionAnalysisRecord,
|
|
type NutritionItem
|
|
} from '@/services/nutritionLabelAnalysis';
|
|
import { triggerLightHaptic } from '@/utils/haptics';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import dayjs from 'dayjs';
|
|
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
|
|
import { Image } from 'expo-image';
|
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
import { useRouter } from 'expo-router';
|
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
import {
|
|
ActivityIndicator,
|
|
Alert,
|
|
BackHandler,
|
|
FlatList,
|
|
RefreshControl,
|
|
StyleSheet,
|
|
Text,
|
|
TouchableOpacity,
|
|
View
|
|
} from 'react-native';
|
|
import ImageViewing from 'react-native-image-viewing';
|
|
|
|
export default function NutritionAnalysisHistoryScreen() {
|
|
const { t } = useI18n();
|
|
const safeAreaTop = useSafeAreaTop();
|
|
const router = useRouter();
|
|
|
|
const [records, setRecords] = useState<NutritionAnalysisRecord[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
const [loadingMore, setLoadingMore] = useState(false);
|
|
const [expandedItems, setExpandedItems] = useState<Set<number>>(new Set());
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [hasMore, setHasMore] = useState(true);
|
|
const [total, setTotal] = useState(0);
|
|
const [statusFilter, setStatusFilter] = useState<string>('');
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [showImagePreview, setShowImagePreview] = useState(false);
|
|
const [previewImageUri, setPreviewImageUri] = useState<string | null>(null);
|
|
const [deletingId, setDeletingId] = useState<number | null>(null);
|
|
const isGlassAvailable = isLiquidGlassAvailable();
|
|
|
|
// 处理Android返回键关闭图片预览
|
|
useEffect(() => {
|
|
const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
|
|
if (showImagePreview) {
|
|
setShowImagePreview(false);
|
|
return true; // 阻止默认返回行为
|
|
}
|
|
return false;
|
|
});
|
|
|
|
return () => backHandler.remove();
|
|
}, [showImagePreview]);
|
|
|
|
// 获取历史记录
|
|
const fetchRecords = useCallback(async (page: number = 1, isRefresh: boolean = false, currentStatusFilter?: string) => {
|
|
try {
|
|
// 清除之前的错误
|
|
setError(null);
|
|
|
|
const params: GetNutritionRecordsParams = {
|
|
page,
|
|
limit: 20,
|
|
};
|
|
|
|
// 使用传入的筛选条件或当前状态
|
|
const filterToUse = currentStatusFilter !== undefined ? currentStatusFilter : statusFilter;
|
|
if (filterToUse) {
|
|
params.status = filterToUse;
|
|
}
|
|
|
|
const response = await getNutritionAnalysisRecords(params);
|
|
|
|
console.log('response', JSON.stringify(response));
|
|
|
|
if (response.code === 0) {
|
|
const newRecords = response.data.records;
|
|
|
|
if (isRefresh || page === 1) {
|
|
setRecords(newRecords);
|
|
} else {
|
|
setRecords(prev => [...prev, ...newRecords]);
|
|
}
|
|
|
|
setTotal(response.data.total);
|
|
setHasMore(page < response.data.totalPages);
|
|
setCurrentPage(page);
|
|
} else {
|
|
const errorMessage = response.message || t('nutritionAnalysisHistory.errors.fetchFailed');
|
|
setError(errorMessage);
|
|
Alert.alert(t('nutritionAnalysisHistory.errors.error'), errorMessage);
|
|
}
|
|
} catch (error) {
|
|
console.error('[HISTORY] 获取历史记录失败:', error);
|
|
const errorMessage = t('nutritionAnalysisHistory.errors.fetchFailedRetry');
|
|
setError(errorMessage);
|
|
Alert.alert(t('nutritionAnalysisHistory.errors.error'), errorMessage);
|
|
} finally {
|
|
setLoading(false);
|
|
setRefreshing(false);
|
|
setLoadingMore(false);
|
|
}
|
|
}, [statusFilter]);
|
|
|
|
// 初始加载 - 只在组件挂载时执行一次
|
|
useEffect(() => {
|
|
setLoading(true);
|
|
fetchRecords(1, true);
|
|
}, []); // 移除 fetchRecords 依赖,避免循环
|
|
|
|
// 筛选条件变化时的处理
|
|
useEffect(() => {
|
|
// 只有在非初始加载时才执行
|
|
if (!loading) {
|
|
setLoading(true);
|
|
setCurrentPage(1);
|
|
fetchRecords(1, true, statusFilter);
|
|
}
|
|
}, [statusFilter]); // 只依赖 statusFilter
|
|
|
|
// 下拉刷新
|
|
const handleRefresh = useCallback(() => {
|
|
setRefreshing(true);
|
|
fetchRecords(1, true);
|
|
}, []); // 移除 fetchRecords 依赖
|
|
|
|
// 加载更多
|
|
const handleLoadMore = useCallback(() => {
|
|
if (!hasMore || loadingMore || loading || error) return; // 添加错误状态检查
|
|
|
|
setLoadingMore(true);
|
|
fetchRecords(currentPage + 1, false);
|
|
}, [hasMore, loadingMore, loading, currentPage, error]); // 移除 fetchRecords 依赖,添加 error 依赖
|
|
|
|
// 切换展开状态
|
|
const toggleExpanded = useCallback((id: number) => {
|
|
triggerLightHaptic();
|
|
setExpandedItems(prev => {
|
|
const newSet = new Set(prev);
|
|
if (newSet.has(id)) {
|
|
newSet.delete(id);
|
|
} else {
|
|
newSet.add(id);
|
|
}
|
|
return newSet;
|
|
});
|
|
}, []);
|
|
|
|
// 获取状态颜色
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'success':
|
|
return '#4CAF50';
|
|
case 'failed':
|
|
return '#F44336';
|
|
case 'processing':
|
|
return '#FF9800';
|
|
default:
|
|
return '#9E9E9E';
|
|
}
|
|
};
|
|
|
|
// 获取状态文本
|
|
const getStatusText = (status: string) => {
|
|
switch (status) {
|
|
case 'success':
|
|
return t('nutritionAnalysisHistory.status.success');
|
|
case 'failed':
|
|
return t('nutritionAnalysisHistory.status.failed');
|
|
case 'processing':
|
|
return t('nutritionAnalysisHistory.status.processing');
|
|
default:
|
|
return t('nutritionAnalysisHistory.status.unknown');
|
|
}
|
|
};
|
|
|
|
// 从营养数据中提取主要营养素的辅助函数
|
|
const getMainNutrients = (data: NutritionItem[]) => {
|
|
const energy = data.find(item => item.key === 'energy_kcal');
|
|
const protein = data.find(item => item.key === 'protein');
|
|
const carbs = data.find(item => item.key === 'carbohydrate');
|
|
const fat = data.find(item => item.key === 'fat');
|
|
|
|
return {
|
|
energy: energy?.value || '',
|
|
protein: protein?.value || '',
|
|
carbs: carbs?.value || '',
|
|
fat: fat?.value || ''
|
|
};
|
|
};
|
|
|
|
// 处理图片预览
|
|
const handleImagePreview = useCallback((imageUrl: string) => {
|
|
triggerLightHaptic();
|
|
setPreviewImageUri(imageUrl);
|
|
setShowImagePreview(true);
|
|
}, []);
|
|
|
|
// 处理删除记录
|
|
const handleDeleteRecord = useCallback((recordId: number) => {
|
|
Alert.alert(
|
|
t('nutritionAnalysisHistory.delete.confirmTitle'),
|
|
t('nutritionAnalysisHistory.delete.confirmMessage'),
|
|
[
|
|
{
|
|
text: t('nutritionAnalysisHistory.delete.cancel'),
|
|
style: 'cancel',
|
|
},
|
|
{
|
|
text: t('nutritionAnalysisHistory.delete.delete'),
|
|
style: 'destructive',
|
|
onPress: async () => {
|
|
try {
|
|
setDeletingId(recordId);
|
|
await deleteNutritionAnalysisRecord(recordId);
|
|
|
|
// 从本地状态中移除删除的记录
|
|
setRecords(prev => prev.filter(record => record.id !== recordId));
|
|
setTotal(prev => Math.max(0, prev - 1));
|
|
|
|
// 触发轻微震动反馈
|
|
triggerLightHaptic();
|
|
|
|
// 显示成功提示
|
|
Alert.alert(t('nutritionAnalysisHistory.delete.successTitle'), t('nutritionAnalysisHistory.delete.successMessage'));
|
|
} catch (error) {
|
|
console.error('[HISTORY] 删除记录失败:', error);
|
|
Alert.alert(t('nutritionAnalysisHistory.errors.error'), t('nutritionAnalysisHistory.errors.deleteFailed'));
|
|
} finally {
|
|
setDeletingId(null);
|
|
}
|
|
},
|
|
},
|
|
]
|
|
);
|
|
}, []);
|
|
|
|
// 渲染历史记录项
|
|
const renderRecordItem = useCallback(({ item }: { item: NutritionAnalysisRecord }) => {
|
|
const isExpanded = expandedItems.has(item.id);
|
|
const isSuccess = item.status === 'success';
|
|
|
|
return (
|
|
<View style={styles.recordItem}>
|
|
{/* 头部信息 */}
|
|
<View style={styles.recordHeader}>
|
|
<View style={styles.recordInfo}>
|
|
{isSuccess && (
|
|
<Text style={styles.recordTitle}>
|
|
{t('nutritionAnalysisHistory.recognized', { count: item.nutritionCount })}
|
|
</Text>
|
|
)}
|
|
<Text style={styles.recordDate}>
|
|
{dayjs(item.createdAt).format(t('nutritionAnalysisHistory.dateFormat'))}
|
|
</Text>
|
|
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(item.status) }]}>
|
|
<Text style={styles.statusText}>{getStatusText(item.status)}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 删除按钮 */}
|
|
<TouchableOpacity
|
|
onPress={() => handleDeleteRecord(item.id)}
|
|
disabled={deletingId === item.id}
|
|
activeOpacity={0.7}
|
|
>
|
|
{isGlassAvailable ? (
|
|
<GlassView
|
|
style={styles.glassDeleteButton}
|
|
glassEffectStyle="clear"
|
|
tintColor="rgba(244, 67, 54, 0.2)"
|
|
isInteractive={true}
|
|
>
|
|
{deletingId === item.id ? (
|
|
<ActivityIndicator size="small" color="#F44336" />
|
|
) : (
|
|
<Ionicons name="trash-outline" size={20} color="#F44336" />
|
|
)}
|
|
</GlassView>
|
|
) : (
|
|
<View style={[styles.glassDeleteButton, styles.fallbackDeleteButton]}>
|
|
{deletingId === item.id ? (
|
|
<ActivityIndicator size="small" color="#F44336" />
|
|
) : (
|
|
<Ionicons name="trash-outline" size={20} color="#F44336" />
|
|
)}
|
|
</View>
|
|
)}
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* 图片预览 */}
|
|
{item.imageUrl && (
|
|
<TouchableOpacity
|
|
style={styles.imageContainer}
|
|
onPress={() => handleImagePreview(item.imageUrl)}
|
|
activeOpacity={0.9}
|
|
>
|
|
<Image
|
|
source={{ uri: item.imageUrl }}
|
|
style={styles.thumbnail}
|
|
contentFit="cover"
|
|
/>
|
|
{/* 预览提示图标 */}
|
|
<View style={styles.previewHint}>
|
|
<Ionicons name="expand-outline" size={16} color="#FFF" />
|
|
</View>
|
|
</TouchableOpacity>
|
|
)}
|
|
|
|
{/* 分析结果摘要 */}
|
|
{isSuccess && item.analysisResult && item.analysisResult.data && item.analysisResult.data.length > 0 && (
|
|
<View style={styles.summaryContainer}>
|
|
<View style={styles.nutritionSummary}>
|
|
{(() => {
|
|
const mainNutrients = getMainNutrients(item.analysisResult.data);
|
|
return (
|
|
<>
|
|
{mainNutrients.energy && (
|
|
<View style={styles.nutritionItem}>
|
|
<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}>{t('nutritionAnalysisHistory.nutrients.protein')}</Text>
|
|
<Text style={styles.nutritionValue}>{mainNutrients.protein}</Text>
|
|
</View>
|
|
)}
|
|
{mainNutrients.carbs && (
|
|
<View style={styles.nutritionItem}>
|
|
<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}>{t('nutritionAnalysisHistory.nutrients.fat')}</Text>
|
|
<Text style={styles.nutritionValue}>{mainNutrients.fat}</Text>
|
|
</View>
|
|
)}
|
|
</>
|
|
);
|
|
})()}
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{/* 失败信息 */}
|
|
{!isSuccess && (
|
|
<View style={styles.errorContainer}>
|
|
<Ionicons name="alert-circle-outline" size={20} color="#F44336" />
|
|
<Text style={styles.errorMessage}>{item.message}</Text>
|
|
</View>
|
|
)}
|
|
|
|
{/* 展开/收起按钮 */}
|
|
<TouchableOpacity
|
|
style={styles.expandButton}
|
|
onPress={() => toggleExpanded(item.id)}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={styles.expandButtonText}>
|
|
{isExpanded ? t('nutritionAnalysisHistory.actions.collapse') : t('nutritionAnalysisHistory.actions.expand')}
|
|
</Text>
|
|
<Ionicons
|
|
name={isExpanded ? 'chevron-up-outline' : 'chevron-down-outline'}
|
|
size={16}
|
|
color={Colors.light.primary}
|
|
/>
|
|
</TouchableOpacity>
|
|
|
|
{/* 详细信息 */}
|
|
{isExpanded && isSuccess && item.analysisResult && item.analysisResult.data && (
|
|
<View style={styles.detailsContainer}>
|
|
<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}>
|
|
<Text style={styles.detailLabel}>{nutritionItem.name}</Text>
|
|
<Text style={styles.detailValue}>{nutritionItem.value}</Text>
|
|
</View>
|
|
{nutritionItem.analysis && (
|
|
<Text style={styles.analysisText}>{nutritionItem.analysis}</Text>
|
|
)}
|
|
</View>
|
|
))}
|
|
|
|
<View style={styles.metaInfo}>
|
|
<Text style={styles.metaText}>{t('nutritionAnalysisHistory.details.aiModel')}: {item.aiModel}</Text>
|
|
<Text style={styles.metaText}>{t('nutritionAnalysisHistory.details.provider')}: {item.aiProvider}</Text>
|
|
</View>
|
|
</View>
|
|
)}
|
|
</View>
|
|
);
|
|
}, [expandedItems, toggleExpanded]);
|
|
|
|
// 渲染空状态
|
|
const renderEmptyState = () => (
|
|
<View style={styles.emptyState}>
|
|
<Ionicons name="document-text-outline" size={64} color="#CCC" />
|
|
<Text style={styles.emptyStateText}>{t('nutritionAnalysisHistory.empty.title')}</Text>
|
|
<Text style={styles.emptyStateSubtext}>{t('nutritionAnalysisHistory.empty.subtitle')}</Text>
|
|
</View>
|
|
);
|
|
|
|
// 渲染错误状态
|
|
const renderErrorState = () => (
|
|
<View style={styles.errorState}>
|
|
<Ionicons name="alert-circle-outline" size={64} color="#F44336" />
|
|
<Text style={styles.errorStateText}>{t('nutritionAnalysisHistory.errors.loadFailed')}</Text>
|
|
<Text style={styles.errorStateSubtext}>{error || t('nutritionAnalysisHistory.errors.unknownError')}</Text>
|
|
<TouchableOpacity
|
|
style={styles.retryButton}
|
|
onPress={() => {
|
|
setLoading(true);
|
|
fetchRecords(1, true);
|
|
}}
|
|
>
|
|
<Text style={styles.retryButtonText}>{t('nutritionAnalysisHistory.actions.retry')}</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
);
|
|
|
|
// 渲染底部加载指示器
|
|
const renderFooter = () => {
|
|
if (!loadingMore) return null;
|
|
|
|
return (
|
|
<View style={styles.loadingFooter}>
|
|
<ActivityIndicator size="small" color={Colors.light.primary} />
|
|
<Text style={styles.loadingFooterText}>{t('nutritionAnalysisHistory.loadingMore')}</Text>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{/* 背景渐变 */}
|
|
<LinearGradient
|
|
colors={['#f5e5fbff', '#e5fcfeff', '#eefdffff', '#ffffffff']}
|
|
style={styles.gradientBackground}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 0, y: 1 }}
|
|
/>
|
|
|
|
<HeaderBar
|
|
title={t('nutritionAnalysisHistory.title')}
|
|
onBack={() => router.back()}
|
|
transparent={true}
|
|
/>
|
|
|
|
{/* 筛选按钮 */}
|
|
<View style={[styles.filterContainer, { paddingTop: safeAreaTop }]}>
|
|
<TouchableOpacity
|
|
style={[styles.filterButton, !statusFilter && styles.filterButtonActive]}
|
|
onPress={() => {
|
|
if (statusFilter !== '') {
|
|
setStatusFilter('');
|
|
setCurrentPage(1);
|
|
// 直接调用数据获取,不依赖 useEffect
|
|
setLoading(true);
|
|
fetchRecords(1, true, '');
|
|
}
|
|
}}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={[styles.filterButtonText, !statusFilter && styles.filterButtonTextActive]}>
|
|
{t('nutritionAnalysisHistory.filter.all')}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={[styles.filterButton, statusFilter === 'success' && styles.filterButtonActive]}
|
|
onPress={() => {
|
|
if (statusFilter !== 'success') {
|
|
setStatusFilter('success');
|
|
setCurrentPage(1);
|
|
// 直接调用数据获取,不依赖 useEffect
|
|
setLoading(true);
|
|
fetchRecords(1, true, 'success');
|
|
}
|
|
}}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={[styles.filterButtonText, statusFilter === 'success' && styles.filterButtonTextActive]}>
|
|
{t('nutritionAnalysisHistory.status.success')}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
style={[styles.filterButton, statusFilter === 'failed' && styles.filterButtonActive]}
|
|
onPress={() => {
|
|
if (statusFilter !== 'failed') {
|
|
setStatusFilter('failed');
|
|
setCurrentPage(1);
|
|
// 直接调用数据获取,不依赖 useEffect
|
|
setLoading(true);
|
|
fetchRecords(1, true, 'failed');
|
|
}
|
|
}}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={[styles.filterButtonText, statusFilter === 'failed' && styles.filterButtonTextActive]}>
|
|
{t('nutritionAnalysisHistory.status.failed')}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* 记录列表 */}
|
|
{loading ? (
|
|
<View style={styles.loadingContainer}>
|
|
<ActivityIndicator size="large" color={Colors.light.primary} />
|
|
<Text style={styles.loadingText}>{t('nutritionAnalysisHistory.loading')}</Text>
|
|
</View>
|
|
) : (
|
|
<FlatList
|
|
data={records}
|
|
renderItem={renderRecordItem}
|
|
keyExtractor={item => item.id.toString()}
|
|
contentContainerStyle={styles.listContainer}
|
|
showsVerticalScrollIndicator={false}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={refreshing}
|
|
onRefresh={handleRefresh}
|
|
colors={[Colors.light.primary]}
|
|
tintColor={Colors.light.primary}
|
|
/>
|
|
}
|
|
onEndReached={handleLoadMore}
|
|
onEndReachedThreshold={0.2} // 提高阈值,减少频繁触发
|
|
ListEmptyComponent={error ? renderErrorState : renderEmptyState} // 错误时显示错误状态
|
|
ListFooterComponent={renderFooter}
|
|
/>
|
|
)}
|
|
|
|
{/* 图片预览 */}
|
|
<ImageViewing
|
|
images={previewImageUri ? [{ uri: previewImageUri }] : []}
|
|
imageIndex={0}
|
|
visible={showImagePreview}
|
|
onRequestClose={() => setShowImagePreview(false)}
|
|
swipeToCloseEnabled={true}
|
|
doubleTapToZoomEnabled={true}
|
|
HeaderComponent={() => (
|
|
<View style={styles.imageViewerHeader}>
|
|
<Text style={styles.imageViewerHeaderText}>
|
|
{dayjs().format(t('nutritionAnalysisHistory.dateFormat'))}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
FooterComponent={() => (
|
|
<View style={styles.imageViewerFooter}>
|
|
<TouchableOpacity
|
|
style={styles.imageViewerFooterButton}
|
|
onPress={() => setShowImagePreview(false)}
|
|
>
|
|
<Text style={styles.imageViewerFooterButtonText}>{t('nutritionLabelAnalysis.actions.close')}</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
)}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#f5e5fbff',
|
|
},
|
|
gradientBackground: {
|
|
position: 'absolute',
|
|
left: 0,
|
|
right: 0,
|
|
top: 0,
|
|
bottom: 0,
|
|
},
|
|
filterContainer: {
|
|
flexDirection: 'row',
|
|
paddingHorizontal: 16,
|
|
paddingBottom: 12,
|
|
gap: 8,
|
|
},
|
|
filterButton: {
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 8,
|
|
borderRadius: 20,
|
|
backgroundColor: 'rgba(255, 255, 255, 0.7)',
|
|
borderWidth: 1,
|
|
borderColor: '#E0E0E0',
|
|
},
|
|
filterButtonActive: {
|
|
backgroundColor: Colors.light.primary,
|
|
borderColor: Colors.light.primary,
|
|
},
|
|
filterButtonText: {
|
|
fontSize: 14,
|
|
fontWeight: '500',
|
|
color: '#666',
|
|
},
|
|
filterButtonTextActive: {
|
|
color: '#FFF',
|
|
},
|
|
listContainer: {
|
|
paddingHorizontal: 16,
|
|
paddingBottom: 20,
|
|
},
|
|
recordItem: {
|
|
backgroundColor: Colors.light.background,
|
|
borderRadius: 16,
|
|
padding: 16,
|
|
marginBottom: 12,
|
|
shadowColor: '#000',
|
|
shadowOffset: {
|
|
width: 0,
|
|
height: 2,
|
|
},
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 4,
|
|
elevation: 3,
|
|
},
|
|
recordHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'flex-start',
|
|
marginBottom: 12,
|
|
},
|
|
recordInfo: {
|
|
flex: 1,
|
|
},
|
|
recordTitle: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
color: Colors.light.text,
|
|
marginBottom: 4,
|
|
},
|
|
recordDate: {
|
|
fontSize: 14,
|
|
color: Colors.light.textSecondary,
|
|
marginBottom: 4,
|
|
},
|
|
statusBadge: {
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
borderRadius: 8,
|
|
alignSelf: 'flex-start',
|
|
},
|
|
statusText: {
|
|
fontSize: 12,
|
|
fontWeight: '500',
|
|
color: '#FFF',
|
|
},
|
|
glassDeleteButton: {
|
|
width: 36,
|
|
height: 36,
|
|
borderRadius: 18,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
overflow: 'hidden',
|
|
},
|
|
fallbackDeleteButton: {
|
|
borderWidth: 1,
|
|
borderColor: 'rgba(244, 67, 54, 0.3)',
|
|
backgroundColor: 'rgba(244, 67, 54, 0.1)',
|
|
},
|
|
imageContainer: {
|
|
marginBottom: 12,
|
|
position: 'relative',
|
|
},
|
|
thumbnail: {
|
|
width: '100%',
|
|
height: 120,
|
|
borderRadius: 12,
|
|
},
|
|
previewHint: {
|
|
position: 'absolute',
|
|
top: 8,
|
|
right: 8,
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
borderRadius: 16,
|
|
padding: 6,
|
|
},
|
|
summaryContainer: {
|
|
marginBottom: 12,
|
|
},
|
|
nutritionSummary: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 12,
|
|
},
|
|
nutritionItem: {
|
|
flex: 1,
|
|
minWidth: '45%',
|
|
backgroundColor: 'rgba(74, 144, 226, 0.1)',
|
|
padding: 8,
|
|
borderRadius: 8,
|
|
},
|
|
nutritionLabel: {
|
|
fontSize: 12,
|
|
color: Colors.light.textSecondary,
|
|
marginBottom: 2,
|
|
},
|
|
nutritionValue: {
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
color: Colors.light.primary,
|
|
},
|
|
errorContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
backgroundColor: 'rgba(244, 67, 54, 0.1)',
|
|
padding: 12,
|
|
borderRadius: 8,
|
|
marginBottom: 12,
|
|
},
|
|
errorMessage: {
|
|
fontSize: 14,
|
|
color: '#F44336',
|
|
marginLeft: 8,
|
|
flex: 1,
|
|
},
|
|
expandButton: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: 8,
|
|
},
|
|
expandButtonText: {
|
|
fontSize: 14,
|
|
color: Colors.light.primary,
|
|
fontWeight: '500',
|
|
marginRight: 4,
|
|
},
|
|
detailsContainer: {
|
|
borderTopWidth: 1,
|
|
borderTopColor: '#F0F0F0',
|
|
paddingTop: 16,
|
|
marginTop: 8,
|
|
},
|
|
detailsTitle: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
color: Colors.light.text,
|
|
marginBottom: 12,
|
|
},
|
|
detailItem: {
|
|
paddingVertical: 12,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: '#F8F8F8',
|
|
},
|
|
detailLabel: {
|
|
fontSize: 14,
|
|
color: Colors.light.text,
|
|
},
|
|
nutritionInfo: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 4,
|
|
},
|
|
detailValue: {
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
color: Colors.light.primary,
|
|
},
|
|
analysisText: {
|
|
fontSize: 12,
|
|
color: Colors.light.textSecondary,
|
|
lineHeight: 16,
|
|
marginTop: 4,
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
backgroundColor: 'rgba(74, 144, 226, 0.05)',
|
|
borderRadius: 6,
|
|
},
|
|
metaInfo: {
|
|
marginTop: 12,
|
|
paddingTop: 12,
|
|
borderTopWidth: 1,
|
|
borderTopColor: '#F0F0F0',
|
|
},
|
|
metaText: {
|
|
fontSize: 12,
|
|
color: Colors.light.textSecondary,
|
|
marginBottom: 4,
|
|
},
|
|
emptyState: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: 60,
|
|
},
|
|
emptyStateText: {
|
|
fontSize: 18,
|
|
fontWeight: '600',
|
|
color: Colors.light.text,
|
|
marginTop: 16,
|
|
},
|
|
emptyStateSubtext: {
|
|
fontSize: 14,
|
|
color: Colors.light.textSecondary,
|
|
marginTop: 8,
|
|
},
|
|
loadingContainer: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
loadingText: {
|
|
fontSize: 16,
|
|
color: Colors.light.textSecondary,
|
|
marginTop: 12,
|
|
},
|
|
loadingFooter: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: 20,
|
|
},
|
|
loadingFooterText: {
|
|
fontSize: 14,
|
|
color: Colors.light.textSecondary,
|
|
marginLeft: 8,
|
|
},
|
|
errorState: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: 60,
|
|
},
|
|
errorStateText: {
|
|
fontSize: 18,
|
|
fontWeight: '600',
|
|
color: Colors.light.text,
|
|
marginTop: 16,
|
|
},
|
|
errorStateSubtext: {
|
|
fontSize: 14,
|
|
color: Colors.light.textSecondary,
|
|
marginTop: 8,
|
|
textAlign: 'center',
|
|
paddingHorizontal: 32,
|
|
},
|
|
retryButton: {
|
|
marginTop: 20,
|
|
paddingHorizontal: 24,
|
|
paddingVertical: 12,
|
|
backgroundColor: Colors.light.primary,
|
|
borderRadius: 24,
|
|
},
|
|
retryButtonText: {
|
|
color: '#FFF',
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
},
|
|
// ImageViewing 组件样式
|
|
imageViewerHeader: {
|
|
position: 'absolute',
|
|
top: 60,
|
|
left: 20,
|
|
right: 20,
|
|
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
borderRadius: 12,
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 12,
|
|
zIndex: 1,
|
|
},
|
|
imageViewerHeaderText: {
|
|
color: '#FFF',
|
|
fontSize: 14,
|
|
fontWeight: '500',
|
|
textAlign: 'center',
|
|
},
|
|
imageViewerFooter: {
|
|
position: 'absolute',
|
|
bottom: 60,
|
|
left: 20,
|
|
right: 20,
|
|
alignItems: 'center',
|
|
zIndex: 1,
|
|
},
|
|
imageViewerFooterButton: {
|
|
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
paddingHorizontal: 24,
|
|
paddingVertical: 12,
|
|
borderRadius: 20,
|
|
},
|
|
imageViewerFooterButtonText: {
|
|
color: '#FFF',
|
|
fontSize: 16,
|
|
fontWeight: '500',
|
|
},
|
|
}); |