/** * TabataFit Root Layout * Expo Router v3 + Inter font loading * Waits for font + store hydration before rendering */ import '@/src/shared/i18n' import '@/src/shared/i18n/types' import { useState, useEffect, useCallback } from 'react' import { Stack } from 'expo-router' import { StatusBar } from 'expo-status-bar' import { View } from 'react-native' import * as SplashScreen from 'expo-splash-screen' import * as Notifications from 'expo-notifications' import { useFonts, Inter_400Regular, Inter_500Medium, Inter_600SemiBold, Inter_700Bold, Inter_900Black, } from '@expo-google-fonts/inter' import { PostHogProvider } from 'posthog-react-native' import { ThemeProvider, useThemeColors } from '@/src/shared/theme' import { useUserStore } from '@/src/shared/stores' import { useNotifications } from '@/src/shared/hooks' import { initializePurchases } from '@/src/shared/services/purchases' import { initializeAnalytics, getPostHogClient, trackScreen } from '@/src/shared/services/analytics' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: false, shouldPlaySound: false, shouldSetBadge: false, shouldShowBanner: false, shouldShowList: false, }), }) SplashScreen.preventAutoHideAsync() // Create React Query Client const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5 minutes gcTime: 1000 * 60 * 60 * 24, // 24 hours retry: 2, refetchOnWindowFocus: false, }, }, }) function RootLayoutInner() { const colors = useThemeColors() const [fontsLoaded] = useFonts({ Inter_400Regular, Inter_500Medium, Inter_600SemiBold, Inter_700Bold, Inter_900Black, }) useNotifications() // Wait for persisted store to hydrate from AsyncStorage const [hydrated, setHydrated] = useState(useUserStore.persist.hasHydrated()) useEffect(() => { const unsub = useUserStore.persist.onFinishHydration(() => setHydrated(true)) return unsub }, []) // Initialize RevenueCat + PostHog after hydration useEffect(() => { if (hydrated) { initializePurchases().catch((err) => { console.error('Failed to initialize RevenueCat:', err) }) initializeAnalytics().catch((err) => { console.error('Failed to initialize PostHog:', err) }) } }, [hydrated]) const onLayoutRootView = useCallback(async () => { if (fontsLoaded && hydrated) { await SplashScreen.hideAsync() } }, [fontsLoaded, hydrated]) if (!fontsLoaded || !hydrated) { return null } const content = ( ) const posthogClient = getPostHogClient() // Only wrap with PostHogProvider if client is initialized if (!posthogClient) { return content } return ( {content} ) } export default function RootLayout() { return ( ) }