161 lines
5.8 KiB
TypeScript
161 lines
5.8 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 { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||
import 'react-native-reanimated';
|
||
|
||
import PrivacyConsentModal from '@/components/PrivacyConsentModal';
|
||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||
import { useQuickActions } from '@/hooks/useQuickActions';
|
||
import { clearAiCoachSessionCache } from '@/services/aiCoachSession';
|
||
import { backgroundTaskManager } from '@/services/backgroundTaskManager';
|
||
import { notificationService } from '@/services/notifications';
|
||
import { setupQuickActions } from '@/services/quickActions';
|
||
import { store } from '@/store';
|
||
import { rehydrateUser, setPrivacyAgreed } from '@/store/userSlice';
|
||
import { MoodNotificationHelpers, NutritionNotificationHelpers } from '@/utils/notificationHelpers';
|
||
import React from 'react';
|
||
import RNExitApp from 'react-native-exit-app';
|
||
|
||
import { DialogProvider } from '@/components/ui/DialogProvider';
|
||
import { ToastProvider } from '@/contexts/ToastContext';
|
||
import { Provider } from 'react-redux';
|
||
|
||
function Bootstrapper({ children }: { children: React.ReactNode }) {
|
||
const dispatch = useAppDispatch();
|
||
const { privacyAgreed, profile } = useAppSelector((state) => state.user);
|
||
const [showPrivacyModal, setShowPrivacyModal] = React.useState(false);
|
||
const [userDataLoaded, setUserDataLoaded] = React.useState(false);
|
||
|
||
// 初始化快捷动作处理
|
||
useQuickActions();
|
||
|
||
React.useEffect(() => {
|
||
const loadUserData = async () => {
|
||
await dispatch(rehydrateUser());
|
||
setUserDataLoaded(true);
|
||
};
|
||
const initializeNotifications = async () => {
|
||
try {
|
||
// 初始化通知服务
|
||
await notificationService.initialize();
|
||
console.log('通知服务初始化成功');
|
||
|
||
// 初始化后台任务管理器
|
||
await backgroundTaskManager.initialize();
|
||
console.log('后台任务管理器初始化成功');
|
||
|
||
// 初始化快捷动作
|
||
await setupQuickActions();
|
||
console.log('快捷动作初始化成功');
|
||
} catch (error) {
|
||
console.error('通知服务、后台任务管理器或快捷动作初始化失败:', error);
|
||
}
|
||
};
|
||
|
||
loadUserData();
|
||
initializeNotifications();
|
||
// 冷启动时清空 AI 教练会话缓存
|
||
clearAiCoachSessionCache();
|
||
}, [dispatch]);
|
||
|
||
React.useEffect(() => {
|
||
// 当用户数据加载完成后,检查是否需要显示隐私同意弹窗
|
||
if (userDataLoaded && !privacyAgreed) {
|
||
setShowPrivacyModal(true);
|
||
}
|
||
}, [userDataLoaded, privacyAgreed]);
|
||
|
||
// 当用户数据加载完成且用户名存在时,注册所有提醒
|
||
React.useEffect(() => {
|
||
const registerAllReminders = async () => {
|
||
try {
|
||
await notificationService.initialize();
|
||
// 后台任务
|
||
await backgroundTaskManager.initialize()
|
||
// 注册午餐提醒(12:00)
|
||
await NutritionNotificationHelpers.scheduleDailyLunchReminder(profile.name || '');
|
||
console.log('午餐提醒已注册');
|
||
|
||
// 注册晚餐提醒(18:00)
|
||
await NutritionNotificationHelpers.scheduleDailyDinnerReminder(profile.name || '');
|
||
console.log('晚餐提醒已注册');
|
||
|
||
// 注册心情提醒(21:00)
|
||
await MoodNotificationHelpers.scheduleDailyMoodReminder(profile.name || '');
|
||
console.log('心情提醒已注册');
|
||
|
||
|
||
console.log('喝水提醒后台任务已注册');
|
||
} catch (error) {
|
||
console.error('注册提醒失败:', error);
|
||
}
|
||
};
|
||
|
||
registerAllReminders();
|
||
}, [userDataLoaded, profile?.name]);
|
||
|
||
const handlePrivacyAgree = () => {
|
||
dispatch(setPrivacyAgreed());
|
||
setShowPrivacyModal(false);
|
||
};
|
||
|
||
const handlePrivacyDisagree = () => {
|
||
RNExitApp.exitApp();
|
||
};
|
||
|
||
return (
|
||
<DialogProvider>
|
||
{children}
|
||
<PrivacyConsentModal
|
||
visible={showPrivacyModal}
|
||
onAgree={handlePrivacyAgree}
|
||
onDisagree={handlePrivacyDisagree}
|
||
/>
|
||
</DialogProvider>
|
||
);
|
||
}
|
||
|
||
export default function RootLayout() {
|
||
const [loaded] = useFonts({
|
||
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
|
||
});
|
||
|
||
if (!loaded) {
|
||
// Async font loading only occurs in development.
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||
<Provider store={store}>
|
||
<Bootstrapper>
|
||
<ToastProvider>
|
||
<ThemeProvider value={DefaultTheme}>
|
||
<Stack screenOptions={{ headerShown: false }}>
|
||
<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="goals-list" 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="water-settings" options={{ headerShown: false }} />
|
||
<Stack.Screen name="+not-found" />
|
||
</Stack>
|
||
<StatusBar style="dark" />
|
||
</ThemeProvider>
|
||
</ToastProvider>
|
||
</Bootstrapper>
|
||
</Provider>
|
||
</GestureHandlerRootView>
|
||
);
|
||
}
|