import ActivityHeatMap from '@/components/ActivityHeatMap'; import { PRIVACY_POLICY_URL, USER_AGREEMENT_URL } from '@/constants/Agree'; import { getTabBarBottomPadding } from '@/constants/TabBar'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useAuthGuard } from '@/hooks/useAuthGuard'; import { useNotifications } from '@/hooks/useNotifications'; import { DEFAULT_MEMBER_NAME, fetchActivityHistory, fetchMyProfile } from '@/store/userSlice'; import { Ionicons } from '@expo/vector-icons'; import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; import { useFocusEffect } from '@react-navigation/native'; import { Image } from 'expo-image'; import React, { useEffect, useMemo, useState } from 'react'; import { Alert, Linking, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; const DEFAULT_AVATAR_URL = 'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/avatar/avatarGirl01.jpeg'; export default function PersonalScreen() { const dispatch = useAppDispatch(); const { confirmLogout, confirmDeleteAccount, isLoggedIn, pushIfAuthedElseLogin } = useAuthGuard(); const insets = useSafeAreaInsets(); const tabBarHeight = useBottomTabBarHeight(); // 推送通知相关 const { permissionStatus, requestPermission, sendNotification, } = useNotifications(); const [notificationEnabled, setNotificationEnabled] = useState(false); // 计算底部间距 const bottomPadding = useMemo(() => { return getTabBarBottomPadding(tabBarHeight) + (insets?.bottom ?? 0); }, [tabBarHeight, insets?.bottom]); // 直接使用 Redux 中的用户信息,避免重复状态管理 const userProfile = useAppSelector((state) => state.user.profile); // 页面聚焦时获取最新用户信息 useFocusEffect( React.useCallback(() => { dispatch(fetchMyProfile()); dispatch(fetchActivityHistory()) }, [dispatch]) ); // 数据格式化函数 const formatHeight = () => { if (userProfile.height == null) return '--'; return `${parseFloat(userProfile.height).toFixed(1)}cm`; }; const formatWeight = () => { if (userProfile.weight == null) return '--'; return `${parseFloat(userProfile.weight).toFixed(1)}kg`; }; const formatAge = () => { if (!userProfile.birthDate) return '--'; const birthDate = new Date(userProfile.birthDate); const today = new Date(); const age = today.getFullYear() - birthDate.getFullYear(); return `${age}岁`; }; // 显示名称 const displayName = (userProfile.name?.trim()) ? userProfile.name : DEFAULT_MEMBER_NAME; // 监听通知权限状态变化 useEffect(() => { if (permissionStatus === 'granted') { setNotificationEnabled(true); } else { setNotificationEnabled(false); } }, [permissionStatus]); // 处理通知开关变化 const handleNotificationToggle = async (value: boolean) => { if (value) { try { const status = await requestPermission(); if (status === 'granted') { setNotificationEnabled(true); // 发送测试通知 await sendNotification({ title: '通知已开启', body: '您将收到运动提醒和重要通知', sound: true, priority: 'normal', }); } else { Alert.alert('权限被拒绝', '请在系统设置中开启通知权限'); } } catch (error) { Alert.alert('错误', '请求通知权限失败'); } } else { setNotificationEnabled(false); Alert.alert('通知已关闭', '您将不会收到推送通知'); } }; // 用户信息头部 const UserHeader = () => ( 个人信息 {displayName} pushIfAuthedElseLogin('/profile/edit')}> 编辑 ); // 数据统计部分 const StatsSection = () => ( 身体数据 {formatHeight()} 身高 {formatWeight()} 体重 {formatAge()} 年龄 ); // 菜单项组件 const MenuSection = ({ title, items }: { title: string; items: any[] }) => ( {title} {items.map((item, index) => ( {item.title} {item.type === 'switch' ? ( { })} trackColor={{ false: '#E5E5E5', true: '#9370DB' }} thumbColor="#FFFFFF" style={styles.switch} /> ) : ( )} ))} ); // 菜单项配置 const menuSections = [ { title: '账户', items: [ { icon: 'flag-outline' as const, title: '目标管理', onPress: () => pushIfAuthedElseLogin('/profile/goals'), }, ], }, { title: '通知', items: [ { icon: 'notifications-outline' as const, title: '消息推送', type: 'switch' as const, switchValue: notificationEnabled, onSwitchChange: handleNotificationToggle, }, ], }, { title: '其他', items: [ { icon: 'shield-checkmark-outline' as const, title: '隐私政策', onPress: () => Linking.openURL(PRIVACY_POLICY_URL), }, { icon: 'document-text-outline' as const, title: '用户协议', onPress: () => Linking.openURL(USER_AGREEMENT_URL), }, ], }, // 只有登录用户才显示账号与安全菜单 ...(isLoggedIn ? [{ title: '账号与安全', items: [ { icon: 'log-out-outline' as const, title: '退出登录', onPress: confirmLogout, isDanger: false, }, { icon: 'trash-outline' as const, title: '注销帐号', onPress: confirmDeleteAccount, isDanger: true, }, ], }] : []), ]; return ( {/* */} 鱼干记录 {menuSections.map((section, index) => ( ))} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F0F9FF', }, safeArea: { flex: 1, }, scrollView: { flex: 1, paddingHorizontal: 16, paddingTop: 16, }, // 部分容器 sectionContainer: { marginBottom: 20, }, sectionTitle: { fontSize: 16, fontWeight: 'bold', color: '#2C3E50', marginBottom: 10, paddingHorizontal: 4, }, // 卡片容器 cardContainer: { backgroundColor: '#FFFFFF', borderRadius: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.05, shadowRadius: 4, elevation: 2, overflow: 'hidden', }, // 用户信息区域 userInfoContainer: { flexDirection: 'row', alignItems: 'center', padding: 16, }, avatarContainer: { marginRight: 12, }, avatar: { width: 60, height: 60, borderRadius: 30, borderWidth: 2, borderColor: '#9370DB', }, userDetails: { flex: 1, }, userName: { fontSize: 18, fontWeight: 'bold', color: '#2C3E50', marginBottom: 4, }, userRole: { fontSize: 14, color: '#9370DB', fontWeight: '500', }, editButton: { backgroundColor: '#9370DB', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 16, }, editButtonText: { color: '#FFFFFF', fontSize: 14, fontWeight: '600', }, // 数据统计 statsContainer: { flexDirection: 'row', justifyContent: 'space-between', padding: 16, }, statItem: { alignItems: 'center', flex: 1, }, statValue: { fontSize: 18, fontWeight: 'bold', color: '#9370DB', marginBottom: 4, }, statLabel: { fontSize: 12, color: '#6C757D', fontWeight: '500', }, // 菜单项 menuItem: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 14, paddingHorizontal: 16, borderBottomWidth: 1, borderBottomColor: '#F1F3F4', }, menuItemLeft: { flexDirection: 'row', alignItems: 'center', flex: 1, }, iconContainer: { width: 32, height: 32, borderRadius: 6, alignItems: 'center', justifyContent: 'center', marginRight: 12, }, menuItemText: { fontSize: 15, color: '#2C3E50', fontWeight: '500', }, switch: { transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }], }, fishRecordContainer: { flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start', marginBottom: 10, }, fishRecordText: { fontSize: 16, fontWeight: 'bold', color: '#2C3E50', marginLeft: 4, }, });