Files
digital-pilates/app/_layout.tsx
2025-09-09 14:26:16 +08:00

194 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { initializeWaterRecordBridge } from '@/services/waterRecordBridge';
import { WaterRecordSource } from '@/services/waterRecords';
import { store } from '@/store';
import { rehydrateUser, setPrivacyAgreed } from '@/store/userSlice';
import { createWaterRecordAction } from '@/store/waterSlice';
import { MoodNotificationHelpers, NutritionNotificationHelpers } from '@/utils/notificationHelpers';
import { clearPendingWaterRecords, syncPendingWidgetChanges } from '@/utils/widgetDataSync';
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('快捷动作初始化成功');
// 初始化喝水记录 bridge
initializeWaterRecordBridge();
console.log('喝水记录 Bridge 初始化成功');
// 检查并同步Widget数据更改
const widgetSync = await syncPendingWidgetChanges();
if (widgetSync.hasPendingChanges && widgetSync.pendingRecords) {
console.log(`检测到 ${widgetSync.pendingRecords.length} 条待同步的水记录`);
// 将待同步的记录添加到 Redux store
for (const record of widgetSync.pendingRecords) {
try {
await store.dispatch(createWaterRecordAction({
amount: record.amount,
recordedAt: record.recordedAt,
source: WaterRecordSource.Auto, // 标记为自动添加来自Widget
})).unwrap();
console.log(`成功同步水记录: ${record.amount}ml at ${record.recordedAt}`);
} catch (error) {
console.error('同步水记录失败:', error);
}
}
// 清除已同步的记录
await clearPendingWaterRecords();
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>
);
}