feat(healthkit): 实现HealthKit与服务端的双向数据同步,包括身高、体重和出生日期的获取与保存
This commit is contained in:
@@ -15,11 +15,14 @@ import { Colors } from '@/constants/Colors';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { BackgroundTaskManager } from '@/services/backgroundTaskManagerV2';
|
||||
import { syncHealthKitToServer } from '@/services/healthKitSync';
|
||||
import { setHealthData } from '@/store/healthSlice';
|
||||
import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/moodSlice';
|
||||
import { updateUserProfile } from '@/store/userSlice';
|
||||
import { fetchTodayWaterStats } from '@/store/waterSlice';
|
||||
import { getMonthDaysZh, getTodayIndexInMonth } from '@/utils/date';
|
||||
import { fetchHealthDataForDate, testHRVDataFetch } from '@/utils/health';
|
||||
import { logger } from '@/utils/logger';
|
||||
import dayjs from 'dayjs';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { debounce } from 'lodash';
|
||||
@@ -58,6 +61,7 @@ const FloatingCard = ({ children, style }: {
|
||||
export default function ExploreScreen() {
|
||||
const { t } = useTranslation();
|
||||
const stepGoal = useAppSelector((s) => s.user.profile?.dailyStepsGoal) ?? 2000;
|
||||
const userProfile = useAppSelector((s) => s.user.profile);
|
||||
|
||||
const { pushIfAuthedElseLogin, isLoggedIn } = useAuthGuard();
|
||||
|
||||
@@ -273,8 +277,44 @@ export default function ExploreScreen() {
|
||||
}
|
||||
}, [executeLoadAllData, debouncedLoadAllData]);
|
||||
|
||||
// 同步 HealthKit 数据到服务端(带智能 diff 比较)
|
||||
const syncHealthDataToServer = React.useCallback(async () => {
|
||||
if (!isLoggedIn || !userProfile) {
|
||||
logger.info('用户未登录,跳过 HealthKit 数据同步');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info('开始同步 HealthKit 个人健康数据到服务端...');
|
||||
|
||||
// 传入当前用户资料,用于 diff 比较
|
||||
const success = await syncHealthKitToServer(
|
||||
async (data) => {
|
||||
await dispatch(updateUserProfile(data) as any);
|
||||
},
|
||||
userProfile // 传入当前用户资料进行比较
|
||||
);
|
||||
|
||||
if (success) {
|
||||
logger.info('HealthKit 数据同步到服务端成功');
|
||||
} else {
|
||||
logger.info('HealthKit 数据同步到服务端跳过(无变化)或失败');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('同步 HealthKit 数据到服务端失败:', error);
|
||||
}
|
||||
}, [isLoggedIn, dispatch, userProfile]);
|
||||
|
||||
// 初始加载时执行数据加载和同步
|
||||
useEffect(() => {
|
||||
loadAllData(currentSelectedDate);
|
||||
|
||||
// 延迟1秒后执行同步,避免影响初始加载性能
|
||||
const syncTimer = setTimeout(() => {
|
||||
syncHealthDataToServer();
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(syncTimer);
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import PrivacyConsentModal from '@/components/PrivacyConsentModal';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useQuickActions } from '@/hooks/useQuickActions';
|
||||
import { hrvMonitorService } from '@/services/hrvMonitor';
|
||||
import { notificationService } from '@/services/notifications';
|
||||
import { clearBadgeCount, notificationService } from '@/services/notifications';
|
||||
import { setupQuickActions } from '@/services/quickActions';
|
||||
import { sleepMonitorService } from '@/services/sleepMonitor';
|
||||
import { initializeWaterRecordBridge } from '@/services/waterRecordBridge';
|
||||
@@ -25,6 +25,7 @@ import { initializeHealthPermissions } from '@/utils/health';
|
||||
import { MoodNotificationHelpers, NutritionNotificationHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers';
|
||||
import { clearPendingWaterRecords, syncPendingWidgetChanges } from '@/utils/widgetDataSync';
|
||||
import React, { useEffect } from 'react';
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
|
||||
import { DialogProvider } from '@/components/ui/DialogProvider';
|
||||
import { MembershipModalProvider } from '@/contexts/MembershipModalContext';
|
||||
@@ -149,6 +150,20 @@ function Bootstrapper({ children }: { children: React.ReactNode }) {
|
||||
initializeBasicServices();
|
||||
}, [dispatch]);
|
||||
|
||||
// ==================== 应用状态监听 - 进入前台时清除角标 ====================
|
||||
React.useEffect(() => {
|
||||
const subscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => {
|
||||
if (nextAppState === 'active') {
|
||||
// 应用进入前台时清除角标
|
||||
clearBadgeCount();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ==================== 权限相关服务初始化(应用启动时执行)====================
|
||||
React.useEffect(() => {
|
||||
// 如果已经初始化过,则跳过(确保只初始化一次)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useCosUpload } from '@/hooks/useCosUpload';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||
import { syncServerToHealthKit } from '@/services/healthKitSync';
|
||||
import { fetchMyProfile, updateUserProfile } from '@/store/userSlice';
|
||||
import { fetchMaximumHeartRate } from '@/utils/health';
|
||||
import AsyncStorage from '@/utils/kvStore';
|
||||
@@ -212,6 +213,18 @@ export default function EditProfileScreen() {
|
||||
}));
|
||||
// 拉取最新用户信息,刷新全局状态
|
||||
await dispatch(fetchMyProfile() as any);
|
||||
|
||||
// 同步身高、体重到 HealthKit
|
||||
console.log('开始同步个人健康数据到 HealthKit...');
|
||||
const syncSuccess = await syncServerToHealthKit({
|
||||
height: next.height,
|
||||
weight: next.weight,
|
||||
birthDate: next.birthDate ? new Date(next.birthDate).getTime() / 1000 : undefined
|
||||
});
|
||||
|
||||
if (syncSuccess) {
|
||||
console.log('个人健康数据已同步到 HealthKit');
|
||||
}
|
||||
} catch (e: any) {
|
||||
// 接口失败不阻断本地保存
|
||||
console.warn('更新用户信息失败', e?.message || e);
|
||||
|
||||
Reference in New Issue
Block a user