104 lines
3.6 KiB
TypeScript
104 lines
3.6 KiB
TypeScript
import { DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
|
import { useFonts } from 'expo-font';
|
|
import { Stack } from 'expo-router';
|
|
import { StatusBar } from 'expo-status-bar';
|
|
import 'react-native-reanimated';
|
|
|
|
import PrivacyConsentModal from '@/components/PrivacyConsentModal';
|
|
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
|
import { clearAiCoachSessionCache } from '@/services/aiCoachSession';
|
|
import { store } from '@/store';
|
|
import { rehydrateUser, setPrivacyAgreed } from '@/store/userSlice';
|
|
import React from 'react';
|
|
import RNExitApp from 'react-native-exit-app';
|
|
import Toast from 'react-native-toast-message';
|
|
|
|
import { DialogProvider } from '@/components/ui/DialogProvider';
|
|
import { Provider } from 'react-redux';
|
|
|
|
function Bootstrapper({ children }: { children: React.ReactNode }) {
|
|
const dispatch = useAppDispatch();
|
|
const { privacyAgreed } = useAppSelector((state) => state.user);
|
|
const [showPrivacyModal, setShowPrivacyModal] = React.useState(false);
|
|
const [userDataLoaded, setUserDataLoaded] = React.useState(false);
|
|
|
|
React.useEffect(() => {
|
|
const loadUserData = async () => {
|
|
await dispatch(rehydrateUser());
|
|
setUserDataLoaded(true);
|
|
};
|
|
|
|
loadUserData();
|
|
// 冷启动时清空 AI 教练会话缓存
|
|
clearAiCoachSessionCache();
|
|
}, [dispatch]);
|
|
|
|
React.useEffect(() => {
|
|
// 当用户数据加载完成后,检查是否需要显示隐私同意弹窗
|
|
if (userDataLoaded && !privacyAgreed) {
|
|
setShowPrivacyModal(true);
|
|
}
|
|
}, [userDataLoaded, privacyAgreed]);
|
|
|
|
const handlePrivacyAgree = () => {
|
|
dispatch(setPrivacyAgreed());
|
|
setShowPrivacyModal(false);
|
|
};
|
|
|
|
const handlePrivacyDisagree = () => {
|
|
RNExitApp.exitApp();
|
|
};
|
|
|
|
return (
|
|
<DialogProvider>
|
|
{children}
|
|
<PrivacyConsentModal
|
|
visible={showPrivacyModal}
|
|
onAgree={handlePrivacyAgree}
|
|
onDisagree={handlePrivacyDisagree}
|
|
/>
|
|
<Toast />
|
|
</DialogProvider>
|
|
);
|
|
}
|
|
|
|
export default function RootLayout() {
|
|
const colorScheme = useColorScheme();
|
|
const [loaded] = useFonts({
|
|
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
|
|
});
|
|
|
|
if (!loaded) {
|
|
// Async font loading only occurs in development.
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<Provider store={store}>
|
|
<Bootstrapper>
|
|
<ThemeProvider value={DefaultTheme}>
|
|
<Stack screenOptions={{ headerShown: false }}>
|
|
<Stack.Screen name="onboarding" />
|
|
<Stack.Screen name="(tabs)" />
|
|
<Stack.Screen name="challenge" options={{ headerShown: false }} />
|
|
<Stack.Screen name="training-plan" options={{ headerShown: false }} />
|
|
<Stack.Screen name="workout" options={{ headerShown: false }} />
|
|
<Stack.Screen name="profile/edit" />
|
|
<Stack.Screen name="profile/goals" options={{ headerShown: false }} />
|
|
<Stack.Screen name="ai-coach-chat" options={{ headerShown: false }} />
|
|
<Stack.Screen name="ai-posture-assessment" />
|
|
<Stack.Screen name="auth/login" options={{ headerShown: false }} />
|
|
<Stack.Screen name="legal/user-agreement" options={{ headerShown: true, title: '用户协议' }} />
|
|
<Stack.Screen name="legal/privacy-policy" options={{ headerShown: true, title: '隐私政策' }} />
|
|
<Stack.Screen name="article/[id]" options={{ headerShown: false }} />
|
|
<Stack.Screen name="+not-found" />
|
|
</Stack>
|
|
<StatusBar style="dark" />
|
|
<Toast />
|
|
</ThemeProvider>
|
|
</Bootstrapper>
|
|
</Provider>
|
|
);
|
|
}
|