feat: 更新应用名称及图标,新增HRV数据管理,优化营养记录展示

This commit is contained in:
richarjiang
2025-08-19 19:13:02 +08:00
parent 260546ff46
commit 35cd320ea7
10 changed files with 643 additions and 564 deletions

View File

@@ -2,11 +2,9 @@ import { NutritionRecordCard } from '@/components/NutritionRecordCard';
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
import { DietRecord } from '@/services/dietRecords';
import { getMockDietRecords } from '@/services/mockDietRecords';
import { DietRecord, getDietRecords } from '@/services/dietRecords';
import { getMonthDaysZh, getMonthTitleZh, getTodayIndexInMonth } from '@/utils/date';
import { Ionicons } from '@expo/vector-icons';
// import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import dayjs from 'dayjs';
import { router } from 'expo-router';
import React, { useEffect, useRef, useState } from 'react';
@@ -20,15 +18,12 @@ import {
TouchableOpacity,
View
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
type ViewMode = 'daily' | 'all';
export default function NutritionRecordsScreen() {
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
const colorTokens = Colors[theme];
// const tabBarHeight = useBottomTabBarHeight();
const insets = useSafeAreaInsets();
// 日期相关状态 - 使用与统计页面相同的日期逻辑
const days = getMonthDaysZh();
@@ -83,24 +78,11 @@ export default function NutritionRecordsScreen() {
if (viewMode === 'daily') {
// 按天查看时,获取选中日期的数据
const selectedDate = days[selectedIndex]?.date?.format('YYYY-MM-DD') ?? dayjs().format('YYYY-MM-DD');
startDate = selectedDate;
endDate = selectedDate;
startDate = days[selectedIndex]?.date.startOf('day').toISOString();
endDate = days[selectedIndex]?.date.endOf('day').toISOString();
}
// viewMode === 'all' 时不设置日期范围,获取所有数据
// 使用模拟数据进行测试
// const data = await getDietRecords({
// startDate,
// endDate,
// page: currentPage,
// limit: 10,
// });
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 800));
const data = getMockDietRecords({
const data = await getDietRecords({
startDate,
endDate,
page: currentPage,
@@ -140,7 +122,7 @@ export default function NutritionRecordsScreen() {
// 渲染视图模式切换器
const renderViewModeToggle = () => (
<View style={[styles.viewModeContainer, { backgroundColor: colorTokens.surface }]}>
<View style={[styles.viewModeContainer, { backgroundColor: colorTokens.pageBackgroundEmphasis }]}>
<Text style={[styles.monthTitle, { color: colorTokens.text }]}>{monthTitle}</Text>
<View style={[styles.toggleContainer, { backgroundColor: colorTokens.pageBackgroundEmphasis }]}>
<TouchableOpacity
@@ -253,18 +235,32 @@ export default function NutritionRecordsScreen() {
const renderEmptyState = () => (
<View style={styles.emptyContainer}>
<Ionicons name="restaurant-outline" size={64} color={colorTokens.textSecondary} />
<Text style={[styles.emptyTitle, { color: colorTokens.text }]}>
{viewMode === 'daily' ? '今天还没有记录' : '暂无营养记录'}
</Text>
<Text style={[styles.emptySubtitle, { color: colorTokens.textSecondary }]}>
{viewMode === 'daily' ? '点击右上角添加今日营养摄入' : '开始记录你的营养摄入吧'}
</Text>
<View style={styles.emptyTimelineContainer}>
<View style={styles.emptyTimeline}>
<View style={[styles.emptyTimelineDot, { backgroundColor: colorTokens.primary }]}>
<Ionicons name="add-outline" size={16} color="#FFFFFF" />
</View>
</View>
<View style={styles.emptyContent}>
<Ionicons name="restaurant-outline" size={48} color={colorTokens.textSecondary} />
<Text style={[styles.emptyTitle, { color: colorTokens.text }]}>
{viewMode === 'daily' ? '今天还没有记录' : '暂无营养记录'}
</Text>
<Text style={[styles.emptySubtitle, { color: colorTokens.textSecondary }]}>
{viewMode === 'daily' ? '开始记录今日营养摄入' : '开始记录你的营养摄入吧'}
</Text>
</View>
</View>
</View>
);
const renderRecord = ({ item }: { item: DietRecord }) => (
<NutritionRecordCard record={item} />
const renderRecord = ({ item, index }: { item: DietRecord; index: number }) => (
<NutritionRecordCard
record={item}
showTimeline={true}
isFirst={index === 0}
isLast={index === records.length - 1}
/>
);
const renderFooter = () => {
@@ -296,17 +292,6 @@ export default function NutritionRecordsScreen() {
<HeaderBar
title="营养记录"
onBack={() => router.back()}
right={
<TouchableOpacity
style={[styles.addButton, { backgroundColor: colorTokens.primary }]}
onPress={() => {
// TODO: 跳转到添加营养记录页面
console.log('添加营养记录');
}}
>
<Ionicons name="add" size={20} color={colorTokens.onPrimary} />
</TouchableOpacity>
}
/>
{renderViewModeToggle()}
@@ -322,11 +307,11 @@ export default function NutritionRecordsScreen() {
) : (
<FlatList
data={records}
renderItem={renderRecord}
renderItem={({ item, index }) => renderRecord({ item, index })}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={[
styles.listContainer,
{ paddingBottom: 40 }
{ paddingBottom: 40, paddingTop: 16 }
]}
showsVerticalScrollIndicator={false}
refreshControl={
@@ -387,8 +372,8 @@ const styles = StyleSheet.create({
paddingVertical: 8,
},
dayPill: {
width: 68,
height: 68,
width: 48,
height: 48,
borderRadius: 34,
marginRight: 12,
alignItems: 'center',
@@ -434,18 +419,47 @@ const styles = StyleSheet.create({
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: 20,
fontSize: 18,
fontWeight: '700',
marginTop: 16,
marginBottom: 8,
textAlign: 'center',
},
emptySubtitle: {
fontSize: 16,
fontSize: 14,
fontWeight: '500',
textAlign: 'center',
lineHeight: 22,
lineHeight: 20,
},
footerContainer: {
paddingVertical: 20,