// app/_layout.tsx import 'expo-dev-client'; import '@/global.css'; import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; import { Stack } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import * as React from 'react'; import { View, Text, Platform, ActivityIndicator } from 'react-native'; import { NAV_THEME } from '@/lib/theme/constants'; import { useColorScheme } from '@/lib/theme/useColorScheme'; import { PortalHost } from '@rn-primitives/portal'; import { setAndroidNavigationBar } from '@/lib/android-navigation-bar'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { DatabaseProvider } from '@/components/DatabaseProvider'; import { ErrorBoundary } from '@/components/ErrorBoundary'; import { SettingsDrawerProvider } from '@/lib/contexts/SettingsDrawerContext'; import SettingsDrawer from '@/components/SettingsDrawer'; import RelayInitializer from '@/components/RelayInitializer'; import OfflineIndicator from '@/components/OfflineIndicator'; import { useNDKStore, FLAGS } from '@/lib/stores/ndk'; import { NDKContext } from '@/lib/auth/ReactQueryAuthProvider'; import { useWorkoutStore } from '@/stores/workoutStore'; import { ConnectivityService } from '@/lib/db/services/ConnectivityService'; import { AuthProvider } from '@/lib/auth/AuthProvider'; import { ReactQueryAuthProvider } from '@/lib/auth/ReactQueryAuthProvider'; import { QueryClientProvider } from '@tanstack/react-query'; import { createQueryClient } from '@/lib/queryClient'; // Import splash screens with improved fallback mechanism let SplashComponent: React.ComponentType<{onFinish: () => void}>; let useVideoSplash = false; // Determine if we should use video splash based on platform if (Platform.OS === 'ios') { // On iOS, try to use the video splash screen try { // Check if expo-av is available require('expo-av'); useVideoSplash = true; console.log('expo-av is available, will use VideoSplashScreen on iOS'); } catch (e) { console.warn('expo-av not available on iOS:', e); useVideoSplash = false; } } else { // On Android, directly use SimpleSplashScreen to avoid issues console.log('Android platform detected, using SimpleSplashScreen'); useVideoSplash = false; } // Import the appropriate splash screen component if (useVideoSplash) { try { SplashComponent = require('@/components/VideoSplashScreen').default; console.log('Successfully imported VideoSplashScreen'); } catch (e) { console.warn('Failed to import VideoSplashScreen:', e); useVideoSplash = false; } } // If video splash is not available or failed to import, use simple splash if (!useVideoSplash) { try { SplashComponent = require('@/components/SimpleSplashScreen').default; console.log('Using SimpleSplashScreen'); } catch (simpleSplashError) { console.warn('Failed to import SimpleSplashScreen:', simpleSplashError); // Last resort fallback is an inline component SplashComponent = ({onFinish}) => { React.useEffect(() => { // Call onFinish after a short delay const timer = setTimeout(() => { onFinish(); }, 1000); return () => clearTimeout(timer); }, [onFinish]); return ( POWR ); }; } } console.log('_layout.tsx loaded'); const LIGHT_THEME = { ...DefaultTheme, colors: NAV_THEME.light, }; const DARK_THEME = { ...DarkTheme, colors: NAV_THEME.dark, }; export default function RootLayout() { const [isInitialized, setIsInitialized] = React.useState(false); const [isSplashFinished, setIsSplashFinished] = React.useState(false); const { colorScheme, isDarkColorScheme } = useColorScheme(); const { init } = useNDKStore(); const initializationPromise = React.useRef | null>(null); // Create a query client instance that can be used by both auth systems const queryClient = React.useMemo(() => createQueryClient(), []); // Start app initialization immediately React.useEffect(() => { if (!initializationPromise.current) { initializationPromise.current = (async () => { try { console.log('Starting app initialization in background...'); if (Platform.OS === 'web') { document.documentElement.classList.add('bg-background'); } setAndroidNavigationBar(colorScheme); // Initialize connectivity service first const connectivityService = ConnectivityService.getInstance(); const isOnline = await connectivityService.checkNetworkStatus(); console.log(`Network connectivity: ${isOnline ? 'online' : 'offline'}`); // Start database initialization and NDK initialization in parallel const initPromises = []; // Initialize NDK with credentials migration first const ndkPromise = (async () => { try { // Import and run key migration before NDK init const { migrateKeysIfNeeded } = await import('@/lib/auth/persistence/secureStorage'); console.log('Running pre-NDK credential migration...'); await migrateKeysIfNeeded(); // Now initialize NDK console.log('Starting NDK initialization...'); return await init(); } catch (error) { console.error('NDK initialization error:', error); // Continue even if NDK fails return { offlineMode: true }; } })(); initPromises.push(ndkPromise); // Load favorites from SQLite (local operation) const favoritesPromise = useWorkoutStore.getState().loadFavorites() .catch(error => { console.error('Error loading favorites:', error); // Continue even if loading favorites fails }); initPromises.push(favoritesPromise); // Wait for all initialization tasks with a timeout const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Initialization timeout')), 10000) ); try { // Use Promise.allSettled to continue even if some promises fail await Promise.race([ Promise.allSettled(initPromises), timeoutPromise ]); } catch (error) { console.warn('Some initialization tasks timed out, continuing anyway:', error); } console.log('App initialization completed!'); setIsInitialized(true); } catch (error) { console.error('Failed to initialize:', error); // Still mark as initialized to prevent hanging setIsInitialized(true); } })(); } return () => { // This is just for cleanup, the promise will continue executing initializationPromise.current = null; }; }, []); // Function to handle splash finish - will check if initialization is also complete const handleSplashFinish = React.useCallback(() => { console.log('Splash video finished playing'); setIsSplashFinished(true); // If initialization isn't done yet, we'll show a loading indicator if (!isInitialized) { console.log('Waiting for initialization to complete...'); } }, [isInitialized]); // Show splash screen if not finished if (!isSplashFinished) { try { return ; } catch (e) { console.error('Error rendering splash screen:', e); // Skip splash screen if there's an error if (!isInitialized) { return ( Loading... ); } // Force continue to main app setIsSplashFinished(true); return null; } } // If splash is done but initialization isn't, show loading if (!isInitialized) { return ( Finalizing setup... ); } // Main app UI wrapped in error boundary return ( {/* Conditionally render authentication providers based on feature flag */} {FLAGS.useReactQueryAuth ? ( /* Use React Query Auth system */ {/* React Query specific components */} {/* Settings drawer needs to be outside the navigation stack */} ) : ( /* Use Legacy Auth system but still provide QueryClientProvider and NDKContext for data fetching */ {/* Settings drawer needs to be outside the navigation stack */} )} ); }