import ActivityHeatMap from '@/components/ActivityHeatMap'; import { PRIVACY_POLICY_URL, USER_AGREEMENT_URL } from '@/constants/Agree'; import { ROUTES } from '@/constants/Routes'; 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 { log } from '@/utils/logger'; import { getNotificationEnabled, setNotificationEnabled as saveNotificationEnabled } from '@/utils/userPreferences'; import { getItem, setItem } from '@/utils/kvStore'; import { Ionicons } from '@expo/vector-icons'; import { useFocusEffect } from '@react-navigation/native'; import { Image } from 'expo-image'; import { LinearGradient } from 'expo-linear-gradient'; import React, { useEffect, useMemo, useRef, 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 { requestPermission, sendNotification, } = useNotifications(); const [notificationEnabled, setNotificationEnabled] = useState(false); // 开发者模式相关状态 const [showDeveloperSection, setShowDeveloperSection] = useState(false); const clickTimestamps = useRef([]); const clickTimeoutRef = useRef(null); // 计算底部间距 const bottomPadding = useMemo(() => { return getTabBarBottomPadding(60) + (insets?.bottom ?? 0); }, [insets?.bottom]); // 直接使用 Redux 中的用户信息,避免重复状态管理 const userProfile = useAppSelector((state) => state.user.profile); // 页面聚焦时获取最新用户信息 useFocusEffect( React.useCallback(() => { dispatch(fetchMyProfile()); dispatch(fetchActivityHistory()); // 加载用户推送偏好设置 loadNotificationPreference(); // 加载开发者模式状态 loadDeveloperModeState(); }, [dispatch]) ); // 加载用户推送偏好设置 const loadNotificationPreference = async () => { try { const enabled = await getNotificationEnabled(); setNotificationEnabled(enabled); } catch (error) { console.error('加载推送偏好设置失败:', error); } }; // 加载开发者模式状态 const loadDeveloperModeState = async () => { try { const enabled = await getItem('developer_mode_enabled'); if (enabled === 'true') { setShowDeveloperSection(true); } } catch (error) { console.error('加载开发者模式状态失败:', error); } }; // 保存开发者模式状态 const saveDeveloperModeState = async (enabled: boolean) => { try { await setItem('developer_mode_enabled', enabled.toString()); } 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(); loadDeveloperModeState(); }, []); // 处理用户名连续点击 const handleUserNamePress = () => { const now = Date.now(); clickTimestamps.current.push(now); // 清除之前的超时 if (clickTimeoutRef.current) { clearTimeout(clickTimeoutRef.current); } // 只保留最近1秒内的点击 clickTimestamps.current = clickTimestamps.current.filter(timestamp => now - timestamp <= 1000); // 检查是否有3次连续点击 if (clickTimestamps.current.length >= 3) { setShowDeveloperSection(true); saveDeveloperModeState(true); // 持久化保存开发者模式状态 clickTimestamps.current = []; // 清空点击记录 log.info('开发者模式已激活'); } else { // 1秒后清空点击记录 clickTimeoutRef.current = setTimeout(() => { clickTimestamps.current = []; }, 1000); } }; // 处理通知开关变化 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, }, ], }, // 开发者section(需要连续点击三次用户名激活) ...(showDeveloperSection ? [{ title: '开发者', items: [ { icon: 'code-slash-outline' as const, title: '开发者选项', onPress: () => pushIfAuthedElseLogin(ROUTES.DEVELOPER), }, ], }] : []), { 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, }, });