Some checks failed
CI / Lint & Typecheck (push) Has been cancelled
CI / Test (routes) (push) Has been cancelled
CI / Test (security) (push) Has been cancelled
CI / Test (services) (push) Has been cancelled
CI / Test (unit) (push) Has been cancelled
CI / Test (integration) (push) Has been cancelled
CI / Test Coverage (push) Has been cancelled
CI / Build (push) Has been cancelled
129 lines
3.8 KiB
TypeScript
129 lines
3.8 KiB
TypeScript
/**
|
|
* Root layout - handles auth state and navigation
|
|
*/
|
|
import { Buffer } from 'buffer';
|
|
global.Buffer = Buffer;
|
|
|
|
import '../global.css';
|
|
import { useEffect, useState } from 'react';
|
|
import { Stack, useRouter, useSegments } from 'expo-router';
|
|
import { StatusBar } from 'expo-status-bar';
|
|
import { View, ActivityIndicator, StyleSheet } from 'react-native';
|
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
import { QueryProvider } from '@/providers/QueryProvider';
|
|
import { SocketProvider } from '@/providers/SocketProvider';
|
|
import { MediaServerProvider } from '@/providers/MediaServerProvider';
|
|
import { ErrorBoundary } from '@/components/ErrorBoundary';
|
|
import { useAuthStore } from '@/lib/authStore';
|
|
import { usePushNotifications } from '@/hooks/usePushNotifications';
|
|
import { colors } from '@/lib/theme';
|
|
|
|
function RootLayoutNav() {
|
|
const { isAuthenticated, isLoading, initialize } = useAuthStore();
|
|
const segments = useSegments();
|
|
const router = useRouter();
|
|
const [hasInitialized, setHasInitialized] = useState(false);
|
|
|
|
// Initialize push notifications (only when authenticated)
|
|
usePushNotifications();
|
|
|
|
// Initialize auth state on mount
|
|
useEffect(() => {
|
|
void initialize().finally(() => setHasInitialized(true));
|
|
}, [initialize]);
|
|
|
|
// Handle navigation based on auth state
|
|
// Note: We allow authenticated users to access (auth)/pair for adding servers
|
|
// Wait for initialization to complete before redirecting (prevents hot reload issues)
|
|
useEffect(() => {
|
|
if (isLoading || !hasInitialized) return;
|
|
|
|
const inAuthGroup = segments[0] === '(auth)';
|
|
|
|
if (!isAuthenticated && !inAuthGroup) {
|
|
// Not authenticated and not on auth screen - redirect to pair
|
|
router.replace('/(auth)/pair');
|
|
}
|
|
// Don't redirect away from pair if authenticated - user might be adding a server
|
|
}, [isAuthenticated, isLoading, hasInitialized, segments, router]);
|
|
|
|
// Show loading screen while initializing
|
|
if (isLoading) {
|
|
return (
|
|
<View style={styles.loadingContainer}>
|
|
<ActivityIndicator size="large" color={colors.cyan.core} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<StatusBar style="light" backgroundColor={colors.background.dark} translucent={false} />
|
|
<Stack
|
|
screenOptions={{
|
|
headerShown: false,
|
|
contentStyle: { backgroundColor: colors.background.dark },
|
|
animation: 'fade',
|
|
}}
|
|
>
|
|
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
|
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
|
<Stack.Screen
|
|
name="user"
|
|
options={{
|
|
headerShown: false,
|
|
presentation: 'card',
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="session"
|
|
options={{
|
|
headerShown: false,
|
|
presentation: 'card',
|
|
}}
|
|
/>
|
|
<Stack.Screen
|
|
name="settings"
|
|
options={{
|
|
headerShown: false,
|
|
presentation: 'modal',
|
|
}}
|
|
/>
|
|
<Stack.Screen name="+not-found" />
|
|
</Stack>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function RootLayout() {
|
|
return (
|
|
<GestureHandlerRootView style={styles.container}>
|
|
<SafeAreaProvider>
|
|
<ErrorBoundary>
|
|
<QueryProvider>
|
|
<SocketProvider>
|
|
<MediaServerProvider>
|
|
<RootLayoutNav />
|
|
</MediaServerProvider>
|
|
</SocketProvider>
|
|
</QueryProvider>
|
|
</ErrorBoundary>
|
|
</SafeAreaProvider>
|
|
</GestureHandlerRootView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: colors.background.dark,
|
|
},
|
|
loadingContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: colors.background.dark,
|
|
},
|
|
});
|