import React, { useEffect, useState } from 'react'; import { Text, View } from 'react-native'; import { Redirect, Stack, usePathname } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { AppProvider, useApp } from '../context/AppContext'; import { CoachMarksProvider } from '../context/CoachMarksContext'; import { CoachMarksOverlay } from '../components/CoachMarksOverlay'; import { useColors } from '../constants/Colors'; import { initDatabase, AppMetaDb } from '../services/database'; import * as SecureStore from 'expo-secure-store'; import * as SplashScreen from 'expo-splash-screen'; import { AuthService } from '../services/authService'; import { AnimatedSplashScreen } from '../components/AnimatedSplashScreen'; // Prevent the splash screen from auto-hiding before asset loading is complete. SplashScreen.preventAutoHideAsync().catch(() => { }); const SECURE_INSTALL_MARKER = 'greenlens_install_v1'; const SHARE_INTENT_CALLBACK_PATH = '/dataUrl=greenlensShareKey'; const isShareIntentCallbackPath = (path: string | null | undefined) => path === SHARE_INTENT_CALLBACK_PATH; const toStartupErrorMessage = (error: unknown): string => { if (!error) return 'Unknown startup error'; if (error instanceof Error) return error.message; return String(error); }; const StartupFallback = ({ details }: { details?: string | null }) => ( GreenLens could not start. Please send this startup error to support. {details ? ( {details} ) : null} ); class RootErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean; errorMessage: string | null }> { state = { hasError: false, errorMessage: null }; static getDerivedStateFromError(error: unknown) { return { hasError: true, errorMessage: toStartupErrorMessage(error) }; } componentDidCatch(error: unknown) { console.error('[RootErrorBoundary]', error); } render() { if (this.state.hasError) { return ; } return this.props.children; } } const ensureInstallConsistency = async (): Promise => { try { const sqliteMarker = AppMetaDb.get('install_marker_v2'); const secureMarker = await SecureStore.getItemAsync(SECURE_INSTALL_MARKER).catch(() => null); if (sqliteMarker === '1' && secureMarker === '1') { return false; // Alles gut, keine Neuinstallation } if (sqliteMarker === '1' || secureMarker === '1') { // Teilweise vorhanden -> heilen, nicht löschen AppMetaDb.set('install_marker_v2', '1'); await SecureStore.setItemAsync(SECURE_INSTALL_MARKER, '1'); return false; } // Fresh Install: Alles zurücksetzen await AuthService.logout(); await AsyncStorage.removeItem('greenlens_show_tour'); AppMetaDb.set('install_marker_v2', '1'); await SecureStore.setItemAsync(SECURE_INSTALL_MARKER, '1'); return true; } catch (error) { console.error('Failed to initialize install marker', error); return false; } }; function RootLayoutInner() { const { isDarkMode, colorPalette, signOut, session, billingSummary, isActivatingEntitlement, isInitializing, isLoadingPlants, isLoadingBilling, } = useApp(); const colors = useColors(isDarkMode, colorPalette); const pathname = usePathname(); const [installCheckDone, setInstallCheckDone] = useState(false); const [splashAnimationComplete, setSplashAnimationComplete] = useState(false); useEffect(() => { (async () => { const didResetSessionForFreshInstall = await ensureInstallConsistency(); if (didResetSessionForFreshInstall) { await signOut(); } setInstallCheckDone(true); })(); }, [signOut]); const isAppReady = installCheckDone && !isInitializing && !isLoadingPlants; const hasActiveEntitlement = isActivatingEntitlement || (billingSummary?.entitlement?.plan === 'pro' && billingSummary?.entitlement?.status === 'active'); const isAllowedWithoutSession = pathname.includes('onboarding') || pathname.includes('auth/') || pathname.includes('scanner') || isShareIntentCallbackPath(pathname) || pathname.includes('profile/billing'); const isAllowedWithoutEntitlement = pathname.includes('auth/') || pathname.includes('onboarding') || pathname.includes('scanner') || isShareIntentCallbackPath(pathname) || pathname.includes('profile/billing'); let content = null; if (isAppReady) { if (!session) { // Only redirect if we are not already on an auth-related page or the scanner if (!isAllowedWithoutSession) { content = ; } else { content = ( ); } } else if (!hasActiveEntitlement && !isLoadingBilling && !isAllowedWithoutEntitlement) { content = ; } else { content = ( <> ); } } return ( <> {content} {!splashAnimationComplete && ( setSplashAnimationComplete(true)} /> )} ); } export default function RootLayout() { let dbInitError: string | null = null; try { initDatabase(); } catch (e) { dbInitError = String(e); } if (dbInitError) { return ; } return ( ); }