feat(hrv): 添加心率变异性监控和压力评估功能
- 新增 HRV 监听服务,实时监控心率变异性数据 - 实现 HRV 到压力指数的转换算法和压力等级评估 - 添加智能通知服务,在压力偏高时推送健康建议 - 优化日志系统,修复日志丢失问题并增强刷新机制 - 改进个人页面下拉刷新,支持并行数据加载 - 优化勋章数据缓存策略,减少不必要的网络请求 - 重构应用初始化流程,优化权限服务和健康监听服务的启动顺序 - 移除冗余日志输出,提升应用性能
This commit is contained in:
@@ -22,7 +22,7 @@ import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Linking, Modal, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { Linking, Modal, RefreshControl, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import { AppLanguage, changeAppLanguage, getNormalizedLanguage } from '@/i18n';
|
||||
@@ -62,6 +62,7 @@ export default function PersonalScreen() {
|
||||
const isLgAvaliable = isLiquidGlassAvailable();
|
||||
const [languageModalVisible, setLanguageModalVisible] = useState(false);
|
||||
const [isSwitchingLanguage, setIsSwitchingLanguage] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const languageOptions = useMemo<LanguageOption[]>(() => ([
|
||||
{
|
||||
@@ -165,18 +166,38 @@ export default function PersonalScreen() {
|
||||
console.log('badgePreview', badgePreview);
|
||||
|
||||
|
||||
// 页面聚焦时获取最新用户信息
|
||||
// 首次加载时获取用户信息和数据
|
||||
useEffect(() => {
|
||||
dispatch(fetchMyProfile());
|
||||
dispatch(fetchActivityHistory());
|
||||
dispatch(fetchAvailableBadges());
|
||||
}, [dispatch]);
|
||||
|
||||
// 页面聚焦时智能刷新(依赖 Redux 的缓存策略)
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
dispatch(fetchMyProfile());
|
||||
dispatch(fetchActivityHistory());
|
||||
useCallback(() => {
|
||||
// 徽章数据由 Redux 的缓存策略控制,只有过期才会重新请求
|
||||
dispatch(fetchAvailableBadges());
|
||||
// 不再需要在这里加载推送偏好设置,因为已移到通知设置页面
|
||||
// 加载开发者模式状态
|
||||
loadDeveloperModeState();
|
||||
}, [dispatch])
|
||||
);
|
||||
|
||||
// 手动刷新处理
|
||||
const onRefresh = useCallback(async () => {
|
||||
setRefreshing(true);
|
||||
try {
|
||||
// 并行刷新所有数据
|
||||
await Promise.all([
|
||||
dispatch(fetchMyProfile()).unwrap(),
|
||||
dispatch(fetchActivityHistory()).unwrap(),
|
||||
dispatch(fetchAvailableBadges()).unwrap(),
|
||||
]);
|
||||
} catch (error) {
|
||||
log.warn('刷新数据失败', error);
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
// 移除 loadNotificationPreference 函数,因为已移到通知设置页面
|
||||
|
||||
// 加载开发者模式状态
|
||||
@@ -695,6 +716,15 @@ export default function PersonalScreen() {
|
||||
paddingHorizontal: 16,
|
||||
}}
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor="#9370DB"
|
||||
colors={['#9370DB']}
|
||||
progressViewOffset={insets.top}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<UserHeader />
|
||||
{userProfile.isVip ? <VipMembershipCard /> : <MembershipBanner />}
|
||||
|
||||
172
app/_layout.tsx
172
app/_layout.tsx
@@ -10,6 +10,7 @@ import PrivacyConsentModal from '@/components/PrivacyConsentModal';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useQuickActions } from '@/hooks/useQuickActions';
|
||||
import { clearAiCoachSessionCache } from '@/services/aiCoachSession';
|
||||
import { hrvMonitorService } from '@/services/hrvMonitor';
|
||||
import { notificationService } from '@/services/notifications';
|
||||
import { setupQuickActions } from '@/services/quickActions';
|
||||
import { sleepMonitorService } from '@/services/sleepMonitor';
|
||||
@@ -21,7 +22,7 @@ import { hydrateActiveSchedule, selectActiveFastingSchedule } from '@/store/fast
|
||||
import { fetchMyProfile, setPrivacyAgreed } from '@/store/userSlice';
|
||||
import { createWaterRecordAction } from '@/store/waterSlice';
|
||||
import { loadActiveFastingSchedule } from '@/utils/fasting';
|
||||
import { ensureHealthPermissions, initializeHealthPermissions } from '@/utils/health';
|
||||
import { initializeHealthPermissions } from '@/utils/health';
|
||||
import { MoodNotificationHelpers, NutritionNotificationHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers';
|
||||
import { clearPendingWaterRecords, syncPendingWidgetChanges } from '@/utils/widgetDataSync';
|
||||
import React, { useEffect } from 'react';
|
||||
@@ -130,10 +131,10 @@ function Bootstrapper({ children }: { children: React.ReactNode }) {
|
||||
initializeBasicServices();
|
||||
}, [dispatch]);
|
||||
|
||||
// ==================== 权限相关服务初始化(仅在 onboarding 完成后执行)====================
|
||||
// ==================== 权限相关服务初始化(应用启动时执行)====================
|
||||
React.useEffect(() => {
|
||||
// 如果还没完成 onboarding,或已经初始化过权限,则跳过
|
||||
if (!onboardingCompleted || permissionInitializedRef.current) {
|
||||
// 如果已经初始化过,则跳过(确保只初始化一次)
|
||||
if (permissionInitializedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -141,85 +142,6 @@ function Bootstrapper({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const delay = (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms));
|
||||
|
||||
const initializePermissionServices = async () => {
|
||||
try {
|
||||
logger.info('🔐 开始初始化需要权限的服务(onboarding 已完成)...');
|
||||
|
||||
// 1. 初始化通知服务(包含权限请求)
|
||||
await notificationService.initialize();
|
||||
logger.info('✅ 通知服务初始化完成');
|
||||
|
||||
// 2. 延迟请求 HealthKit 权限(避免立即弹窗打断用户)
|
||||
await delay(2000);
|
||||
try {
|
||||
const granted = await ensureHealthPermissions();
|
||||
if (granted) {
|
||||
logger.info('✅ HealthKit 权限请求完成');
|
||||
} else {
|
||||
logger.warn('⚠️ 用户未授予 HealthKit 权限,相关功能将受限');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('⚠️ HealthKit 权限请求失败,可能在模拟器上运行:', error);
|
||||
}
|
||||
|
||||
// 3. 异步同步 Widget 数据
|
||||
syncWidgetDataInBackground();
|
||||
|
||||
logger.info('🎉 权限相关服务初始化完成');
|
||||
} catch (error) {
|
||||
logger.error('❌ 权限相关服务初始化失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 后台服务初始化(延迟执行)====================
|
||||
const initializeBackgroundServices = () => {
|
||||
const { InteractionManager } = require('react-native');
|
||||
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
logger.info('📅 开始初始化后台服务...');
|
||||
|
||||
// 1. 批量注册所有通知提醒
|
||||
await registerAllNotifications();
|
||||
|
||||
// 2. 初始化后台任务管理器
|
||||
await initializeBackgroundTaskManager();
|
||||
|
||||
// 3. 初始化健康监听服务
|
||||
await initializeHealthMonitoring();
|
||||
|
||||
logger.info('🎉 后台服务初始化完成');
|
||||
} catch (error) {
|
||||
logger.error('❌ 后台服务初始化失败:', error);
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
|
||||
// ==================== 空闲服务初始化====================
|
||||
const initializeIdleServices = () => {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
logger.info('🔄 开始初始化空闲服务...');
|
||||
|
||||
// 1. 后台任务详细状态检查
|
||||
await checkBackgroundTaskStatus();
|
||||
|
||||
// 2. 开发环境调试工具
|
||||
if (__DEV__ && BackgroundTaskDebugger) {
|
||||
BackgroundTaskDebugger.getInstance().initialize();
|
||||
logger.info('✅ 后台任务调试工具已初始化(开发环境)');
|
||||
}
|
||||
|
||||
logger.info('🎉 空闲服务初始化完成');
|
||||
} catch (error) {
|
||||
logger.error('❌ 空闲服务初始化失败:', error);
|
||||
}
|
||||
}, 8000);
|
||||
};
|
||||
|
||||
// ==================== 辅助函数 ====================
|
||||
|
||||
// 异步同步 Widget 数据(不阻塞主流程)
|
||||
@@ -320,9 +242,10 @@ function Bootstrapper({ children }: { children: React.ReactNode }) {
|
||||
try {
|
||||
logger.info('💪 初始化健康监听服务...');
|
||||
|
||||
const [workoutResult, sleepResult] = await Promise.allSettled([
|
||||
const [workoutResult, sleepResult, hrvResult] = await Promise.allSettled([
|
||||
workoutMonitorService.initialize(),
|
||||
sleepMonitorService.initialize(),
|
||||
hrvMonitorService.initialize(),
|
||||
]);
|
||||
|
||||
const workoutReady = workoutResult.status === 'fulfilled';
|
||||
@@ -339,13 +262,20 @@ function Bootstrapper({ children }: { children: React.ReactNode }) {
|
||||
logger.error('❌ 睡眠监听服务初始化失败:', sleepResult.reason);
|
||||
}
|
||||
|
||||
if (workoutReady && sleepReady) {
|
||||
const hrvReady = hrvResult.status === 'fulfilled';
|
||||
if (hrvReady) {
|
||||
logger.info('✅ HRV 监听服务初始化成功');
|
||||
} else {
|
||||
logger.error('❌ HRV 监听服务初始化失败:', hrvResult.reason);
|
||||
}
|
||||
|
||||
if (workoutReady && sleepReady && hrvReady) {
|
||||
logger.info('🎉 健康监听服务初始化完成');
|
||||
} else {
|
||||
logger.warn('⚠️ 健康监听服务部分未能初始化成功,请检查上述错误日志');
|
||||
}
|
||||
|
||||
return workoutReady && sleepReady;
|
||||
return workoutReady && sleepReady && hrvReady;
|
||||
} catch (error) {
|
||||
logger.error('❌ 健康监听服务初始化失败:', error);
|
||||
return false;
|
||||
@@ -381,6 +311,74 @@ function Bootstrapper({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
};
|
||||
|
||||
// 权限服务初始化
|
||||
const initializePermissionServices = async () => {
|
||||
try {
|
||||
logger.info('🔐 开始初始化需要权限的服务...');
|
||||
|
||||
// 1. 初始化通知服务(包含权限请求)
|
||||
await notificationService.initialize();
|
||||
logger.info('✅ 通知服务初始化完成');
|
||||
|
||||
// 2. 异步同步 Widget 数据(不阻塞主流程)
|
||||
syncWidgetDataInBackground();
|
||||
|
||||
logger.info('🎉 权限相关服务初始化完成');
|
||||
logger.info('💡 HealthKit 权限将在用户首次访问健康数据时请求');
|
||||
} catch (error) {
|
||||
logger.error('❌ 权限相关服务初始化失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 后台服务初始化(延迟执行)====================
|
||||
const initializeBackgroundServices = () => {
|
||||
const { InteractionManager } = require('react-native');
|
||||
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
logger.info('📅 开始初始化后台服务...');
|
||||
|
||||
// 1. 批量注册所有通知提醒
|
||||
await registerAllNotifications();
|
||||
|
||||
// 2. 初始化后台任务管理器
|
||||
await initializeBackgroundTaskManager();
|
||||
|
||||
// 3. 初始化健康监听服务
|
||||
await initializeHealthMonitoring();
|
||||
|
||||
logger.info('🎉 后台服务初始化完成');
|
||||
} catch (error) {
|
||||
logger.error('❌ 后台服务初始化失败:', error);
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
|
||||
// ==================== 空闲服务初始化====================
|
||||
const initializeIdleServices = () => {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
logger.info('🔄 开始初始化空闲服务...');
|
||||
|
||||
// 1. 后台任务详细状态检查
|
||||
await checkBackgroundTaskStatus();
|
||||
|
||||
// 2. 开发环境调试工具
|
||||
if (__DEV__ && BackgroundTaskDebugger) {
|
||||
BackgroundTaskDebugger.getInstance().initialize();
|
||||
logger.info('✅ 后台任务调试工具已初始化(开发环境)');
|
||||
}
|
||||
|
||||
logger.info('🎉 空闲服务初始化完成');
|
||||
} catch (error) {
|
||||
logger.error('❌ 空闲服务初始化失败:', error);
|
||||
}
|
||||
}, 8000);
|
||||
};
|
||||
|
||||
const runInitializationSequence = async () => {
|
||||
try {
|
||||
await initializePermissionServices();
|
||||
@@ -397,7 +395,7 @@ function Bootstrapper({ children }: { children: React.ReactNode }) {
|
||||
|
||||
runInitializationSequence();
|
||||
|
||||
}, [onboardingCompleted, profile.name]);
|
||||
}, []); // 每次应用启动都执行,不依赖其他状态
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user