feat: 集成健康数据功能

- 在项目中引入 react-native-health 库以获取健康数据
- 在 Explore 页面中添加步数和能量消耗的显示
- 实现页面聚焦时自动拉取今日健康数据
- 更新 iOS 权限设置以支持健康数据访问
- 添加健康数据相关的工具函数以简化数据获取
This commit is contained in:
richarjiang
2025-08-12 09:29:34 +08:00
parent 9796c614ed
commit 67972fa92b
7 changed files with 278 additions and 6 deletions

View File

@@ -2,8 +2,10 @@ import { ProgressBar } from '@/components/ProgressBar';
import { Colors } from '@/constants/Colors';
import { getTabBarBottomPadding } from '@/constants/TabBar';
import { getMonthDaysZh, getMonthTitleZh, getTodayIndexInMonth } from '@/utils/date';
import { ensureHealthPermissions, fetchTodayHealthData } from '@/utils/health';
import { Ionicons } from '@expo/vector-icons';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useFocusEffect } from '@react-navigation/native';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
SafeAreaView,
@@ -46,6 +48,30 @@ export default function ExploreScreen() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [scrollWidth]);
// HealthKit: 每次页面聚焦都拉取今日数据
const [stepCount, setStepCount] = useState<number | null>(null);
const [activeCalories, setActiveCalories] = useState<number | null>(null);
useFocusEffect(
React.useCallback(() => {
let isActive = true;
const run = async () => {
console.log('HealthKit init start');
const ok = await ensureHealthPermissions();
if (!ok) return;
const data = await fetchTodayHealthData();
if (!isActive) return;
setStepCount(data.steps);
setActiveCalories(Math.round(data.activeEnergyBurned));
};
run();
return () => {
isActive = false;
};
}, [])
);
return (
<View style={styles.container}>
<SafeAreaView style={styles.safeArea}>
@@ -100,15 +126,17 @@ export default function ExploreScreen() {
<View style={styles.metricsRight}>
<View style={[styles.metricsRightCard, styles.caloriesCard, { minHeight: 88 }]}>
<Text style={styles.cardTitleSecondary}></Text>
<Text style={styles.caloriesValue}>645 </Text>
<Text style={styles.caloriesValue}>
{activeCalories != null ? `${activeCalories} 千卡` : '——'}
</Text>
</View>
<View style={[styles.metricsRightCard, styles.stepsCard, { minHeight: 88 }]}>
<View style={styles.cardHeaderRow}>
<View style={styles.iconSquare}><Ionicons name="footsteps-outline" size={18} color="#192126" /></View>
<Text style={styles.cardTitle}></Text>
</View>
<Text style={styles.stepsValue}>999/2000</Text>
<ProgressBar progress={0.5} height={12} trackColor="#FFEBCB" fillColor="#FFC365" />
<Text style={styles.stepsValue}>{stepCount != null ? `${stepCount}/2000` : '——/2000'}</Text>
<ProgressBar progress={Math.min(1, Math.max(0, (stepCount ?? 0) / 2000))} height={12} trackColor="#FFEBCB" fillColor="#FFC365" />
</View>
</View>
</View>