import { NutritionRecordCard } from '@/components/NutritionRecordCard'; import { HeaderBar } from '@/components/ui/HeaderBar'; import { Colors } from '@/constants/Colors'; import { useColorScheme } from '@/hooks/useColorScheme'; import { DietRecord, getDietRecords } from '@/services/dietRecords'; import { getMonthDaysZh, getMonthTitleZh, getTodayIndexInMonth } from '@/utils/date'; import { Ionicons } from '@expo/vector-icons'; import dayjs from 'dayjs'; import { router } from 'expo-router'; import React, { useEffect, useRef, useState } from 'react'; import { ActivityIndicator, FlatList, RefreshControl, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; type ViewMode = 'daily' | 'all'; export default function NutritionRecordsScreen() { const theme = (useColorScheme() ?? 'light') as 'light' | 'dark'; const colorTokens = Colors[theme]; // 日期相关状态 - 使用与统计页面相同的日期逻辑 const days = getMonthDaysZh(); const [selectedIndex, setSelectedIndex] = useState(getTodayIndexInMonth()); const monthTitle = getMonthTitleZh(); // 视图模式:按天查看 vs 全部查看 const [viewMode, setViewMode] = useState('daily'); // 数据状态 const [records, setRecords] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [hasMoreData, setHasMoreData] = useState(true); const [page, setPage] = useState(1); // 日期滚动相关 const daysScrollRef = useRef(null); const [scrollWidth, setScrollWidth] = useState(0); const DAY_PILL_WIDTH = 60; // 48px width + 12px marginRight = 60px total per item const DAY_PILL_SPACING = 0; // spacing is included in the width above // 日期滚动控制 const scrollToIndex = (index: number, animated = true) => { if (scrollWidth <= 0) return; const itemOffset = index * DAY_PILL_WIDTH; const scrollViewCenterX = scrollWidth / 2; const itemCenterX = DAY_PILL_WIDTH / 2; const centerOffset = Math.max(0, itemOffset - scrollViewCenterX + itemCenterX); daysScrollRef.current?.scrollTo({ x: centerOffset, animated }); }; // 初始化时滚动到选中位置 useEffect(() => { if (scrollWidth > 0) { // 延迟滚动以确保ScrollView已经完全渲染 setTimeout(() => { scrollToIndex(selectedIndex, false); }, 100); } }, [scrollWidth]); // 选中日期变化时滚动 useEffect(() => { if (scrollWidth > 0) { scrollToIndex(selectedIndex, true); } }, [selectedIndex]); // 加载记录数据 const loadRecords = async (isRefresh = false, loadMore = false) => { try { if (isRefresh) { setRefreshing(true); setPage(1); } else if (loadMore) { // 加载更多时不显示loading } else { setLoading(true); } const currentPage = isRefresh ? 1 : (loadMore ? page + 1 : 1); let startDate: string | undefined; let endDate: string | undefined; if (viewMode === 'daily') { // 按天查看时,获取选中日期的数据 startDate = days[selectedIndex]?.date.startOf('day').toISOString(); endDate = days[selectedIndex]?.date.endOf('day').toISOString(); } const data = await getDietRecords({ startDate, endDate, page: currentPage, limit: 10, }); if (isRefresh || currentPage === 1) { setRecords(data.records); } else { setRecords(prev => [...prev, ...data.records]); } setHasMoreData(data.records.length === 10); // 如果返回的记录数少于limit,说明没有更多数据 setPage(currentPage); } catch (error) { console.error('加载营养记录失败:', error); } finally { setLoading(false); setRefreshing(false); } }; // 当选中日期或视图模式变化时重新加载数据 useEffect(() => { loadRecords(); }, [selectedIndex, viewMode]); const onRefresh = () => { loadRecords(true); }; const loadMoreRecords = () => { if (hasMoreData && !loading && !refreshing) { loadRecords(false, true); } }; // 渲染视图模式切换器 const renderViewModeToggle = () => ( {monthTitle} setViewMode('daily')} > 按天查看 setViewMode('all')} > 全部记录 ); // 渲染日期选择器(仅在按天查看模式下显示) const renderDateSelector = () => { if (viewMode !== 'daily') return null; return ( setScrollWidth(e.nativeEvent.layout.width)} > {days.map((day, index) => { const isSelected = index === selectedIndex; const isToday = day.isToday; const isDisabled = day.date?.isAfter(dayjs(), 'day') ?? false; return ( { if (!isDisabled) { setSelectedIndex(index); } }} disabled={isDisabled} > {day.date?.date() ?? ''} {day.dayAbbr} ); })} ); }; const renderEmptyState = () => ( {viewMode === 'daily' ? '今天还没有记录' : '暂无营养记录'} {viewMode === 'daily' ? '开始记录今日营养摄入' : '开始记录你的营养摄入吧'} ); const renderRecord = ({ item, index }: { item: DietRecord; index: number }) => ( ); const renderFooter = () => { if (!hasMoreData) { return ( 没有更多数据了 ); } if (viewMode === 'all' && records.length > 0) { return ( 加载更多 ); } return null; }; return ( router.back()} /> {renderViewModeToggle()} {renderDateSelector()} {loading ? ( 加载中... ) : ( renderRecord({ item, index })} keyExtractor={(item) => item.id.toString()} contentContainerStyle={[ styles.listContainer, { paddingBottom: 40, paddingTop: 16 } ]} showsVerticalScrollIndicator={false} refreshControl={ } ListEmptyComponent={renderEmptyState} ListFooterComponent={renderFooter} onEndReached={viewMode === 'all' ? loadMoreRecords : undefined} onEndReachedThreshold={0.1} /> )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, viewModeContainer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 12, marginBottom: 8, }, monthTitle: { fontSize: 22, fontWeight: '800', }, toggleContainer: { flexDirection: 'row', borderRadius: 20, padding: 2, }, toggleButton: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 18, minWidth: 80, alignItems: 'center', }, toggleText: { fontSize: 14, fontWeight: '600', }, daysContainer: { marginBottom: 12, }, daysScrollContainer: { paddingHorizontal: 16, paddingVertical: 8, }, dayPill: { width: 48, height: 48, borderRadius: 34, marginRight: 12, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 4, elevation: 3, }, dayNumber: { fontSize: 18, textAlign: 'center', }, dayLabel: { fontSize: 12, marginTop: 2, textAlign: 'center', }, addButton: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, loadingText: { marginTop: 12, fontSize: 16, fontWeight: '500', }, listContainer: { paddingHorizontal: 16, paddingTop: 8, }, emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingVertical: 60, paddingHorizontal: 16, }, emptyTimelineContainer: { flexDirection: 'row', alignItems: 'center', maxWidth: 320, }, emptyTimeline: { width: 64, alignItems: 'center', paddingTop: 8, }, emptyTimelineDot: { width: 32, height: 32, borderRadius: 16, justifyContent: 'center', alignItems: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 2, }, emptyContent: { flex: 1, alignItems: 'center', marginLeft: 16, }, emptyTitle: { fontSize: 18, fontWeight: '700', marginTop: 16, marginBottom: 8, textAlign: 'center', }, emptySubtitle: { fontSize: 14, fontWeight: '500', textAlign: 'center', lineHeight: 20, }, footerContainer: { paddingVertical: 20, alignItems: 'center', }, footerText: { fontSize: 14, fontWeight: '500', }, loadMoreButton: { paddingVertical: 16, alignItems: 'center', }, loadMoreText: { fontSize: 16, fontWeight: '600', }, });