import { useEffect } from 'react'; import { AppState } from 'react-native'; import { Stack } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import * as Notifications from 'expo-notifications'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { KeyboardProvider } from 'react-native-keyboard-controller'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { ActionSheetProvider } from '@expo/react-native-action-sheet'; import * as SplashScreen from 'expo-splash-screen'; import { useFonts, Nunito_400Regular, Nunito_600SemiBold, Nunito_700Bold, Nunito_800ExtraBold, } from '@expo-google-fonts/nunito'; import { supabase } from '../lib/supabase'; import { useAuthStore } from '../stores/auth'; import { useThemeStore } from '../stores/theme'; import { useRealtimeDebugStore } from '../stores/realtimeDebug'; import { useColors } from '../lib/theme'; import { useLanguageStore } from '../stores/language'; import { useAppLockStore } from '../stores/appLock'; import { BrandSplash } from '../components/BrandSplash'; import { AppLockGate } from '../components/AppLockGate'; import { DeviceLimitReachedSheet } from '../components/DeviceLimitReachedSheet'; import '../lib/i18n'; // i18next-Init via Side-Effect import '../global.css'; SplashScreen.preventAutoHideAsync(); Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowBanner: true, shouldShowList: true, shouldPlaySound: true, shouldSetBadge: false, }), }); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 2, staleTime: 1000 * 60, }, }, }); function RootLayoutInner() { const { loading, init } = useAuthStore(); const initTheme = useThemeStore((s) => s.init); const colorScheme = useThemeStore((s) => s.colorScheme); const initLanguage = useLanguageStore((s) => s.init); const initAppLock = useAppLockStore((s) => s.init); const appLockReady = useAppLockStore((s) => s.ready); const initRealtimeDebug = useRealtimeDebugStore((s) => s.init); const colors = useColors(); const [fontsLoaded] = useFonts({ Nunito_400Regular, Nunito_600SemiBold, Nunito_700Bold, Nunito_800ExtraBold, }); useEffect(() => { init(); initTheme(); initLanguage(); initAppLock(); if (__DEV__) initRealtimeDebug(); }, []); // Supabase-Doku-Pattern für RN: Token-Auto-Refresh nur wenn App aktiv ist. // Plus Foreground-Reconnect via onAuthStateChange (TOKEN_REFRESHED → // realtime.setAuth wird intern getriggert). Fixt den Realtime-Disconnect-Bug // bei lange eingeloggten Usern (siehe `project_session_2026-05-15_push.md`). useEffect(() => { const sub = AppState.addEventListener('change', (state) => { if (state === 'active') { supabase.auth.startAutoRefresh(); } else { supabase.auth.stopAutoRefresh(); } }); return () => sub.remove(); }, []); useEffect(() => { if (fontsLoaded && !loading && appLockReady) { SplashScreen.hideAsync(); } }, [fontsLoaded, loading, appLockReady]); if (!fontsLoaded || loading || !appLockReady) { return ; } return ( ); } export default function RootLayout() { return ( ); }