Files
digital-pilates/app/(tabs)/personal.tsx
richarjiang 9796c614ed feat: 添加日历功能和进度条组件
- 在项目中引入 dayjs 库以处理日期
- 新增 PlanCard 和 ProgressBar 组件,分别用于展示训练计划和进度条
- 更新首页以显示推荐的训练计划
- 优化个人中心页面的底部留白处理
- 本地化界面文本为中文
2025-08-12 09:16:59 +08:00

379 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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',
},
});