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 { getNotificationEnabled, setNotificationEnabled as saveNotificationEnabled } from '@/utils/userPreferences'; 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 { LinearGradient } from 'expo-linear-gradient'; import React, { useEffect, useMemo, useState } from 'react'; import { Alert, Linking, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; const DEFAULT_AVATAR_URL = 'https://plates-1251306435.cos.ap-guangzhou.myqcloud.com/images/seal-avatar/2.jpeg'; export default function PersonalScreen() { const dispatch = useAppDispatch(); const { confirmLogout, confirmDeleteAccount, isLoggedIn, pushIfAuthedElseLogin } = useAuthGuard(); const insets = useSafeAreaInsets(); const tabBarHeight = useBottomTabBarHeight(); // 推送通知相关 const { 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()); // 加载用户推送偏好设置 loadNotificationPreference(); }, [dispatch]) ); // 加载用户推送偏好设置 const loadNotificationPreference = async () => { try { const enabled = await getNotificationEnabled(); setNotificationEnabled(enabled); } catch (error) { console.error('加载推送偏好设置失败:', error); } }; // 数据格式化函数 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(() => { loadNotificationPreference(); }, []); // 处理通知开关变化 const handleNotificationToggle = async (value: boolean) => { if (value) { try { // 先检查系统权限 const status = await requestPermission(); if (status === 'granted') { // 系统权限获取成功,保存用户偏好设置 await saveNotificationEnabled(true); setNotificationEnabled(true); // 发送测试通知 await sendNotification({ title: '通知已开启', body: '您将收到运动提醒和重要通知', sound: true, priority: 'normal', }); } else { // 系统权限被拒绝,不更新用户偏好设置 Alert.alert( '权限被拒绝', '请在系统设置中开启通知权限,然后再尝试开启推送功能', [ { text: '取消', style: 'cancel' }, { text: '去设置', onPress: () => Linking.openSettings() } ] ); } } catch (error) { console.error('开启推送通知失败:', error); Alert.alert('错误', '请求通知权限失败'); } } else { try { // 关闭推送,保存用户偏好设置 await saveNotificationEnabled(false); setNotificationEnabled(false); } catch (error) { console.error('关闭推送通知失败:', error); 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: '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, }, gradientBackground: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, }, decorativeCircle1: { position: 'absolute', top: 40, right: 20, width: 60, height: 60, borderRadius: 30, backgroundColor: '#0EA5E9', opacity: 0.1, }, decorativeCircle2: { position: 'absolute', bottom: -15, left: -15, width: 40, height: 40, borderRadius: 20, backgroundColor: '#0EA5E9', opacity: 0.05, }, scrollView: { flex: 1, }, // 部分容器 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, }, });