Amba-Agent-App/app/(root)/_layout.tsx
2026-01-16 00:22:35 +03:00

177 lines
5.2 KiB
TypeScript

import "../global.css";
import "~/lib/i18n";
import { Theme, ThemeProvider, DefaultTheme } from "@react-navigation/native";
import { Stack, SplashScreen, router, usePathname } from "expo-router";
import { StatusBar } from "expo-status-bar";
import * as React from "react";
import { useFonts } from "expo-font";
import { PortalHost } from "@rn-primitives/portal";
import { useEffect, useRef } from "react";
import { View } from "react-native";
import {
useAuthStore,
useContactsStore,
useRecipientsStore,
} from "~/lib/stores";
import { setGlobalLoading } from "~/lib/stores/uiStore";
import GlobalLoadingOverlay from "~/components/ui/GlobalLoadingOverlay";
import ChatwootFloatingButton from "~/components/other/ChatwootFloatingButton";
import { enableRouterLoader } from "~/lib/navigation/routerLoader";
import { useFCM } from "~/lib/hooks/useFCM";
import * as Network from "expo-network";
import { HOME } from "~/lib/routes";
const NAV_THEME = {
light: {
background: "hsl(0 0% 100%)", // background
border: "hsla(30,100%,84%,0.24)", // border uses secondary color (FFB668) with 30% opacity
card: "hsla(145, 45%, 50%, 0.80)", // card uses primary background color (4CD080 with 8% opacity)
notification: "hsl(0 84.2% 60.2%)", // destructive
primary: "hsl(145, 45%, 25%)", // primary color 105D38
text: "hsl(0, 0%, 100%)", // text is white
},
};
const LIGHT_THEME: Theme = {
...DefaultTheme,
colors: NAV_THEME.light,
};
export {
// Catch any errors thrown by the Layout component.
ErrorBoundary,
} from "expo-router";
// Component to initialize stores
function AppContent() {
const { user, loading, initializeAuth } = useAuthStore();
const { initialize: initializeContacts } = useContactsStore();
const { initialize: initializeRecipients } = useRecipientsStore();
const pathname = usePathname();
// Track if we've already shown the initial auth loader
const hasInitializedAuth = useRef(false);
// Initialize FCM (Android only)
useFCM();
// Show opaque loader during initial auth check
useEffect(() => {
if (!hasInitializedAuth.current) {
// Show opaque loader on mount (auth is loading by default)
setGlobalLoading(true, { opaque: true });
hasInitializedAuth.current = true;
}
}, []);
// Hide loader when auth state is determined
useEffect(() => {
if (!loading && hasInitializedAuth.current) {
setGlobalLoading(false);
}
}, [loading]);
// Initialize auth listener
useEffect(() => {
console.log("Initializing auth listener");
const unsubscribe = initializeAuth();
return unsubscribe;
}, [initializeAuth]);
useEffect(() => {
enableRouterLoader();
}, []);
// Initialize contacts - defer to not block navigation
useEffect(() => {
// Defer contacts initialization to avoid blocking navigation
const initContacts = async () => {
// Wait for navigation to settle first
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log("Initializing contacts");
await initializeContacts();
};
initContacts();
}, [initializeContacts]);
// Initialize recipients when user changes
useEffect(() => {
if (user && !loading) {
console.log("Initializing recipients for user:", user.uid);
initializeRecipients(user);
}
}, [user, loading, initializeRecipients]);
useEffect(() => {
let interval: ReturnType<typeof setInterval> | null = null;
const checkNetwork = async () => {
try {
const state = await Network.getNetworkStateAsync();
const isOffline =
!state.isConnected || state.isInternetReachable === false;
const isOnNoInternet = pathname?.includes("nointernet");
if (isOffline) {
if (!isOnNoInternet) {
router.replace("/nointernet");
}
} else {
if (isOnNoInternet) {
router.replace(HOME);
}
}
} catch (error) {
console.log("Network check error", error);
}
};
checkNetwork();
interval = setInterval(checkNetwork, 10000);
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [pathname]);
return (
<ThemeProvider value={LIGHT_THEME}>
<View style={{ flex: 1 }}>
<Stack screenOptions={{ headerShown: false }} />
<PortalHost />
<GlobalLoadingOverlay />
<ChatwootFloatingButton />
</View>
</ThemeProvider>
);
}
export default function RootLayout() {
const [fontsLoaded] = useFonts({
"DMSans-Regular": require("../../assets/fonts/DMSans-Regular.ttf"),
"DMSans-Bold": require("../../assets/fonts/DMSans-Bold.ttf"),
"DMSans-Black": require("../../assets/fonts/DMSans-Black.ttf"),
"DMSans-Medium": require("../../assets/fonts/DMSans-Medium.ttf"),
"DMSans-SemiBold": require("../../assets/fonts/DMSans-SemiBold.ttf"),
"DMSans-Thin": require("../../assets/fonts/DMSans-Thin.ttf"),
"DMSans-ExtraBold": require("../../assets/fonts/DMSans-ExtraBold.ttf"),
"DMSans-ExtraLight": require("../../assets/fonts/DMSans-ExtraLight.ttf"),
"DMSans-Light": require("../../assets/fonts/DMSans-Light.ttf"),
});
useEffect(() => {
if (fontsLoaded) {
SplashScreen.hideAsync();
}
}, [fontsLoaded]);
if (!fontsLoaded) return null;
return <AppContent />;
}