import 'react-native-get-random-values'; import { useEffect, useState } from 'react'; import { View, ActivityIndicator } from 'react-native'; import { Stack } from 'expo-router'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import Toast from 'react-native-toast-message'; import { useHouseholdStore, useRealtimeSync } from '../src/hooks/useHousehold'; import { useNetworkSync } from '../src/hooks/useNetworkSync'; import { getOrCreateDeviceId, getStoredHouseholdId, getHousehold, registerMember } from '../src/services/household'; import { getFcmToken } from '../src/services/notifications'; import { COLORS } from '../src/constants'; function withTimeout(promise: Promise, ms: number): Promise { return Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), ms) ), ]); } function AppInit() { const setHousehold = useHouseholdStore((s) => s.setHousehold); const setDeviceId = useHouseholdStore((s) => s.setDeviceId); const setInitialized = useHouseholdStore((s) => s.setInitialized); const [hydrated, setHydrated] = useState(() => useHouseholdStore.persist.hasHydrated()); useRealtimeSync(); useNetworkSync(); // Step 1: wait for Zustand AsyncStorage hydration useEffect(() => { if (hydrated) return; console.log('[AppInit] waiting for store hydration...'); const unsub = useHouseholdStore.persist.onFinishHydration(() => { console.log('[AppInit] store hydrated'); setHydrated(true); }); return unsub; }, [hydrated]); // Step 2: run init logic after hydration is confirmed useEffect(() => { if (!hydrated) return; console.log('[AppInit] init start'); (async () => { try { const deviceId = await getOrCreateDeviceId(); console.log('[AppInit] deviceId:', deviceId.slice(0, 8)); setDeviceId(deviceId); const alreadyHasHousehold = useHouseholdStore.getState().household != null; console.log('[AppInit] alreadyHasHousehold:', alreadyHasHousehold); if (!alreadyHasHousehold) { const householdId = await getStoredHouseholdId(); console.log('[AppInit] storedHouseholdId:', householdId); if (householdId) { try { console.log('[AppInit] fetching household from PocketBase...'); const household = await withTimeout(getHousehold(householdId), 5000); if (household) { console.log('[AppInit] household loaded:', household.name); setHousehold(household); } } catch (err) { console.log('[AppInit] getHousehold failed (offline/timeout):', String(err)); } } } } catch (e) { console.error('[AppInit] unexpected error:', e); } finally { console.log('[AppInit] setInitialized()'); setInitialized(); const h = useHouseholdStore.getState().household; if (h) { getFcmToken() .catch(() => null) .then((token) => registerMember(h.id, token).catch(() => {})); } } })(); }, [hydrated]); return null; } export default function RootLayout() { const isInitialized = useHouseholdStore((s) => s.isInitialized); const setInitialized = useHouseholdStore((s) => s.setInitialized); // Absolute safety net: force init after 8s regardless of what happened above useEffect(() => { const timer = setTimeout(() => { if (!useHouseholdStore.getState().isInitialized) { console.warn('[RootLayout] safety timeout fired — forcing init'); setInitialized(); } }, 8000); return () => clearTimeout(timer); }, []); return ( {!isInitialized ? ( ) : ( <> )} ); }