diff --git a/app/index.tsx b/app/index.tsx index c52b0ef..f17fe96 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -1,17 +1,20 @@ -import { useCallback, useContext, useEffect } from 'react'; +import { useContext, useEffect } from 'react'; -import AsyncStorage from '@react-native-async-storage/async-storage'; +import { SplashScreen } from 'expo-router'; import { StatusBar, View } from 'react-native'; import { ThemeContext } from 'react-native-ficus-ui'; -import theme, { THEME_KEY } from '@/theme'; +import useAuthStore from '@/modules/auth/auth.store'; +import theme from '@/theme'; + +// Prevent native splash screen from autohiding before App component declaration +SplashScreen.preventAutoHideAsync(); const Index = () => { const { setTheme } = useContext(ThemeContext); - - const loadTheme = useCallback(async () => { - const themeValue = await AsyncStorage.getItem(THEME_KEY); - if (themeValue === 'dark') { + useEffect(() => { + const appMode = useAuthStore.getState().appMode; + if (appMode === 'dark') { setTheme(theme.dark); StatusBar.setBarStyle('light-content'); } else { @@ -20,10 +23,6 @@ const Index = () => { } }, []); - useEffect(() => { - loadTheme(); - }, [loadTheme]); - return ; }; export default Index; diff --git a/src/modules/auth/auth.hook.ts b/src/modules/auth/auth.hook.ts index 8587561..555ed8d 100644 --- a/src/modules/auth/auth.hook.ts +++ b/src/modules/auth/auth.hook.ts @@ -1,6 +1,11 @@ -import { useLayoutEffect, useMemo } from 'react'; - -import { useRootNavigationState, useRouter, useSegments } from 'expo-router'; +import { useLayoutEffect, useMemo, useRef } from 'react'; + +import { + SplashScreen, + useRootNavigationState, + useRouter, + useSegments, +} from 'expo-router'; import { useShallow } from 'zustand/react/shallow'; import useAuthStore from '@/modules/auth/auth.store'; @@ -11,7 +16,7 @@ const useProtectedRoute = () => { const router = useRouter(); const isAuthentificated = useAuthStore(useShallow((state) => !!state.token)); const isHydrated = useAuthStore(useShallow((state) => state.isHydrated)); - + const currentRouteRef = useRef<'auth' | 'tabs' | null>(null); const navigationKey = useMemo(() => { return rootNavigationState?.key; }, [rootNavigationState]); @@ -22,11 +27,18 @@ const useProtectedRoute = () => { if (!navigationKey || !isHydrated) { return; } + SplashScreen.hideAsync(); - if (!isAuthentificated && !inAuthGroup) { + if ( + !isAuthentificated && + !inAuthGroup && + currentRouteRef.current !== 'auth' + ) { router.replace('/onboarding'); - } else if (isAuthentificated && inAuthGroup) { + currentRouteRef.current = 'auth'; + } else if (isAuthentificated && currentRouteRef.current !== 'tabs') { router.replace('/(tabs)/home'); + currentRouteRef.current = 'tabs'; } }, [isAuthentificated, segments, navigationKey, isHydrated]); }; diff --git a/src/modules/auth/auth.store.ts b/src/modules/auth/auth.store.ts index 1997e42..4ba387a 100644 --- a/src/modules/auth/auth.store.ts +++ b/src/modules/auth/auth.store.ts @@ -1,4 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; +import { Appearance } from 'react-native'; import { create } from 'zustand'; import { createJSONStorage, devtools, persist } from 'zustand/middleware'; @@ -8,16 +9,26 @@ import { AUTH_STORAGE_KEY } from '@/modules/auth/auth.constants'; type AuthState = { token: string | null; setToken: (newToken: string | null) => void; + appMode: 'light' | 'dark'; + setAppMode: (newAppMode: 'light' | 'dark') => void; logout: () => void; isHydrated: boolean; setIsHydrated: (isHydrated: boolean) => void; }; +// Apperance is a React Native API that allows you to determine if the user prefers a dark or light mode +const DEFAULT_SYSTEM_MODE = + Appearance.getColorScheme() === 'dark' ? 'dark' : 'light'; + const useAuthStore = create()( devtools( persist( (set) => ({ isHydrated: false, + appMode: DEFAULT_SYSTEM_MODE, + setAppMode: (newAppMode: 'light' | 'dark') => { + set({ appMode: newAppMode }); + }, token: null, setIsHydrated: (isHydrated: boolean) => { set({ isHydrated }); @@ -33,13 +44,14 @@ const useAuthStore = create()( { name: AUTH_STORAGE_KEY, storage: createJSONStorage(() => AsyncStorage), // Specifying the storage - partialize: (state) => ({ token: state.token }), // Persist only the token - onRehydrateStorage: () => (state, error) => { // Called when the storage is rehydrated + partialize: (state) => ({ token: state.token, appMode: state.appMode }), // Persist only the token and app mode + onRehydrateStorage: () => (state, error) => { + // Called when the storage is rehydrated if (error) { console.error(error); } state?.setIsHydrated?.(true); - } + }, } ) ) diff --git a/src/theme/useDarkMode.ts b/src/theme/useDarkMode.ts index ea66650..dccc7c1 100644 --- a/src/theme/useDarkMode.ts +++ b/src/theme/useDarkMode.ts @@ -1,13 +1,14 @@ import { useCallback } from 'react'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import { StatusBar } from 'react-native'; import { useTheme } from 'react-native-ficus-ui'; -import ficusThemes, { THEME_KEY } from '@/theme'; +import useAuthStore from '@/modules/auth/auth.store'; +import ficusThemes from '@/theme'; export const useDarkMode = () => { const { theme, setTheme } = useTheme(); + const setAppMode = useAuthStore((state) => state.setAppMode); const colorModeValue = useCallback( (lightValue: Value, darkValue: Value) => @@ -18,14 +19,14 @@ export const useDarkMode = () => { const setColorMode = async (colorMode: 'dark' | 'light') => { setTheme(ficusThemes[colorMode]); StatusBar.setBarStyle(`${colorMode}-content`); - await AsyncStorage.setItem(THEME_KEY, colorMode); + setAppMode(colorMode); }; - const toggleColorMode = async () => { + const toggleColorMode = () => { const newTheme = theme.name === 'dark' ? 'light' : 'dark'; StatusBar.setBarStyle(`${theme.name as 'dark' | 'light'}-content`); setTheme(ficusThemes[newTheme]); - await AsyncStorage.setItem(THEME_KEY, newTheme); + setAppMode(newTheme); }; const getThemeColor = (color: `${string}.${ColorShade}`) => {