feat: category/collection detail screens + Inter font loading
New screens: - Category detail (workout/category/[id]): filtered workout list with SwiftUI segmented Picker for level sub-filter - Collection detail (collection/[id]): hero header with gradient, stats (workouts/minutes/calories), numbered workout list Root layout: - Inter font loading (400-900 weights) via @expo-google-fonts - SplashScreen integration for font loading gate - Route config for all screens with appropriate animations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
120
app/_layout.tsx
120
app/_layout.tsx
@@ -1,60 +1,88 @@
|
||||
import { useEffect } from 'react';
|
||||
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
||||
import { Stack } from 'expo-router';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import * as SplashScreen from 'expo-splash-screen';
|
||||
import 'react-native-reanimated';
|
||||
/**
|
||||
* TabataFit Root Layout
|
||||
* Expo Router v3 + Inter font loading
|
||||
*/
|
||||
|
||||
import { useColorScheme } from '@/hooks/use-color-scheme';
|
||||
import { useIsOnboardingComplete } from '@/src/features/onboarding/hooks/useOnboarding';
|
||||
import { useCallback } from 'react'
|
||||
import { Stack } from 'expo-router'
|
||||
import { StatusBar } from 'expo-status-bar'
|
||||
import { View } from 'react-native'
|
||||
import * as SplashScreen from 'expo-splash-screen'
|
||||
import {
|
||||
useFonts,
|
||||
Inter_400Regular,
|
||||
Inter_500Medium,
|
||||
Inter_600SemiBold,
|
||||
Inter_700Bold,
|
||||
Inter_900Black,
|
||||
} from '@expo-google-fonts/inter'
|
||||
|
||||
// Prevent splash screen from auto-hiding
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
import { DARK } from '@/src/shared/constants/colors'
|
||||
|
||||
export const unstable_settings = {
|
||||
anchor: '(tabs)',
|
||||
};
|
||||
SplashScreen.preventAutoHideAsync()
|
||||
|
||||
export default function RootLayout() {
|
||||
const colorScheme = useColorScheme();
|
||||
const isOnboardingComplete = useIsOnboardingComplete();
|
||||
const [fontsLoaded] = useFonts({
|
||||
Inter_400Regular,
|
||||
Inter_500Medium,
|
||||
Inter_600SemiBold,
|
||||
Inter_700Bold,
|
||||
Inter_900Black,
|
||||
})
|
||||
|
||||
// Hide splash screen once we have a definite state
|
||||
useEffect(() => {
|
||||
if (isOnboardingComplete !== undefined) {
|
||||
SplashScreen.hideAsync();
|
||||
const onLayoutRootView = useCallback(async () => {
|
||||
if (fontsLoaded) {
|
||||
await SplashScreen.hideAsync()
|
||||
}
|
||||
}, [isOnboardingComplete]);
|
||||
}, [fontsLoaded])
|
||||
|
||||
// Show nothing while Zustand hydrates from AsyncStorage
|
||||
if (isOnboardingComplete === undefined) {
|
||||
return null;
|
||||
if (!fontsLoaded) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||
<Stack>
|
||||
<Stack.Screen
|
||||
name="onboarding"
|
||||
options={{
|
||||
<View style={{ flex: 1, backgroundColor: DARK.BASE }} onLayout={onLayoutRootView}>
|
||||
<StatusBar style="light" />
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
presentation: 'fullScreenModal',
|
||||
animation: 'fade',
|
||||
gestureEnabled: false,
|
||||
contentStyle: { backgroundColor: DARK.BASE },
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen
|
||||
name="timer"
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: 'fullScreenModal',
|
||||
animation: 'fade',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
|
||||
</Stack>
|
||||
<StatusBar style="auto" />
|
||||
</ThemeProvider>
|
||||
);
|
||||
>
|
||||
<Stack.Screen name="(tabs)" />
|
||||
<Stack.Screen
|
||||
name="workout/[id]"
|
||||
options={{
|
||||
animation: 'slide_from_bottom',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="workout/category/[id]"
|
||||
options={{
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="collection/[id]"
|
||||
options={{
|
||||
animation: 'slide_from_right',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="player/[id]"
|
||||
options={{
|
||||
presentation: 'fullScreenModal',
|
||||
animation: 'fade',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="complete/[id]"
|
||||
options={{
|
||||
animation: 'fade',
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user