import React, { useEffect, useRef, useState } from 'react'; import { AppState, AppStateStatus, Platform, View, ActivityIndicator } from 'react-native'; import { Stack, useRouter } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { PaperProvider } from 'react-native-paper'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import * as SystemUI from 'expo-system-ui'; import { useFonts } from 'expo-font'; import { registerNotificationPresentationHandler } from '@/services/notification'; import { api } from '@/services/core'; import { wsService } from '@/services/core'; import { EventSubscriber } from '../src/infrastructure/EventSubscriber'; import { ThemeBootstrap, useAppColors, usePaperThemeFromStore, useResolvedColorScheme, } from '../src/theme'; import AppPromptBar from '../src/components/common/AppPromptBar'; import AppDialogHost from '../src/components/common/AppDialogHost'; import { installAlertOverride } from '@/services/ui'; import { useAuthStore } from '../src/stores'; import { checkForAPKUpdate } from '@/services/platform'; import { CallScreen, IncomingCallModal, FloatingCallWindow } from '../src/components/call'; import { jpushService } from '@/services/notification/jpushService'; registerNotificationPresentationHandler(); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 2, staleTime: 5 * 60 * 1000, }, }, }); installAlertOverride(); if (Platform.OS === 'web' && typeof document !== 'undefined') { const style = document.createElement('style'); style.textContent = ` input:focus, textarea:focus, select:focus { outline: none !important; outline-width: 0 !important; box-shadow: none !important; } input:focus-visible, textarea:focus-visible { outline: none !important; } *:focus { outline: none !important; } *:focus-visible { outline: none !important; } /* 修复移动端滑动问题 - 仅对根容器限制 */ html, body { overscroll-behavior: none; touch-action: manipulation; -webkit-overflow-scrolling: touch; } /* React Native Web 生成的滚动容器 - 允许双向滑动 */ [class*="css-view"] { touch-action: auto; -webkit-overflow-scrolling: touch; } /* 水平滚动容器 */ [data-horizontal-scroll="true"], div[style*="overflow-x"], div[style*="overflow: scroll"], div[style*="overflow: auto"] { touch-action: pan-x pan-y; -webkit-overflow-scrolling: touch; } /* 手势区域 - 允许滑动手势 */ [data-gesture-area="true"] { touch-action: pan-x pan-y; } /* 图片查看器手势区域 */ .react-native-image-viewer, [data-image-viewer="true"] { touch-action: pinch-zoom pan-x pan-y; } `; document.head.appendChild(style); } function SystemChrome() { const colors = useAppColors(); useEffect(() => { void SystemUI.setBackgroundColorAsync(colors.background.default); if (Platform.OS === 'web' && typeof document !== 'undefined') { const meta = document.querySelector('meta[name="theme-color"]'); if (meta) { meta.setAttribute('content', colors.background.default); } } }, [colors.background.default]); return null; } function SessionGate({ children }: { children: React.ReactNode }) { const [ready, setReady] = useState(false); const fetchCurrentUser = useAuthStore((s) => s.fetchCurrentUser); const colors = useAppColors(); useEffect(() => { fetchCurrentUser().finally(() => setReady(true)); }, [fetchCurrentUser]); if (!ready) { return ( ); } return <>{children}; } function NotificationBootstrap() { const appState = useRef(AppState.currentState); const permissionRequested = useRef(false); const router = useRouter(); useEffect(() => { const initNotifications = async () => { const { loadNotificationPreferences } = await import('@/services/notification'); await loadNotificationPreferences(); const { systemNotificationService } = await import('@/services/notification'); await systemNotificationService.initialize(); const { initBackgroundService } = await import('@/services/background'); await initBackgroundService(); const subscription = AppState.addEventListener('change', (nextAppState) => { if (appState.current.match(/inactive|background/) && nextAppState === 'active') { systemNotificationService.clearBadge(); } appState.current = nextAppState; }); // Listen for JPush notification taps — navigate to the relevant screen jpushService.onNotification((message) => { console.log('[NotificationBootstrap] JPush notification:', message.notificationEventType, message); if (message.notificationEventType !== 'notificationOpened') return; const extras = message.extras || {}; const notifType = extras.notification_type || message.notificationType; if (notifType === 'chat_message' || notifType === 'chat') { const conversationId = extras.conversation_id || extras.conversationId; const senderId = extras.sender_id || extras.senderId; const conversationType = extras.conversation_type || extras.conversationType; const groupId = extras.group_id || extras.groupId; const groupName = extras.group_name || extras.groupName; const groupAvatar = extras.group_avatar || extras.groupAvatar; if (conversationId) { const isGroup = conversationType === 'group'; const q = new URLSearchParams(); if (isGroup) { q.set('isGroupChat', '1'); if (groupId) q.set('groupId', groupId); if (groupName) q.set('groupName', groupName); if (groupAvatar) q.set('groupAvatar', groupAvatar); } else if (senderId) { q.set('userId', senderId); } const qs = q.toString(); router.push(`/chat/${encodeURIComponent(conversationId)}${qs ? `?${qs}` : ''}`); } else { router.push('/messages/notifications'); } } else { router.push('/messages/notifications'); } }); return () => { subscription.remove(); }; }; const requestPermissionOnLaunch = async () => { if (permissionRequested.current) return; permissionRequested.current = true; if (Platform.OS === 'web') return; try { const hasPermission = await jpushService.checkNotificationPermission(); if (!hasPermission) { await jpushService.requestNotificationPermission(); } } catch (error) { console.warn('[NotificationBootstrap] request permission on launch failed:', error); } }; initNotifications(); requestPermissionOnLaunch(); }, []); return null; } function APKUpdateBootstrap() { const hasChecked = useRef(false); useEffect(() => { if (hasChecked.current) return; hasChecked.current = true; // 延迟执行,避免与启动流程冲突 const timer = setTimeout(() => { checkForAPKUpdate().catch((error) => { console.error('APK update check failed:', error); }); }, 3000); return () => clearTimeout(timer); }, []); return null; } function ThemedStack() { const router = useRouter(); const resolved = useResolvedColorScheme(); return ( <> ); } function ThemedProviders({ children }: { children: React.ReactNode }) { const theme = usePaperThemeFromStore(); return {children}; } export default function RootLayout() { const [fontsLoaded] = useFonts({}); if (!fontsLoaded) { return ( ); } return ( ); }