feat: 更新健康数据功能和用户个人信息页面
- 在 Explore 页面中添加日期选择功能,允许用户查看指定日期的健康数据 - 重构健康数据获取逻辑,支持根据日期获取健康数据 - 在个人信息页面中集成用户资料编辑功能,支持姓名、性别、年龄、体重和身高的输入 - 新增 AnimatedNumber 和 CircularRing 组件,优化数据展示效果 - 更新 package.json 和 package-lock.json,添加 react-native-svg 依赖 - 修改布局以支持新功能的显示和交互
This commit is contained in:
@@ -4,7 +4,9 @@ import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { router } from 'expo-router';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
SafeAreaView,
|
||||
@@ -29,6 +31,71 @@ export default function PersonalScreen() {
|
||||
const colorScheme = useColorScheme();
|
||||
const colors = Colors[colorScheme ?? 'light'];
|
||||
|
||||
type WeightUnit = 'kg' | 'lb';
|
||||
type HeightUnit = 'cm' | 'ft';
|
||||
type UserProfile = {
|
||||
fullName?: string;
|
||||
email?: string;
|
||||
gender?: 'male' | 'female' | '';
|
||||
age?: string;
|
||||
weightKg?: number;
|
||||
heightCm?: number;
|
||||
unitPref?: { weight: WeightUnit; height: HeightUnit };
|
||||
};
|
||||
|
||||
const [profile, setProfile] = useState<UserProfile>({});
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
const [p, o] = await Promise.all([
|
||||
AsyncStorage.getItem('@user_profile'),
|
||||
AsyncStorage.getItem('@user_personal_info'),
|
||||
]);
|
||||
let next: UserProfile = {};
|
||||
if (o) {
|
||||
try {
|
||||
const parsed = JSON.parse(o);
|
||||
next = {
|
||||
...next,
|
||||
age: parsed?.age ? String(parsed.age) : undefined,
|
||||
gender: parsed?.gender || '',
|
||||
heightCm: parsed?.height ? parseFloat(parsed.height) : undefined,
|
||||
weightKg: parsed?.weight ? parseFloat(parsed.weight) : undefined,
|
||||
};
|
||||
} catch { }
|
||||
}
|
||||
if (p) {
|
||||
try { next = { ...next, ...JSON.parse(p) }; } catch { }
|
||||
}
|
||||
setProfile(next);
|
||||
} catch (e) {
|
||||
console.warn('加载用户资料失败', e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => { load(); }, []);
|
||||
useFocusEffect(React.useCallback(() => { load(); return () => { }; }, []));
|
||||
|
||||
const formatHeight = () => {
|
||||
if (profile.heightCm == null) return '--';
|
||||
const unit = profile.unitPref?.height ?? 'cm';
|
||||
if (unit === 'cm') return `${Math.round(profile.heightCm)}cm`;
|
||||
return `${round(profile.heightCm / 30.48, 1)}ft`;
|
||||
};
|
||||
|
||||
const formatWeight = () => {
|
||||
if (profile.weightKg == null) return '--';
|
||||
const unit = profile.unitPref?.weight ?? 'kg';
|
||||
if (unit === 'kg') return `${round(profile.weightKg, 1)}kg`;
|
||||
return `${round(profile.weightKg * 2.2046226218, 1)}lb`;
|
||||
};
|
||||
|
||||
const formatAge = () => (profile.age ? `${profile.age}岁` : '--');
|
||||
|
||||
const round = (n: number, d = 0) => {
|
||||
const p = Math.pow(10, d); return Math.round(n * p) / p;
|
||||
};
|
||||
|
||||
const handleResetOnboarding = () => {
|
||||
Alert.alert(
|
||||
'重置引导',
|
||||
@@ -74,13 +141,13 @@ export default function PersonalScreen() {
|
||||
|
||||
{/* 用户信息 */}
|
||||
<View style={styles.userDetails}>
|
||||
<Text style={styles.userName}>Masi Ramezanzade</Text>
|
||||
<Text style={styles.userProgram}>Lose a Fat Program</Text>
|
||||
<Text style={styles.userName}>{profile.fullName || '未设置姓名'}</Text>
|
||||
<Text style={styles.userProgram}>减脂计划</Text>
|
||||
</View>
|
||||
|
||||
{/* 编辑按钮 */}
|
||||
<TouchableOpacity style={dynamicStyles.editButton}>
|
||||
<Text style={dynamicStyles.editButtonText}>Edit</Text>
|
||||
<TouchableOpacity style={dynamicStyles.editButton} onPress={() => router.push('/profile/edit')}>
|
||||
<Text style={dynamicStyles.editButtonText}>编辑</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@@ -89,16 +156,16 @@ export default function PersonalScreen() {
|
||||
const StatsSection = () => (
|
||||
<View style={styles.statsContainer}>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={dynamicStyles.statValue}>180cm</Text>
|
||||
<Text style={styles.statLabel}>Height</Text>
|
||||
<Text style={dynamicStyles.statValue}>{formatHeight()}</Text>
|
||||
<Text style={styles.statLabel}>身高</Text>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={dynamicStyles.statValue}>65kg</Text>
|
||||
<Text style={styles.statLabel}>Weight</Text>
|
||||
<Text style={dynamicStyles.statValue}>{formatWeight()}</Text>
|
||||
<Text style={styles.statLabel}>体重</Text>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={dynamicStyles.statValue}>22yo</Text>
|
||||
<Text style={styles.statLabel}>Age</Text>
|
||||
<Text style={dynamicStyles.statValue}>{formatAge()}</Text>
|
||||
<Text style={styles.statLabel}>年龄</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -176,25 +243,26 @@ export default function PersonalScreen() {
|
||||
icon: 'person-outline',
|
||||
iconBg: '#E8F5E8',
|
||||
iconColor: '#4ADE80',
|
||||
title: 'Personal Data',
|
||||
title: '个人资料',
|
||||
onPress: () => router.push('/profile/edit'),
|
||||
},
|
||||
{
|
||||
icon: 'trophy-outline',
|
||||
iconBg: '#E8F5E8',
|
||||
iconColor: '#4ADE80',
|
||||
title: 'Achievement',
|
||||
title: '成就',
|
||||
},
|
||||
{
|
||||
icon: 'time-outline',
|
||||
iconBg: '#E8F5E8',
|
||||
iconColor: '#4ADE80',
|
||||
title: 'Activity History',
|
||||
title: '活动历史',
|
||||
},
|
||||
{
|
||||
icon: 'stats-chart-outline',
|
||||
iconBg: '#E8F5E8',
|
||||
iconColor: '#4ADE80',
|
||||
title: 'Workout Progress',
|
||||
title: '训练进度',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -203,7 +271,7 @@ export default function PersonalScreen() {
|
||||
icon: 'notifications-outline',
|
||||
iconBg: '#E8F5E8',
|
||||
iconColor: '#4ADE80',
|
||||
title: 'Pop-up Notification',
|
||||
title: '弹窗通知',
|
||||
type: 'switch',
|
||||
},
|
||||
];
|
||||
@@ -213,19 +281,19 @@ export default function PersonalScreen() {
|
||||
icon: 'mail-outline',
|
||||
iconBg: '#E8F5E8',
|
||||
iconColor: '#4ADE80',
|
||||
title: 'Contact Us',
|
||||
title: '联系我们',
|
||||
},
|
||||
{
|
||||
icon: 'shield-checkmark-outline',
|
||||
iconBg: '#E8F5E8',
|
||||
iconColor: '#4ADE80',
|
||||
title: 'Privacy Policy',
|
||||
title: '隐私政策',
|
||||
},
|
||||
{
|
||||
icon: 'settings-outline',
|
||||
iconBg: '#E8F5E8',
|
||||
iconColor: '#4ADE80',
|
||||
title: 'Settings',
|
||||
title: '设置',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -250,10 +318,10 @@ export default function PersonalScreen() {
|
||||
>
|
||||
<UserInfoSection />
|
||||
<StatsSection />
|
||||
<MenuSection title="Account" items={accountItems} />
|
||||
<MenuSection title="Notification" items={notificationItems} />
|
||||
<MenuSection title="Other" items={otherItems} />
|
||||
<MenuSection title="Developer" items={developerItems} />
|
||||
<MenuSection title="账户" items={accountItems} />
|
||||
<MenuSection title="通知" items={notificationItems} />
|
||||
<MenuSection title="其他" items={otherItems} />
|
||||
<MenuSection title="开发者" items={developerItems} />
|
||||
|
||||
{/* 底部浮动按钮 */}
|
||||
<View style={[styles.floatingButtonContainer, { bottom: Math.max(30, tabBarHeight / 2) + (insets?.bottom ?? 0) }]}>
|
||||
|
||||
Reference in New Issue
Block a user