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 (
);
}