feat: Update FitnessRingsCard to navigate to fitness rings detail page on press feat: Modify NutritionRadarCard to enhance UI and add haptic feedback on actions feat: Add FITNESS_RINGS_DETAIL route for navigation fix: Adjust minimum fetch interval in BackgroundTaskManager for background tasks feat: Implement haptic feedback utility functions for better user experience feat: Extend health permissions to include Apple Exercise Time and Apple Stand Time feat: Add functions to fetch hourly activity, exercise, and stand data for improved health tracking feat: Enhance user preferences to manage fitness exercise minutes and active hours info dismissal
186 lines
5.1 KiB
TypeScript
186 lines
5.1 KiB
TypeScript
import React from 'react';
|
|
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
|
|
import { router } from 'expo-router';
|
|
import { CircularRing } from './CircularRing';
|
|
import { ROUTES } from '@/constants/Routes';
|
|
|
|
type FitnessRingsCardProps = {
|
|
style?: any;
|
|
// 活动卡路里数据
|
|
activeCalories?: number;
|
|
activeCaloriesGoal?: number;
|
|
// 锻炼分钟数据
|
|
exerciseMinutes?: number;
|
|
exerciseMinutesGoal?: number;
|
|
// 站立小时数据
|
|
standHours?: number;
|
|
standHoursGoal?: number;
|
|
// 动画重置令牌
|
|
resetToken?: unknown;
|
|
};
|
|
|
|
/**
|
|
* 健身圆环卡片组件,模仿 Apple Watch 的健身圆环
|
|
*/
|
|
export function FitnessRingsCard({
|
|
style,
|
|
activeCalories = 25,
|
|
activeCaloriesGoal = 350,
|
|
exerciseMinutes = 1,
|
|
exerciseMinutesGoal = 5,
|
|
standHours = 2,
|
|
standHoursGoal = 13,
|
|
resetToken,
|
|
}: FitnessRingsCardProps) {
|
|
// 计算进度百分比
|
|
const caloriesProgress = Math.min(1, Math.max(0, activeCalories / activeCaloriesGoal));
|
|
const exerciseProgress = Math.min(1, Math.max(0, exerciseMinutes / exerciseMinutesGoal));
|
|
const standProgress = Math.min(1, Math.max(0, standHours / standHoursGoal));
|
|
|
|
const handlePress = () => {
|
|
router.push(ROUTES.FITNESS_RINGS_DETAIL);
|
|
};
|
|
|
|
return (
|
|
<TouchableOpacity style={[styles.container, style]} onPress={handlePress}>
|
|
<View style={styles.contentContainer}>
|
|
{/* 左侧圆环 */}
|
|
<View style={styles.ringsContainer}>
|
|
<View style={styles.ringWrapper}>
|
|
{/* 外圈 - 活动卡路里 (红色) */}
|
|
<View style={[styles.ringPosition]}>
|
|
<CircularRing
|
|
size={36}
|
|
strokeWidth={2.5}
|
|
trackColor="rgba(255, 59, 48, 0.15)"
|
|
progressColor="#FF3B30"
|
|
progress={caloriesProgress}
|
|
showCenterText={false}
|
|
resetToken={resetToken}
|
|
startAngleDeg={-90}
|
|
/>
|
|
</View>
|
|
|
|
{/* 中圈 - 锻炼分钟 (橙色) */}
|
|
<View style={[styles.ringPosition]}>
|
|
<CircularRing
|
|
size={26}
|
|
strokeWidth={2}
|
|
trackColor="rgba(255, 149, 0, 0.15)"
|
|
progressColor="#FF9500"
|
|
progress={exerciseProgress}
|
|
showCenterText={false}
|
|
resetToken={resetToken}
|
|
startAngleDeg={-90}
|
|
/>
|
|
</View>
|
|
|
|
{/* 内圈 - 站立小时 (蓝色) */}
|
|
<View style={[styles.ringPosition]}>
|
|
<CircularRing
|
|
size={16}
|
|
strokeWidth={1.5}
|
|
trackColor="rgba(0, 122, 255, 0.15)"
|
|
progressColor="#007AFF"
|
|
progress={standProgress}
|
|
showCenterText={false}
|
|
resetToken={resetToken}
|
|
startAngleDeg={-90}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 右侧数据显示 */}
|
|
<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>
|
|
</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>
|
|
</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>
|
|
</Text>
|
|
<Text style={styles.dataUnit}>小时</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
borderRadius: 16,
|
|
shadowColor: '#000',
|
|
shadowOffset: {
|
|
width: 0,
|
|
height: 2,
|
|
},
|
|
shadowOpacity: 0.08,
|
|
shadowRadius: 8,
|
|
elevation: 3,
|
|
},
|
|
contentContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
},
|
|
ringsContainer: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
marginRight: 12,
|
|
},
|
|
ringWrapper: {
|
|
position: 'relative',
|
|
width: 36,
|
|
height: 36,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
ringPosition: {
|
|
position: 'absolute',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
dataContainer: {
|
|
flex: 1,
|
|
gap: 3,
|
|
},
|
|
dataRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
},
|
|
dataText: {
|
|
fontSize: 12,
|
|
fontWeight: '700',
|
|
flex: 1,
|
|
},
|
|
dataValue: {
|
|
color: '#192126',
|
|
},
|
|
dataGoal: {
|
|
color: '#9AA3AE',
|
|
},
|
|
dataUnit: {
|
|
fontSize: 10,
|
|
color: '#9AA3AE',
|
|
fontWeight: '500',
|
|
minWidth: 25,
|
|
textAlign: 'right',
|
|
},
|
|
}); |