feat(health): 完善HealthKit权限管理和数据获取系统
- 重构权限管理,新增SimpleEventEmitter实现状态监听 - 实现完整的健身圆环数据获取(活动热量、锻炼时间、站立小时) - 优化组件状态管理,支持实时数据刷新和权限状态响应 - 新增useHealthPermissions Hook,简化权限状态管理 - 完善iOS原生代码,支持按小时统计健身数据 - 优化应用启动时权限初始化流程,避免启动弹窗 BREAKING CHANGE: FitnessRingsCard组件API变更,移除手动传参改为自动获取数据
This commit is contained in:
@@ -1,20 +1,14 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
|
||||
import { router } from 'expo-router';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { CircularRing } from './CircularRing';
|
||||
import { ROUTES } from '@/constants/Routes';
|
||||
import { fetchActivityRingsForDate, ActivityRingsData } from '@/utils/health';
|
||||
|
||||
type FitnessRingsCardProps = {
|
||||
style?: any;
|
||||
// 活动卡路里数据
|
||||
activeCalories?: number;
|
||||
activeCaloriesGoal?: number;
|
||||
// 锻炼分钟数据
|
||||
exerciseMinutes?: number;
|
||||
exerciseMinutesGoal?: number;
|
||||
// 站立小时数据
|
||||
standHours?: number;
|
||||
standHoursGoal?: number;
|
||||
selectedDate?: Date;
|
||||
// 动画重置令牌
|
||||
resetToken?: unknown;
|
||||
};
|
||||
@@ -24,14 +18,48 @@ type FitnessRingsCardProps = {
|
||||
*/
|
||||
export function FitnessRingsCard({
|
||||
style,
|
||||
activeCalories = 25,
|
||||
activeCaloriesGoal = 350,
|
||||
exerciseMinutes = 1,
|
||||
exerciseMinutesGoal = 5,
|
||||
standHours = 2,
|
||||
standHoursGoal = 13,
|
||||
selectedDate,
|
||||
resetToken,
|
||||
}: FitnessRingsCardProps) {
|
||||
const [activityData, setActivityData] = useState<ActivityRingsData | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const loadingRef = useRef(false);
|
||||
|
||||
// 获取健身圆环数据 - 在页面聚焦、日期变化、从后台切换到前台时触发
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const loadActivityData = async () => {
|
||||
if (!selectedDate) return;
|
||||
|
||||
// 防止重复请求
|
||||
if (loadingRef.current) return;
|
||||
|
||||
try {
|
||||
loadingRef.current = true;
|
||||
setLoading(true);
|
||||
const data = await fetchActivityRingsForDate(selectedDate);
|
||||
setActivityData(data);
|
||||
} catch (error) {
|
||||
console.error('FitnessRingsCard: 获取健身圆环数据失败:', error);
|
||||
setActivityData(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
loadingRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
loadActivityData();
|
||||
}, [selectedDate])
|
||||
);
|
||||
|
||||
// 使用获取到的数据或默认值
|
||||
const activeCalories = activityData?.activeEnergyBurned ?? 0;
|
||||
const activeCaloriesGoal = activityData?.activeEnergyBurnedGoal ?? 350;
|
||||
const exerciseMinutes = activityData?.appleExerciseTime ?? 0;
|
||||
const exerciseMinutesGoal = activityData?.appleExerciseTimeGoal ?? 30;
|
||||
const standHours = activityData?.appleStandHours ?? 0;
|
||||
const standHoursGoal = activityData?.appleStandHoursGoal ?? 12;
|
||||
|
||||
// 计算进度百分比
|
||||
const caloriesProgress = Math.min(1, Math.max(0, activeCalories / activeCaloriesGoal));
|
||||
const exerciseProgress = Math.min(1, Math.max(0, exerciseMinutes / exerciseMinutesGoal));
|
||||
@@ -95,24 +123,42 @@ export function FitnessRingsCard({
|
||||
<View style={styles.dataContainer}>
|
||||
<View style={styles.dataRow}>
|
||||
<Text style={styles.dataText}>
|
||||
<Text style={styles.dataValue}>{Math.round(activeCalories)}</Text>
|
||||
<Text style={styles.dataGoal}>/{activeCaloriesGoal}</Text>
|
||||
{loading ? (
|
||||
<Text style={styles.dataValue}>--</Text>
|
||||
) : (
|
||||
<>
|
||||
<Text style={styles.dataValue}>{Math.round(activeCalories)}</Text>
|
||||
<Text style={styles.dataGoal}>/{activeCaloriesGoal}</Text>
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
<Text style={styles.dataUnit}>千卡</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.dataRow}>
|
||||
<Text style={styles.dataText}>
|
||||
<Text style={styles.dataValue}>{Math.round(exerciseMinutes)}</Text>
|
||||
<Text style={styles.dataGoal}>/{exerciseMinutesGoal}</Text>
|
||||
{loading ? (
|
||||
<Text style={styles.dataValue}>--</Text>
|
||||
) : (
|
||||
<>
|
||||
<Text style={styles.dataValue}>{Math.round(exerciseMinutes)}</Text>
|
||||
<Text style={styles.dataGoal}>/{exerciseMinutesGoal}</Text>
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
<Text style={styles.dataUnit}>分钟</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.dataRow}>
|
||||
<Text style={styles.dataText}>
|
||||
<Text style={styles.dataValue}>{Math.round(standHours)}</Text>
|
||||
<Text style={styles.dataGoal}>/{standHoursGoal}</Text>
|
||||
{loading ? (
|
||||
<Text style={styles.dataValue}>--</Text>
|
||||
) : (
|
||||
<>
|
||||
<Text style={styles.dataValue}>{Math.round(standHours)}</Text>
|
||||
<Text style={styles.dataGoal}>/{standHoursGoal}</Text>
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
<Text style={styles.dataUnit}>小时</Text>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user