- 在项目中引入 dayjs 库以处理日期 - 新增 PlanCard 和 ProgressBar 组件,分别用于展示训练计划和进度条 - 更新首页以显示推荐的训练计划 - 优化个人中心页面的底部留白处理 - 本地化界面文本为中文
379 lines
9.2 KiB
TypeScript
379 lines
9.2 KiB
TypeScript
import { Colors } from '@/constants/Colors';
|
||
import { getTabBarBottomPadding } from '@/constants/TabBar';
|
||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||
import { Ionicons } from '@expo/vector-icons';
|
||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
||
import React, { useMemo, useState } from 'react';
|
||
import {
|
||
SafeAreaView,
|
||
ScrollView,
|
||
StatusBar,
|
||
StyleSheet,
|
||
Switch,
|
||
Text,
|
||
TouchableOpacity,
|
||
View
|
||
} from 'react-native';
|
||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||
|
||
export default function PersonalScreen() {
|
||
const insets = useSafeAreaInsets();
|
||
const tabBarHeight = useBottomTabBarHeight();
|
||
const bottomPadding = useMemo(() => {
|
||
// 统一的页面底部留白:TabBar 高度 + TabBar 与底部的额外间距 + 安全区底部
|
||
return getTabBarBottomPadding(tabBarHeight) + (insets?.bottom ?? 0);
|
||
}, [tabBarHeight, insets?.bottom]);
|
||
const [notificationEnabled, setNotificationEnabled] = useState(true);
|
||
const colorScheme = useColorScheme();
|
||
const colors = Colors[colorScheme ?? 'light'];
|
||
|
||
|
||
const UserInfoSection = () => (
|
||
<View style={styles.userInfoCard}>
|
||
<View style={styles.userInfoContainer}>
|
||
{/* 头像 */}
|
||
<View style={styles.avatarContainer}>
|
||
<View style={styles.avatar}>
|
||
<View style={styles.avatarContent}>
|
||
{/* 简单的头像图标,您可以替换为实际图片 */}
|
||
<View style={styles.avatarIcon}>
|
||
<View style={styles.avatarFace} />
|
||
<View style={styles.avatarBody} />
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 用户信息 */}
|
||
<View style={styles.userDetails}>
|
||
<Text style={styles.userName}>Masi Ramezanzade</Text>
|
||
<Text style={styles.userProgram}>Lose a Fat Program</Text>
|
||
</View>
|
||
|
||
{/* 编辑按钮 */}
|
||
<TouchableOpacity style={dynamicStyles.editButton}>
|
||
<Text style={dynamicStyles.editButtonText}>Edit</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
const StatsSection = () => (
|
||
<View style={styles.statsContainer}>
|
||
<View style={styles.statItem}>
|
||
<Text style={dynamicStyles.statValue}>180cm</Text>
|
||
<Text style={styles.statLabel}>Height</Text>
|
||
</View>
|
||
<View style={styles.statItem}>
|
||
<Text style={dynamicStyles.statValue}>65kg</Text>
|
||
<Text style={styles.statLabel}>Weight</Text>
|
||
</View>
|
||
<View style={styles.statItem}>
|
||
<Text style={dynamicStyles.statValue}>22yo</Text>
|
||
<Text style={styles.statLabel}>Age</Text>
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
const MenuSection = ({ title, items }: { title: string; items: any[] }) => (
|
||
<View style={styles.menuSection}>
|
||
<Text style={styles.sectionTitle}>{title}</Text>
|
||
{items.map((item, index) => (
|
||
<TouchableOpacity key={index} style={styles.menuItem}>
|
||
<View style={styles.menuItemLeft}>
|
||
<View style={[styles.menuIcon]}>
|
||
<Ionicons name={item.icon} size={20} color={colors.primary} />
|
||
</View>
|
||
<Text style={styles.menuItemText}>{item.title}</Text>
|
||
</View>
|
||
{item.type === 'switch' ? (
|
||
<Switch
|
||
value={notificationEnabled}
|
||
onValueChange={setNotificationEnabled}
|
||
trackColor={{ false: '#E5E5E5', true: colors.primary }}
|
||
thumbColor="#FFFFFF"
|
||
style={styles.switch}
|
||
/>
|
||
) : (
|
||
<Ionicons name="chevron-forward" size={20} color="#C4C4C4" />
|
||
)}
|
||
</TouchableOpacity>
|
||
))}
|
||
</View>
|
||
);
|
||
|
||
// 动态创建样式
|
||
const dynamicStyles = {
|
||
editButton: {
|
||
backgroundColor: colors.primary,
|
||
paddingHorizontal: 20,
|
||
paddingVertical: 10,
|
||
borderRadius: 20,
|
||
},
|
||
editButtonText: {
|
||
color: '#192126',
|
||
fontSize: 14,
|
||
fontWeight: '600' as const,
|
||
},
|
||
statValue: {
|
||
fontSize: 18,
|
||
fontWeight: 'bold' as const,
|
||
color: colors.primary,
|
||
marginBottom: 4,
|
||
},
|
||
floatingButton: {
|
||
width: 56,
|
||
height: 56,
|
||
borderRadius: 28,
|
||
backgroundColor: colors.primary,
|
||
alignItems: 'center' as const,
|
||
justifyContent: 'center' as const,
|
||
shadowColor: colors.primary,
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 4,
|
||
},
|
||
shadowOpacity: 0.3,
|
||
shadowRadius: 8,
|
||
elevation: 8,
|
||
},
|
||
};
|
||
|
||
const accountItems = [
|
||
{
|
||
icon: 'person-outline',
|
||
iconBg: '#E8F5E8',
|
||
iconColor: '#4ADE80',
|
||
title: 'Personal Data',
|
||
},
|
||
{
|
||
icon: 'trophy-outline',
|
||
iconBg: '#E8F5E8',
|
||
iconColor: '#4ADE80',
|
||
title: 'Achievement',
|
||
},
|
||
{
|
||
icon: 'time-outline',
|
||
iconBg: '#E8F5E8',
|
||
iconColor: '#4ADE80',
|
||
title: 'Activity History',
|
||
},
|
||
{
|
||
icon: 'stats-chart-outline',
|
||
iconBg: '#E8F5E8',
|
||
iconColor: '#4ADE80',
|
||
title: 'Workout Progress',
|
||
},
|
||
];
|
||
|
||
const notificationItems = [
|
||
{
|
||
icon: 'notifications-outline',
|
||
iconBg: '#E8F5E8',
|
||
iconColor: '#4ADE80',
|
||
title: 'Pop-up Notification',
|
||
type: 'switch',
|
||
},
|
||
];
|
||
|
||
const otherItems = [
|
||
{
|
||
icon: 'mail-outline',
|
||
iconBg: '#E8F5E8',
|
||
iconColor: '#4ADE80',
|
||
title: 'Contact Us',
|
||
},
|
||
{
|
||
icon: 'shield-checkmark-outline',
|
||
iconBg: '#E8F5E8',
|
||
iconColor: '#4ADE80',
|
||
title: 'Privacy Policy',
|
||
},
|
||
{
|
||
icon: 'settings-outline',
|
||
iconBg: '#E8F5E8',
|
||
iconColor: '#4ADE80',
|
||
title: 'Settings',
|
||
},
|
||
];
|
||
|
||
return (
|
||
<View style={styles.container}>
|
||
<StatusBar barStyle="dark-content" backgroundColor="transparent" translucent />
|
||
<SafeAreaView style={styles.safeArea}>
|
||
<ScrollView
|
||
style={styles.scrollView}
|
||
contentContainerStyle={{ paddingBottom: bottomPadding }}
|
||
showsVerticalScrollIndicator={false}
|
||
>
|
||
<UserInfoSection />
|
||
<StatsSection />
|
||
<MenuSection title="Account" items={accountItems} />
|
||
<MenuSection title="Notification" items={notificationItems} />
|
||
<MenuSection title="Other" items={otherItems} />
|
||
|
||
{/* 底部浮动按钮 */}
|
||
<View style={[styles.floatingButtonContainer, { bottom: Math.max(30, tabBarHeight / 2) + (insets?.bottom ?? 0) }]}>
|
||
<TouchableOpacity style={dynamicStyles.floatingButton}>
|
||
<Ionicons name="search" size={24} color="#192126" />
|
||
</TouchableOpacity>
|
||
</View>
|
||
</ScrollView>
|
||
</SafeAreaView>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: '#F5F5F5', // 浅灰色背景
|
||
},
|
||
safeArea: {
|
||
flex: 1,
|
||
},
|
||
scrollView: {
|
||
flex: 1,
|
||
paddingHorizontal: 20,
|
||
backgroundColor: '#F5F5F5',
|
||
},
|
||
// 用户信息区域
|
||
userInfoCard: {
|
||
borderRadius: 16,
|
||
marginBottom: 20,
|
||
},
|
||
userInfoContainer: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
padding: 20,
|
||
},
|
||
avatarContainer: {
|
||
marginRight: 15,
|
||
},
|
||
avatar: {
|
||
width: 80,
|
||
height: 80,
|
||
borderRadius: 40,
|
||
backgroundColor: '#E8D4F0',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
overflow: 'hidden',
|
||
},
|
||
avatarContent: {
|
||
width: '100%',
|
||
height: '100%',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
avatarIcon: {
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
avatarFace: {
|
||
width: 25,
|
||
height: 25,
|
||
borderRadius: 12.5,
|
||
backgroundColor: '#D4A574',
|
||
marginBottom: 5,
|
||
},
|
||
avatarBody: {
|
||
width: 30,
|
||
height: 20,
|
||
borderRadius: 15,
|
||
backgroundColor: '#F4C842',
|
||
},
|
||
userDetails: {
|
||
flex: 1,
|
||
},
|
||
userName: {
|
||
fontSize: 18,
|
||
fontWeight: 'bold',
|
||
color: '#000',
|
||
marginBottom: 4,
|
||
},
|
||
userProgram: {
|
||
fontSize: 14,
|
||
color: '#888',
|
||
},
|
||
|
||
// 统计信息区域
|
||
statsContainer: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
backgroundColor: '#FFFFFF',
|
||
borderRadius: 16,
|
||
padding: 20,
|
||
marginBottom: 20,
|
||
shadowColor: '#000',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 2,
|
||
},
|
||
shadowOpacity: 0.1,
|
||
shadowRadius: 4,
|
||
elevation: 3,
|
||
},
|
||
statItem: {
|
||
alignItems: 'center',
|
||
flex: 1,
|
||
},
|
||
|
||
statLabel: {
|
||
fontSize: 12,
|
||
color: '#888',
|
||
},
|
||
// 菜单区域
|
||
menuSection: {
|
||
marginBottom: 20,
|
||
backgroundColor: '#FFFFFF',
|
||
padding: 16,
|
||
},
|
||
sectionTitle: {
|
||
fontSize: 18,
|
||
fontWeight: 'bold',
|
||
color: '#000',
|
||
marginBottom: 12,
|
||
paddingHorizontal: 4,
|
||
},
|
||
menuItem: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
paddingVertical: 16,
|
||
paddingHorizontal: 16,
|
||
borderRadius: 12,
|
||
marginBottom: 8,
|
||
},
|
||
menuItemLeft: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
flex: 1,
|
||
},
|
||
menuIcon: {
|
||
width: 36,
|
||
height: 36,
|
||
borderRadius: 8,
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
marginRight: 12,
|
||
},
|
||
menuItemText: {
|
||
fontSize: 16,
|
||
color: '#000',
|
||
flex: 1,
|
||
},
|
||
switch: {
|
||
transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }],
|
||
},
|
||
// 浮动按钮
|
||
floatingButtonContainer: {
|
||
position: 'absolute',
|
||
bottom: 30,
|
||
left: 0,
|
||
right: 0,
|
||
alignItems: 'center',
|
||
pointerEvents: 'box-none',
|
||
},
|
||
|
||
});
|