d3b73464e4
Use brand mark and wordmark images in the time clock Live Activity, migrate file copies to the modern expo-file-system File API, and add eas.json for TestFlight production builds. Co-authored-by: Cursor <cursoragent@cursor.com>
130 lines
3.3 KiB
TypeScript
130 lines
3.3 KiB
TypeScript
import { Stack } from "expo-router";
|
|
import {
|
|
Inter_400Regular,
|
|
Inter_500Medium,
|
|
Inter_600SemiBold,
|
|
Inter_700Bold,
|
|
} from "@expo-google-fonts/inter";
|
|
import {
|
|
PlayfairDisplay_600SemiBold,
|
|
PlayfairDisplay_700Bold,
|
|
} from "@expo-google-fonts/playfair-display";
|
|
import { useFonts } from "expo-font";
|
|
import * as SplashScreen from "expo-splash-screen";
|
|
import { useEffect, type ReactNode } from "react";
|
|
import { Platform, View } from "react-native";
|
|
import { StatusBar } from "expo-status-bar";
|
|
import "react-native-reanimated";
|
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
|
|
|
import { BrandBackground } from "@/components/BrandBackground";
|
|
import { LoadingScreen } from "@/components/LoadingScreen";
|
|
import { AccountsProvider, useAccounts } from "@/contexts/AccountsContext";
|
|
import { AuthProvider, useSession } from "@/contexts/AuthContext";
|
|
import { ThemeProvider, useAppTheme } from "@/contexts/ThemeContext";
|
|
import { TRPCProvider } from "@/lib/trpc";
|
|
import { ensureWidgetBrandAssets } from "@/lib/widget-brand-assets";
|
|
|
|
export { ErrorBoundary } from "expo-router";
|
|
|
|
SplashScreen.preventAutoHideAsync();
|
|
|
|
function AppServices({ children }: { children: ReactNode }) {
|
|
const { apiUrl, authStoragePrefix, activeAccountId } = useAccounts();
|
|
const remountKey = `${activeAccountId ?? "guest"}:${apiUrl}`;
|
|
|
|
return (
|
|
<AuthProvider apiUrl={apiUrl} storagePrefix={authStoragePrefix} key={remountKey}>
|
|
<TRPCProvider apiUrl={apiUrl} key={remountKey}>
|
|
{children}
|
|
</TRPCProvider>
|
|
</AuthProvider>
|
|
);
|
|
}
|
|
|
|
function ThemedChrome({ children }: { children: ReactNode }) {
|
|
const { isDark } = useAppTheme();
|
|
|
|
return (
|
|
<View style={{ flex: 1, backgroundColor: "transparent" }}>
|
|
<BrandBackground />
|
|
<View style={{ flex: 1, zIndex: 1 }}>
|
|
<StatusBar style={isDark ? "light" : "dark"} />
|
|
{children}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
export default function RootLayout() {
|
|
const [loaded, error] = useFonts({
|
|
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
|
Inter_400Regular,
|
|
Inter_500Medium,
|
|
Inter_600SemiBold,
|
|
Inter_700Bold,
|
|
PlayfairDisplay_600SemiBold,
|
|
PlayfairDisplay_700Bold,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (error) throw error;
|
|
}, [error]);
|
|
|
|
useEffect(() => {
|
|
if (loaded) {
|
|
SplashScreen.hideAsync();
|
|
}
|
|
}, [loaded]);
|
|
|
|
useEffect(() => {
|
|
if (Platform.OS === "ios") {
|
|
void ensureWidgetBrandAssets();
|
|
}
|
|
}, []);
|
|
|
|
if (!loaded) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<SafeAreaProvider>
|
|
<ThemeProvider>
|
|
<ThemedChrome>
|
|
<AccountsProvider>
|
|
<AppServices>
|
|
<RootNavigator />
|
|
</AppServices>
|
|
</AccountsProvider>
|
|
</ThemedChrome>
|
|
</ThemeProvider>
|
|
</SafeAreaProvider>
|
|
);
|
|
}
|
|
|
|
function RootNavigator() {
|
|
const { data: session, isPending } = useSession();
|
|
|
|
if (isPending) {
|
|
return <LoadingScreen message="Checking session…" />;
|
|
}
|
|
|
|
const isAuthenticated = Boolean(session?.user);
|
|
|
|
return (
|
|
<Stack
|
|
screenOptions={{
|
|
headerShown: false,
|
|
contentStyle: { backgroundColor: "transparent" },
|
|
}}
|
|
>
|
|
<Stack.Protected guard={!isAuthenticated}>
|
|
<Stack.Screen name="(auth)" />
|
|
</Stack.Protected>
|
|
<Stack.Protected guard={isAuthenticated}>
|
|
<Stack.Screen name="(app)" />
|
|
</Stack.Protected>
|
|
</Stack>
|
|
);
|
|
}
|