Initial Upload
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
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
This commit is contained in:
128
apps/mobile/app/_layout.tsx
Normal file
128
apps/mobile/app/_layout.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user