import '@/i18n'; import { DefaultTheme, ThemeProvider } from '@react-navigation/native'; import { useFonts } from 'expo-font'; import { Stack, useRouter } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import 'react-native-reanimated'; import PrivacyConsentModal from '@/components/PrivacyConsentModal'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useQuickActions } from '@/hooks/useQuickActions'; import { hrvMonitorService } from '@/services/hrvMonitor'; import { cleanupLegacyMedicationNotifications } from '@/services/medicationNotificationCleanup'; import { clearBadgeCount, notificationService } from '@/services/notifications'; import { setupQuickActions } from '@/services/quickActions'; import { sleepMonitorService } from '@/services/sleepMonitor'; import { initializeWaterRecordBridge } from '@/services/waterRecordBridge'; import { WaterRecordSource } from '@/services/waterRecords'; import { workoutMonitorService } from '@/services/workoutMonitor'; import { store } from '@/store'; import { hydrateActiveSchedule, selectActiveFastingSchedule } from '@/store/fastingSlice'; import { fetchMyProfile, logout, setPrivacyAgreed } from '@/store/userSlice'; import { createWaterRecordAction } from '@/store/waterSlice'; import { loadActiveFastingSchedule } from '@/utils/fasting'; import { initializeHealthPermissions } from '@/utils/health'; import { MoodNotificationHelpers, NutritionNotificationHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers'; import { getWaterReminderSettings } from '@/utils/userPreferences'; import { clearPendingWaterRecords, syncPendingWidgetChanges } from '@/utils/widgetDataSync'; import React, { useEffect } from 'react'; import { AppState, AppStateStatus } from 'react-native'; import { DialogProvider } from '@/components/ui/DialogProvider'; import { MembershipModalProvider } from '@/contexts/MembershipModalContext'; import { ToastProvider } from '@/contexts/ToastContext'; import { useAuthGuard } from '@/hooks/useAuthGuard'; import { STORAGE_KEYS, setUnauthorizedHandler } from '@/services/api'; import { BackgroundTaskManager } from '@/services/backgroundTaskManagerV2'; import { fetchChallenges } from '@/store/challengesSlice'; import { loadTabBarConfigs } from '@/store/tabBarConfigSlice'; import AsyncStorage from '@/utils/kvStore'; import { logger } from '@/utils/logger'; import { Provider } from 'react-redux'; // 在开发环境中导入调试工具 let BackgroundTaskDebugger: any = null; if (__DEV__) { try { const debuggerModule = require('@/services/backgroundTaskDebugger'); BackgroundTaskDebugger = debuggerModule.BackgroundTaskDebugger; } catch (error) { logger.warn('无法导入后台任务调试工具:', error); } } function Bootstrapper({ children }: { children: React.ReactNode }) { const dispatch = useAppDispatch(); const router = useRouter(); const { profile, onboardingCompleted } = useAppSelector((state) => state.user); const activeFastingSchedule = useAppSelector(selectActiveFastingSchedule); const [showPrivacyModal, setShowPrivacyModal] = React.useState(false); const { isLoggedIn } = useAuthGuard() const fastingHydrationRequestedRef = React.useRef(false); const permissionInitializedRef = React.useRef(false); // 初始化快捷动作处理 useQuickActions(); // 注册401未授权处理器(应用启动时执行一次) React.useEffect(() => { const handle401 = async () => { try { logger.info('[401处理] 开始处理登录过期'); // 清除Redux状态 await dispatch(logout()); // 跳转到登录页 router.push('/auth/login'); logger.info('[401处理] 登录过期处理完成'); } catch (error) { logger.error('[401处理] 处理失败:', error); } }; setUnauthorizedHandler(handle401); logger.info('[401处理器] 已注册到API服务'); }, [dispatch, router]); React.useEffect(() => { if (fastingHydrationRequestedRef.current) return; if (activeFastingSchedule) { fastingHydrationRequestedRef.current = true; return; } fastingHydrationRequestedRef.current = true; let cancelled = false; const hydrate = async () => { try { const stored = await loadActiveFastingSchedule(); if (cancelled || !stored) return; if (store.getState().fasting.activeSchedule) return; dispatch(hydrateActiveSchedule(stored)); } catch (error) { logger.warn('恢复断食计划失败:', error); } }; hydrate(); return () => { cancelled = true; }; }, [dispatch, activeFastingSchedule]); useEffect(() => { if (isLoggedIn) { dispatch(fetchChallenges()); } }, [isLoggedIn]); // 初始化底部栏配置 useEffect(() => { dispatch(loadTabBarConfigs()); }, [dispatch]); // ==================== 基础服务初始化(不需要权限,总是执行)==================== React.useEffect(() => { const initializeBasicServices = async () => { try { logger.info('🚀 开始初始化基础服务(不需要权限)...'); // 1. 加载用户数据(首屏展示需要) await dispatch(fetchMyProfile()); logger.info('✅ 用户数据加载完成'); // 2. 初始化 HealthKit 权限系统(不请求权限,仅初始化) initializeHealthPermissions(); logger.info('✅ HealthKit 权限系统初始化完成'); // 3. 初始化快捷动作(用户可能立即使用) await setupQuickActions(); logger.info('✅ 快捷动作初始化完成'); // 5. 初始化喝水记录 Bridge initializeWaterRecordBridge(); logger.info('✅ 喝水记录 Bridge 初始化完成'); logger.info('🎉 基础服务初始化完成'); } catch (error) { logger.error('❌ 基础服务初始化失败:', error); } }; initializeBasicServices(); }, [dispatch]); // ==================== 应用状态监听 - 进入前台时清除角标 ==================== React.useEffect(() => { const subscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => { if (nextAppState === 'active') { // 应用进入前台时清除角标 clearBadgeCount(); } }); return () => { subscription.remove(); }; }, []); // ==================== 权限相关服务初始化(应用启动时执行)==================== React.useEffect(() => { // 如果已经初始化过,则跳过(确保只初始化一次) if (permissionInitializedRef.current) { return; } permissionInitializedRef.current = true; const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); // ==================== 辅助函数 ==================== // 异步同步 Widget 数据(不阻塞主流程) const syncWidgetDataInBackground = async () => { try { const widgetSync = await syncPendingWidgetChanges(); if (widgetSync.hasPendingChanges && widgetSync.pendingRecords) { logger.info(`🔄 检测到 ${widgetSync.pendingRecords.length} 条待同步的水记录`); // 异步处理每条记录 for (const record of widgetSync.pendingRecords) { try { await store.dispatch(createWaterRecordAction({ amount: record.amount, recordedAt: record.recordedAt, source: WaterRecordSource.Auto, })).unwrap(); logger.info(`✅ 成功同步水记录: ${record.amount}ml at ${record.recordedAt}`); } catch (error) { logger.error('❌ 同步水记录失败:', error); } } // 清除已同步的记录 await clearPendingWaterRecords(); logger.info('✅ 所有待同步的水记录已处理完成'); } } catch (error) { logger.error('❌ Widget 数据同步失败:', error); } }; // 批量注册所有通知提醒 const registerAllNotifications = async () => { try { logger.info('📢 开始批量注册通知提醒...'); // 并行注册所有通知,提高效率 await Promise.all([ // 营养提醒 NutritionNotificationHelpers.scheduleDailyLunchReminder(profile.name || '').then(() => logger.info('✅ 午餐提醒已注册') ), NutritionNotificationHelpers.scheduleDailyDinnerReminder(profile.name || '').then(() => logger.info('✅ 晚餐提醒已注册') ), // 心情提醒 MoodNotificationHelpers.scheduleDailyMoodReminder(profile.name || '').then(() => logger.info('✅ 心情提醒已注册') ), // 喝水提醒 - 需要先检查设置 getWaterReminderSettings().then(settings => { if (settings.enabled) { // 如果使用的是自定义提醒,scheduleCustomWaterReminders 会被调用(通常在设置页面保存时) // 但为了保险起见,这里也可以根据设置类型来决定调用哪个 // 目前逻辑似乎是 scheduleRegularWaterReminders 是默认的/旧的逻辑? // 查看 notificationHelpers.ts,scheduleRegularWaterReminders 是每2小时一次的固定逻辑 // 而 scheduleCustomWaterReminders 是根据用户设置的时间段和间隔 // 如果用户开启了提醒,应该使用 scheduleCustomWaterReminders WaterNotificationHelpers.scheduleCustomWaterReminders(profile.name || '用户', settings).then(() => logger.info('✅ 自定义喝水提醒已注册') ); } else { logger.info('ℹ️ 用户未开启喝水提醒,跳过注册'); } }), ]); // 检查断食通知(如果有活跃计划) const fastingSchedule = store.getState().fasting.activeSchedule; if (fastingSchedule) { logger.info('✅ 检测到活跃的断食计划,将通过页面 hook 自动安排通知'); } logger.info('🎉 所有通知提醒注册完成'); } catch (error) { logger.error('❌ 通知提醒注册失败:', error); } }; // 初始化后台任务管理器 const initializeBackgroundTaskManager = async () => { try { logger.info('⚙️ 初始化后台任务管理器...'); await BackgroundTaskManager.getInstance().initialize(); logger.info('✅ 后台任务管理器初始化成功'); // 简单的任务调度检查 const taskManager = BackgroundTaskManager.getInstance(); const status = await taskManager.getStatus(); if (status === 'available') { const pendingRequests = await taskManager.getPendingRequests(); if (pendingRequests.length === 0) { await taskManager.scheduleNextTask(); logger.info('✅ 已调度新的后台任务'); } } } catch (error) { logger.error('❌ 后台任务管理器初始化失败:', error); } }; // 初始化健康监听服务(锻炼 + 睡眠) const initializeHealthMonitoring = async () => { try { logger.info('💪 初始化健康监听服务...'); const [workoutResult, sleepResult, hrvResult] = await Promise.allSettled([ workoutMonitorService.initialize(), sleepMonitorService.initialize(), hrvMonitorService.initialize(), ]); const workoutReady = workoutResult.status === 'fulfilled'; if (workoutReady) { logger.info('✅ 锻炼监听服务初始化成功'); } else { logger.error('❌ 锻炼监听服务初始化失败:', workoutResult.reason); } const sleepReady = sleepResult.status === 'fulfilled'; if (sleepReady) { logger.info('✅ 睡眠监听服务初始化成功'); } else { logger.error('❌ 睡眠监听服务初始化失败:', sleepResult.reason); } const hrvReady = hrvResult.status === 'fulfilled'; if (hrvReady) { logger.info('✅ HRV 监听服务初始化成功'); } else { logger.error('❌ HRV 监听服务初始化失败:', hrvResult.reason); } if (workoutReady && sleepReady && hrvReady) { logger.info('🎉 健康监听服务初始化完成'); } else { logger.warn('⚠️ 健康监听服务部分未能初始化成功,请检查上述错误日志'); } return workoutReady && sleepReady && hrvReady; } catch (error) { logger.error('❌ 健康监听服务初始化失败:', error); return false; } }; // 后台任务详细状态检查(空闲时执行) const checkBackgroundTaskStatus = async () => { try { logger.info('🔍 检查后台任务详细状态...'); const taskManager = BackgroundTaskManager.getInstance(); const status = await taskManager.getStatus(); const statusText = await taskManager.checkStatus(); logger.info(`📊 后台任务状态: ${status} (${statusText})`); // 检查上次执行时间 const lastCheckTime = await taskManager.getLastBackgroundCheckTime(); if (lastCheckTime) { const timeSinceLastCheck = Date.now() - lastCheckTime; const hoursSinceLastCheck = timeSinceLastCheck / (1000 * 60 * 60); logger.info(`⏱️ 上次执行: ${new Date(lastCheckTime).toLocaleString()} (${hoursSinceLastCheck.toFixed(1)}小时前)`); if (hoursSinceLastCheck > 24) { logger.warn('⚠️ 超过24小时未执行后台任务,请检查系统设置'); } } logger.info('✅ 后台任务状态检查完成'); } catch (error) { logger.error('❌ 后台任务状态检查失败:', error); } }; // 权限服务初始化 const initializePermissionServices = async () => { try { logger.info('🔐 开始初始化需要权限的服务...'); // 1. 初始化通知服务(包含权限请求) await notificationService.initialize(); logger.info('✅ 通知服务初始化完成'); // 2. 清理旧的药品本地通知(迁移到服务端推送) cleanupLegacyMedicationNotifications().catch(error => { logger.error('❌ 清理旧药品通知失败:', error); }); // 3. 异步同步 Widget 数据(不阻塞主流程) syncWidgetDataInBackground(); logger.info('🎉 权限相关服务初始化完成'); logger.info('💡 HealthKit 权限将在用户首次访问健康数据时请求'); } catch (error) { logger.error('❌ 权限相关服务初始化失败:', error); throw error; } }; // ==================== 后台服务初始化(延迟执行)==================== const initializeBackgroundServices = () => { const { InteractionManager } = require('react-native'); InteractionManager.runAfterInteractions(() => { setTimeout(async () => { try { logger.info('📅 开始初始化后台服务...'); // 1. 批量注册所有通知提醒 await registerAllNotifications(); // 2. 初始化后台任务管理器 await initializeBackgroundTaskManager(); // 3. 初始化健康监听服务 await initializeHealthMonitoring(); logger.info('🎉 后台服务初始化完成'); } catch (error) { logger.error('❌ 后台服务初始化失败:', error); } }, 3000); }); }; // ==================== 空闲服务初始化==================== const initializeIdleServices = () => { setTimeout(async () => { try { logger.info('🔄 开始初始化空闲服务...'); // 1. 后台任务详细状态检查 await checkBackgroundTaskStatus(); // 2. 开发环境调试工具 if (__DEV__ && BackgroundTaskDebugger) { BackgroundTaskDebugger.getInstance().initialize(); logger.info('✅ 后台任务调试工具已初始化(开发环境)'); } logger.info('🎉 空闲服务初始化完成'); } catch (error) { logger.error('❌ 空闲服务初始化失败:', error); } }, 8000); }; const runInitializationSequence = async () => { try { await initializePermissionServices(); } catch { logger.warn('⚠️ 权限相关服务初始化失败,将继续启动后台和空闲服务以便后续重试'); } // 交互完成后执行后台服务 initializeBackgroundServices(); // 空闲时执行非关键服务 initializeIdleServices(); }; runInitializationSequence(); }, []); // 每次应用启动都执行,不依赖其他状态 React.useEffect(() => { const getPrivacyAgreed = async () => { const str = await AsyncStorage.getItem(STORAGE_KEYS.privacyAgreed) setShowPrivacyModal(str !== 'true'); } getPrivacyAgreed(); }, []); const handlePrivacyAgree = () => { dispatch(setPrivacyAgreed()); setShowPrivacyModal(false); }; const handlePrivacyDisagree = () => { // RNExitApp.exitApp(); }; return ( {children} ); } export default function RootLayout() { const [loaded] = useFonts({ SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), }); if (!loaded) { // Async font loading only occurs in development. return null; } return ( ); }