diff --git a/app.json b/app.json
index 231e1f9..504ef7d 100644
--- a/app.json
+++ b/app.json
@@ -17,8 +17,11 @@
"NSCameraUsageDescription": "应用需要使用相机以拍摄您的体态照片用于AI测评。",
"NSPhotoLibraryUsageDescription": "应用需要访问相册以选择您的体态照片用于AI测评。",
"NSPhotoLibraryAddUsageDescription": "应用需要写入相册以保存拍摄的体态照片(可选)。",
+ "NSUserNotificationsUsageDescription": "应用需要发送通知以提醒您喝水和站立活动。",
"UIBackgroundModes": [
- "processing"
+ "processing",
+ "fetch",
+ "remote-notification"
]
}
},
@@ -55,7 +58,7 @@
"react-native-health",
{
"enableHealthAPI": true,
- "healthSharePermission": "应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。",
+ "healthSharePermission": "应用需要访问您的健康数据(步数、能量消耗、心率变异性等)以展示运动统计和压力分析。",
"healthUpdatePermission": "应用需要更新您的健康数据(体重信息)以记录您的健身进度。"
}
],
diff --git a/app/(tabs)/statistics.tsx b/app/(tabs)/statistics.tsx
index 8ece39b..7450afb 100644
--- a/app/(tabs)/statistics.tsx
+++ b/app/(tabs)/statistics.tsx
@@ -17,7 +17,7 @@ import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/mo
import { fetchDailyNutritionData, selectNutritionSummaryByDate } from '@/store/nutritionSlice';
import { fetchTodayWaterStats, selectTodayStats } from '@/store/waterSlice';
import { getMonthDaysZh, getTodayIndexInMonth } from '@/utils/date';
-import { ensureHealthPermissions, fetchHealthDataForDate } from '@/utils/health';
+import { ensureHealthPermissions, fetchHealthDataForDate, testHRVDataFetch } from '@/utils/health';
import { getTestHealthData } from '@/utils/mockHealthData';
import { calculateNutritionGoals } from '@/utils/nutrition';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
@@ -460,15 +460,27 @@ export default function ExploreScreen() {
{/* 开发环境调试按钮 */}
{__DEV__ && (
- {
- console.log('🔧 手动触发后台任务测试...');
- // await backgroundTaskManager.triggerTaskForTesting();
- }}
- >
- 🔧
-
+
+ {
+ console.log('🔧 手动触发后台任务测试...');
+ // await backgroundTaskManager.triggerTaskForTesting();
+ }}
+ >
+ 🔧
+
+
+ {
+ console.log('🫀 测试HRV数据获取...');
+ await testHRVDataFetch();
+ }}
+ >
+ 🫀
+
+
)}
@@ -665,6 +677,10 @@ const styles = StyleSheet.create({
fontWeight: '500',
color: '#192126',
},
+ debugButtonsContainer: {
+ flexDirection: 'row',
+ gap: 8,
+ },
debugButton: {
width: 32,
height: 32,
@@ -681,6 +697,9 @@ const styles = StyleSheet.create({
shadowRadius: 4,
elevation: 3,
},
+ hrvTestButton: {
+ backgroundColor: '#8B5CF6',
+ },
debugButtonText: {
fontSize: 12,
},
diff --git a/components/StressMeter.tsx b/components/StressMeter.tsx
index 5ee464a..e7c5b69 100644
--- a/components/StressMeter.tsx
+++ b/components/StressMeter.tsx
@@ -82,7 +82,7 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
{/* 数值显示区域 */}
{stressIndex === null ? '--' : stressIndex}
- ms
+ %
{/* 进度条区域 */}
diff --git a/ios/WaterWidgetExtension.entitlements b/ios/WaterWidgetExtension.entitlements
new file mode 100644
index 0000000..0c67376
--- /dev/null
+++ b/ios/WaterWidgetExtension.entitlements
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/ios/digitalpilates/Info.plist b/ios/digitalpilates/Info.plist
index a7c4107..8f1c6a9 100644
--- a/ios/digitalpilates/Info.plist
+++ b/ios/digitalpilates/Info.plist
@@ -4,7 +4,7 @@
BGTaskSchedulerPermittedIdentifiers
- com.anonymous.digitalpilates.backgroundfetch
+ background-health-reminders
CADisableMinimumFrameDurationOnPhone
@@ -23,7 +23,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.0.7
+ 1.0.8
CFBundleSignature
????
CFBundleURLTypes
@@ -54,13 +54,15 @@
NSCameraUsageDescription
应用需要使用相机以实现相关功能
NSHealthShareUsageDescription
- 应用需要访问您的健康数据(步数与能量消耗)以展示运动统计。
+ 应用需要访问您的健康数据(步数、能量消耗、心率变异性等)以展示运动统计和压力分析。
NSHealthUpdateUsageDescription
应用需要更新您的健康数据(体重信息)以记录您的健身进度。
NSPhotoLibraryAddUsageDescription
应用需要写入相册以保存拍摄的体态照片(可选)。
NSPhotoLibraryUsageDescription
应用需要访问相册以选择您的体态照片用于AI测评。
+ NSUserNotificationsUsageDescription
+ 应用需要发送通知以提醒您喝水和站立活动。
NSUserActivityTypes
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
@@ -68,7 +70,8 @@
UIBackgroundModes
processing
- fetch
+ fetch
+ remote-notification
UILaunchStoryboardName
SplashScreen
diff --git a/services/backgroundTaskManager.ts b/services/backgroundTaskManager.ts
index c8eb95d..71af81a 100644
--- a/services/backgroundTaskManager.ts
+++ b/services/backgroundTaskManager.ts
@@ -3,6 +3,7 @@ import { StandReminderHelpers, WaterNotificationHelpers } from '@/utils/notifica
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';
+import { TaskManagerTaskBody } from 'expo-task-manager';
/**
* 后台任务标识符
@@ -14,7 +15,7 @@ export const BACKGROUND_TASK_IDS = {
} as const;
// 定义后台任务
-TaskManager.defineTask(BACKGROUND_TASK_IDS.HEALTH_REMINDERS, async () => {
+TaskManager.defineTask(BACKGROUND_TASK_IDS.HEALTH_REMINDERS, async (body: TaskManagerTaskBody) => {
try {
console.log('[BackgroundTask] 后台任务执行');
await executeBackgroundTasks();
diff --git a/utils/health.ts b/utils/health.ts
index 486807d..c96881c 100644
--- a/utils/health.ts
+++ b/utils/health.ts
@@ -452,22 +452,43 @@ async function fetchSleepDuration(options: HealthDataOptions): Promise {
async function fetchHeartRateVariability(options: HealthDataOptions): Promise {
return new Promise((resolve) => {
+ console.log('=== 开始获取HRV数据 ===');
+ console.log('查询选项:', options);
+
AppleHealthKit.getHeartRateVariabilitySamples(options, (err, res) => {
+ console.log('HRV API调用结果:', { err, res });
+
if (err) {
logError('HRV数据', err);
+ console.error('HRV获取错误详情:', err);
return resolve(null);
}
+
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('HRV', '为空或格式错误');
+ console.warn('HRV数据为空,原始响应:', res);
return resolve(null);
}
+
logSuccess('HRV', res);
+ console.log('HRV数据样本数量:', res.length);
+
+ // 打印最新的几个样本用于调试
+ const latestSamples = res.slice(-3);
+ console.log('最新的HRV样本:', latestSamples.map(sample => ({
+ value: sample.value,
+ startDate: sample.startDate,
+ endDate: sample.endDate
+ })));
const latestHrv = res[res.length - 1];
- if (latestHrv && latestHrv.value) {
+ if (latestHrv && latestHrv.value !== undefined && latestHrv.value !== null) {
// HealthKit 中的 HRV 数据已经是毫秒单位,无需转换
- resolve(Math.round(latestHrv.value));
+ const hrvValue = Math.round(latestHrv.value);
+ console.log('最终HRV值:', hrvValue);
+ resolve(hrvValue);
} else {
+ console.warn('HRV样本值无效:', latestHrv);
resolve(null);
}
});
@@ -681,6 +702,41 @@ export async function fetchRecentHRV(hoursBack: number = 2): Promise {
+ console.log('=== 开始测试HRV数据获取 ===');
+
+ try {
+ // 首先确保权限
+ const hasPermission = await ensureHealthPermissions();
+ if (!hasPermission) {
+ console.error('没有健康数据权限,无法测试HRV');
+ return;
+ }
+
+ console.log('权限检查通过,开始获取HRV数据...');
+
+ // 测试不同时间范围的HRV数据
+ const options = createDateRange(date);
+
+ // 获取今日HRV
+ const todayHRV = await fetchHeartRateVariability(options);
+ console.log('今日HRV结果:', todayHRV);
+
+ // 获取最近2小时HRV
+ const recentHRV = await fetchRecentHRV(2);
+ console.log('最近2小时HRV结果:', recentHRV);
+
+ // 获取指定日期HRV
+ const dateHRV = await fetchHRVForDate(date);
+ console.log('指定日期HRV结果:', dateHRV);
+
+ console.log('=== HRV数据测试完成 ===');
+ } catch (error) {
+ console.error('HRV测试过程中出现错误:', error);
+ }
+}
+
// 更新healthkit中的体重
export async function updateWeight(weight: number) {
return new Promise((resolve) => {