- 新增饮水详情页面 `/water/detail` 展示每日饮水记录与统计 - 新增饮水设置页面 `/water/settings` 支持目标与快速添加配置 - 新增喝水提醒设置页面 `/water/reminder-settings` 支持自定义时间段与间隔 - 重构 `useWaterData` Hook,支持按日期查询与实时刷新 - 新增 `WaterNotificationHelpers.scheduleCustomWaterReminders` 实现个性化提醒 - 优化心情编辑页键盘体验,新增 `KeyboardAvoidingView` 与滚动逻辑 - 升级版本号至 1.0.14 并补充路由常量 - 补充用户偏好存储字段 `waterReminderEnabled/startTime/endTime/interval` - 废弃后台定时任务中的旧版喝水提醒逻辑,改为用户手动管理
208 lines
7.8 KiB
TypeScript
208 lines
7.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 { notificationService } from '@/services/notifications';
|
||
import { setupQuickActions } from '@/services/quickActions';
|
||
import { initializeWaterRecordBridge } from '@/services/waterRecordBridge';
|
||
import { WaterRecordSource } from '@/services/waterRecords';
|
||
import { store } from '@/store';
|
||
import { fetchMyProfile, setPrivacyAgreed } from '@/store/userSlice';
|
||
import { createWaterRecordAction } from '@/store/waterSlice';
|
||
import { ensureHealthPermissions, initializeHealthPermissions } from '@/utils/health';
|
||
import { DailySummaryNotificationHelpers, MoodNotificationHelpers, NutritionNotificationHelpers } from '@/utils/notificationHelpers';
|
||
import { clearPendingWaterRecords, syncPendingWidgetChanges } from '@/utils/widgetDataSync';
|
||
import React from 'react';
|
||
|
||
import { DialogProvider } from '@/components/ui/DialogProvider';
|
||
import { ToastProvider } from '@/contexts/ToastContext';
|
||
import { STORAGE_KEYS } from '@/services/api';
|
||
import { BackgroundTaskManager } from '@/services/backgroundTaskManager';
|
||
import AsyncStorage from '@/utils/kvStore';
|
||
import { Provider } from 'react-redux';
|
||
|
||
|
||
function Bootstrapper({ children }: { children: React.ReactNode }) {
|
||
const dispatch = useAppDispatch();
|
||
const { profile } = useAppSelector((state) => state.user);
|
||
const [showPrivacyModal, setShowPrivacyModal] = React.useState(false);
|
||
|
||
// 初始化快捷动作处理
|
||
useQuickActions();
|
||
|
||
React.useEffect(() => {
|
||
const loadUserData = async () => {
|
||
// 数据已经在启动界面预加载,这里只需要快速同步到 Redux 状态
|
||
await dispatch(fetchMyProfile());
|
||
};
|
||
|
||
const initHealthPermissions = async () => {
|
||
// 初始化 HealthKit 权限管理系统
|
||
try {
|
||
console.log('初始化 HealthKit 权限管理系统...');
|
||
initializeHealthPermissions();
|
||
|
||
// 延迟请求权限,避免应用启动时弹窗
|
||
setTimeout(async () => {
|
||
try {
|
||
await ensureHealthPermissions();
|
||
console.log('HealthKit 权限请求完成');
|
||
} catch (error) {
|
||
console.warn('HealthKit 权限请求失败,可能在模拟器上运行:', error);
|
||
}
|
||
}, 2000);
|
||
|
||
console.log('HealthKit 权限管理初始化完成');
|
||
} catch (error) {
|
||
console.warn('HealthKit 权限管理初始化失败:', error);
|
||
}
|
||
}
|
||
|
||
const initializeNotifications = async () => {
|
||
try {
|
||
await BackgroundTaskManager.getInstance().initialize();
|
||
// 初始化通知服务
|
||
await notificationService.initialize();
|
||
console.log('通知服务初始化成功');
|
||
|
||
// 注册午餐提醒(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('心情提醒已注册');
|
||
|
||
await DailySummaryNotificationHelpers.scheduleDailySummaryNotification(profile.name || '')
|
||
|
||
|
||
// 初始化快捷动作
|
||
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();
|
||
initHealthPermissions();
|
||
initializeNotifications();
|
||
// 冷启动时清空 AI 教练会话缓存
|
||
clearAiCoachSessionCache();
|
||
|
||
}, [dispatch]);
|
||
|
||
React.useEffect(() => {
|
||
|
||
const getPrivacyAgreed = async () => {
|
||
const str = await AsyncStorage.getItem(STORAGE_KEYS.privacyAgreed)
|
||
|
||
setShowPrivacyModal(str !== 'true');
|
||
}
|
||
getPrivacyAgreed();
|
||
}, []);
|
||
|
||
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-detail" 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>
|
||
);
|
||
}
|