// App.tsx import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { toastConfig } from 'components/CustomAlertModal'; import { navigationRef } from 'components/NavigationRef'; import i18n from 'i18n/i18n'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { I18nextProvider } from 'react-i18next'; import { Animated, Dimensions, LogBox, StatusBar, View } from 'react-native'; import Toast from 'react-native-toast-message'; import SplashScreen from './src/components/SplashScreen'; // Screens import { authEvents, getLanguage, getToken, loadInitialAuthData, storage, } from 'helpers/event'; import Login from 'screens/auth/login/ui'; import Confirm from 'screens/auth/login/ui/Confirm'; import Register from 'screens/auth/registeration/ui'; import SecondStep from 'screens/auth/registeration/ui/SecondStep'; import TermsAndConditions from 'screens/auth/registeration/ui/TermsAndConditions'; import SelectAuth from 'screens/auth/select-auth/SelectAuth'; import Branches from 'screens/home/branches/ui/Branches'; import ListBranches from 'screens/home/branches/ui/ListBranches'; import CargoPrices from 'screens/home/cargoPrices/ui/CargoPrices'; import Home from 'screens/home/home/ui/Home'; import RestrictedProduct from 'screens/home/restrictedProduct/ui/RestrictedProduct'; import CreatePassword from 'screens/passport/createPassport/ui/CreatePassword'; import Passport from 'screens/passport/myPassport/ui/Passport'; import Profile from 'screens/profile/myProfile/ui/Profile'; import AddedLock from 'screens/profile/settings/ui/AddedLock'; import Settings from 'screens/profile/settings/ui/Settings'; import SettingsLock from 'screens/profile/settings/ui/SettingsLock'; import Support from 'screens/profile/support/ui/Support'; import Warehouses from 'screens/profile/warehouses/ui/Warehouses'; import Status from 'screens/status/ui/Status'; import EnterCard from 'screens/wallet/enterCard/ui/EnterCard'; import Wallet from 'screens/wallet/payment/ui/Wallet'; import PaymentMethod from 'screens/wallet/paymentMethod/ui/PaymentMethod'; import PaymentQrCode from 'screens/wallet/successPayment/ui/PaymentQrCode'; import Onboarding from 'screens/welcome/Onboarding'; LogBox.ignoreLogs([ 'ViewPropTypes will be removed', // NOTE: I recommend NOT ignoring "Non-serializable values were found in the navigation state" // because it hides bugs that can cause crashes. Keep only if you understand the implications. ]); const Stack = createNativeStackNavigator(); const screenWidth = Dimensions.get('window').width; const queryClient = new QueryClient(); export default function App() { const [initialRoute, setInitialRoute] = useState(null); const slideAnim = useRef(new Animated.Value(0)).current; const [isSplashVisible, setIsSplashVisible] = useState(true); const isMounted = useRef(false); const currentAnimation = useRef(null); const token = getToken(); useEffect(() => { loadInitialAuthData(); }, []); useEffect(() => { const logoutListener = () => { if (navigationRef.isReady()) { navigationRef.reset({ index: 0, routes: [{ name: 'Login' }], }); } }; authEvents.on('logout', logoutListener); return () => { authEvents.removeListener('logout', logoutListener); }; }, []); useEffect(() => { isMounted.current = true; return () => { isMounted.current = false; if (currentAnimation.current) { currentAnimation.current.stop(); } }; }, []); useEffect(() => { const initializeApp = async () => { try { const seen = storage.getString('hasSeenOnboarding'); const token = getToken(); const lang = getLanguage(); if (lang) { try { await i18n.changeLanguage(lang); } catch (e) { console.warn('i18n.changeLanguage failed:', e); } } const initialRouteName = !seen ? 'Onboarding' : token ? 'Home' : 'select-auth'; if (isMounted.current) { setInitialRoute(initialRouteName); } } catch (error) { console.error('App initialization error:', error); if (isMounted.current) setInitialRoute('select-auth'); } }; initializeApp(); }, []); const handleSplashFinish = useMemo( () => () => { // create animation and keep ref so we can stop it on unmount const animation = Animated.timing(slideAnim, { toValue: -screenWidth, duration: 600, useNativeDriver: true, }); currentAnimation.current = animation; animation.start(() => { // ensure component still mounted before setting state if (isMounted.current) { setIsSplashVisible(false); } currentAnimation.current = null; }); }, [slideAnim], ); const handleOnboardingFinish = useMemo( () => async (navigation: any) => { try { storage.set('hasSeenOnboarding', 'true'); } catch (e) { console.warn('Failed to set hasSeenOnboarding', e); } navigation.replace('select-auth'); }, [], ); if (!initialRoute || isSplashVisible || !currentAnimation) { return ( ); } return ( {props => ( handleOnboardingFinish(props.navigation)} /> )} ); }