feat: 优化健康数据相关组件及功能
- 在 CoachScreen 中调整键盘高度计算,移除不必要的 insets.bottom - 更新 Statistics 组件,移除未使用的健康数据相关函数,简化代码 - 修改多个统计卡片,移除不必要的图标属性,提升组件简洁性 - 优化 HealthDataCard 和其他统计卡片的样式,提升视觉一致性 - 更新健康数据获取逻辑,确保数据处理更为准确 - 移除 MoodCard 中的多余元素,简化心情记录展示 - 调整 StressMeter 和其他组件的样式,提升用户体验
This commit is contained in:
@@ -419,7 +419,7 @@ export default function CoachScreen() {
|
|||||||
showSub = Keyboard.addListener('keyboardWillChangeFrame', (e: any) => {
|
showSub = Keyboard.addListener('keyboardWillChangeFrame', (e: any) => {
|
||||||
try {
|
try {
|
||||||
if (e?.endCoordinates?.height) {
|
if (e?.endCoordinates?.height) {
|
||||||
const height = Math.max(0, e.endCoordinates.height - insets.bottom);
|
const height = Math.max(0, e.endCoordinates.height);
|
||||||
setKeyboardOffset(height);
|
setKeyboardOffset(height);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { useColorScheme } from '@/hooks/useColorScheme';
|
|||||||
import { calculateNutritionSummary, getDietRecords, NutritionSummary } from '@/services/dietRecords';
|
import { calculateNutritionSummary, getDietRecords, NutritionSummary } from '@/services/dietRecords';
|
||||||
import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/moodSlice';
|
import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/moodSlice';
|
||||||
import { getMonthDaysZh, getTodayIndexInMonth } from '@/utils/date';
|
import { getMonthDaysZh, getTodayIndexInMonth } from '@/utils/date';
|
||||||
import { ensureHealthPermissions, fetchHealthDataForDate, testOxygenSaturationData } from '@/utils/health';
|
import { ensureHealthPermissions, fetchHealthDataForDate } from '@/utils/health';
|
||||||
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
||||||
import { useFocusEffect } from '@react-navigation/native';
|
import { useFocusEffect } from '@react-navigation/native';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@@ -124,14 +124,12 @@ export default function ExploreScreen() {
|
|||||||
const [oxygenSaturation, setOxygenSaturation] = useState<number | null>(null);
|
const [oxygenSaturation, setOxygenSaturation] = useState<number | null>(null);
|
||||||
const [heartRate, setHeartRate] = useState<number | null>(null);
|
const [heartRate, setHeartRate] = useState<number | null>(null);
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
// 用于触发动画重置的 token(当日期或数据变化时更新)
|
// 用于触发动画重置的 token(当日期或数据变化时更新)
|
||||||
const [animToken, setAnimToken] = useState(0);
|
const [animToken, setAnimToken] = useState(0);
|
||||||
const [trainingProgress, setTrainingProgress] = useState(0); // 暂定静态80%
|
|
||||||
|
|
||||||
// 营养数据状态
|
// 营养数据状态
|
||||||
const [nutritionSummary, setNutritionSummary] = useState<NutritionSummary | null>(null);
|
const [nutritionSummary, setNutritionSummary] = useState<NutritionSummary | null>(null);
|
||||||
const [isNutritionLoading, setIsNutritionLoading] = useState(false);
|
|
||||||
const { registerTask } = useBackgroundTasks();
|
const { registerTask } = useBackgroundTasks();
|
||||||
// 心情相关状态
|
// 心情相关状态
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@@ -182,7 +180,6 @@ export default function ExploreScreen() {
|
|||||||
const loadHealthData = async (targetDate?: Date) => {
|
const loadHealthData = async (targetDate?: Date) => {
|
||||||
try {
|
try {
|
||||||
console.log('=== 开始HealthKit初始化流程 ===');
|
console.log('=== 开始HealthKit初始化流程 ===');
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const ok = await ensureHealthPermissions();
|
const ok = await ensureHealthPermissions();
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -246,15 +243,12 @@ export default function ExploreScreen() {
|
|||||||
// 重置血氧饱和度和心率数据
|
// 重置血氧饱和度和心率数据
|
||||||
setOxygenSaturation(null);
|
setOxygenSaturation(null);
|
||||||
setHeartRate(null);
|
setHeartRate(null);
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载营养数据
|
// 加载营养数据
|
||||||
const loadNutritionData = async (targetDate?: Date) => {
|
const loadNutritionData = async (targetDate?: Date) => {
|
||||||
try {
|
try {
|
||||||
setIsNutritionLoading(true);
|
|
||||||
|
|
||||||
// 确定要查询的日期:优先使用传入的日期,否则使用当前选中索引对应的日期
|
// 确定要查询的日期:优先使用传入的日期,否则使用当前选中索引对应的日期
|
||||||
let derivedDate: Date;
|
let derivedDate: Date;
|
||||||
@@ -281,8 +275,6 @@ export default function ExploreScreen() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('营养数据加载失败:', error);
|
console.error('营养数据加载失败:', error);
|
||||||
setNutritionSummary(null);
|
setNutritionSummary(null);
|
||||||
} finally {
|
|
||||||
setIsNutritionLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -300,20 +292,20 @@ export default function ExploreScreen() {
|
|||||||
}, [selectedIndex])
|
}, [selectedIndex])
|
||||||
);
|
);
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// // 注册任务
|
// 注册任务
|
||||||
// registerTask({
|
registerTask({
|
||||||
// id: 'health-data-task',
|
id: 'health-data-task',
|
||||||
// name: 'health-data-task',
|
name: 'health-data-task',
|
||||||
// handler: async () => {
|
handler: async () => {
|
||||||
// try {
|
try {
|
||||||
// await loadHealthData();
|
await loadHealthData();
|
||||||
// } catch (error) {
|
} catch (error) {
|
||||||
// console.error('健康数据任务执行失败:', error);
|
console.error('健康数据任务执行失败:', error);
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
// });
|
});
|
||||||
// }, []);
|
}, []);
|
||||||
|
|
||||||
// 日期点击时,加载对应日期数据
|
// 日期点击时,加载对应日期数据
|
||||||
const onSelectDate = (index: number, date: Date) => {
|
const onSelectDate = (index: number, date: Date) => {
|
||||||
@@ -325,16 +317,6 @@ export default function ExploreScreen() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 测试血氧饱和度数据
|
|
||||||
const testOxygenData = async () => {
|
|
||||||
console.log('开始测试血氧饱和度数据...');
|
|
||||||
const currentDate = getCurrentSelectedDate();
|
|
||||||
await testOxygenSaturationData(currentDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 使用统一的渐变背景色
|
|
||||||
const backgroundGradientColors = [colorTokens.backgroundGradientStart, colorTokens.backgroundGradientEnd] as const;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{/* 背景渐变 */}
|
{/* 背景渐变 */}
|
||||||
@@ -384,8 +366,8 @@ export default function ExploreScreen() {
|
|||||||
/>
|
/>
|
||||||
</FloatingCard>
|
</FloatingCard>
|
||||||
|
|
||||||
<FloatingCard style={[styles.masonryCard, styles.caloriesCard]} delay={500}>
|
<FloatingCard style={styles.masonryCard} delay={500}>
|
||||||
<Text style={styles.cardTitleSecondary}>消耗卡路里</Text>
|
<Text style={styles.cardTitle}>消耗卡路里</Text>
|
||||||
{activeCalories != null ? (
|
{activeCalories != null ? (
|
||||||
<AnimatedNumber
|
<AnimatedNumber
|
||||||
value={activeCalories}
|
value={activeCalories}
|
||||||
@@ -398,7 +380,7 @@ export default function ExploreScreen() {
|
|||||||
)}
|
)}
|
||||||
</FloatingCard>
|
</FloatingCard>
|
||||||
|
|
||||||
<FloatingCard style={[styles.masonryCard, styles.stepsCard]} delay={1000}>
|
<FloatingCard style={styles.masonryCard} delay={1000}>
|
||||||
<View style={styles.cardHeaderRow}>
|
<View style={styles.cardHeaderRow}>
|
||||||
<Text style={styles.cardTitle}>步数</Text>
|
<Text style={styles.cardTitle}>步数</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -414,14 +396,14 @@ export default function ExploreScreen() {
|
|||||||
)}
|
)}
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
progress={Math.min(1, Math.max(0, (stepCount ?? 0) / stepGoal))}
|
progress={Math.min(1, Math.max(0, (stepCount ?? 0) / stepGoal))}
|
||||||
height={16}
|
height={14}
|
||||||
trackColor="#FFEBCB"
|
trackColor="#FFEBCB"
|
||||||
fillColor="#FFC365"
|
fillColor="#FFC365"
|
||||||
showLabel={false}
|
showLabel={false}
|
||||||
/>
|
/>
|
||||||
</FloatingCard>
|
</FloatingCard>
|
||||||
{/* 心情卡片 */}
|
{/* 心情卡片 */}
|
||||||
<FloatingCard style={[styles.masonryCard, styles.moodCard]} delay={1500}>
|
<FloatingCard style={styles.masonryCard} delay={1500}>
|
||||||
<MoodCard
|
<MoodCard
|
||||||
moodCheckin={currentMoodCheckin}
|
moodCheckin={currentMoodCheckin}
|
||||||
onPress={() => pushIfAuthedElseLogin('/mood/calendar')}
|
onPress={() => pushIfAuthedElseLogin('/mood/calendar')}
|
||||||
@@ -444,7 +426,7 @@ export default function ExploreScreen() {
|
|||||||
/>
|
/>
|
||||||
</FloatingCard>
|
</FloatingCard>
|
||||||
|
|
||||||
<FloatingCard style={[styles.masonryCard, styles.sleepCard]} delay={750}>
|
<FloatingCard style={styles.masonryCard} delay={750}>
|
||||||
<View style={styles.cardHeaderRow}>
|
<View style={styles.cardHeaderRow}>
|
||||||
<Text style={styles.cardTitle}>睡眠</Text>
|
<Text style={styles.cardTitle}>睡眠</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -473,13 +455,6 @@ export default function ExploreScreen() {
|
|||||||
style={styles.basalMetabolismCardOverride}
|
style={styles.basalMetabolismCardOverride}
|
||||||
oxygenSaturation={oxygenSaturation}
|
oxygenSaturation={oxygenSaturation}
|
||||||
/>
|
/>
|
||||||
{/* 测试按钮 - 开发时使用 */}
|
|
||||||
<Text
|
|
||||||
style={styles.testButton}
|
|
||||||
onPress={testOxygenData}
|
|
||||||
>
|
|
||||||
测试血氧数据
|
|
||||||
</Text>
|
|
||||||
</FloatingCard>
|
</FloatingCard>
|
||||||
|
|
||||||
{/* 心率卡片 */}
|
{/* 心率卡片 */}
|
||||||
@@ -577,22 +552,11 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 22,
|
borderRadius: 22,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
},
|
},
|
||||||
caloriesCard: {
|
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
},
|
|
||||||
trainingCard: {
|
|
||||||
backgroundColor: '#EEE9FF',
|
|
||||||
},
|
|
||||||
cardTitleSecondary: {
|
|
||||||
color: '#9AA3AE',
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: '600',
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
caloriesValue: {
|
caloriesValue: {
|
||||||
color: '#192126',
|
color: '#192126',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
|
marginTop: 18,
|
||||||
},
|
},
|
||||||
trainingContent: {
|
trainingContent: {
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
@@ -706,9 +670,6 @@ const styles = StyleSheet.create({
|
|||||||
color: '#5B5B5B',
|
color: '#5B5B5B',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
stepsCard: {
|
|
||||||
backgroundColor: '#FFE4B8',
|
|
||||||
},
|
|
||||||
stepsValue: {
|
stepsValue: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: '#7A6A42',
|
color: '#7A6A42',
|
||||||
@@ -797,9 +758,6 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
},
|
},
|
||||||
sleepCard: {
|
|
||||||
backgroundColor: '#E8F4FD',
|
|
||||||
},
|
|
||||||
sleepValue: {
|
sleepValue: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: '#1E40AF',
|
color: '#1E40AF',
|
||||||
@@ -821,14 +779,5 @@ const styles = StyleSheet.create({
|
|||||||
top: 0,
|
top: 0,
|
||||||
padding: 4,
|
padding: 4,
|
||||||
},
|
},
|
||||||
moodCard: {
|
|
||||||
backgroundColor: '#F0FDF4',
|
|
||||||
},
|
|
||||||
testButton: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: '#3B82F6',
|
|
||||||
textAlign: 'center',
|
|
||||||
marginTop: 8,
|
|
||||||
padding: 4,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { AnimatedNumber } from '@/components/AnimatedNumber';
|
import { AnimatedNumber } from '@/components/AnimatedNumber';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
@@ -32,17 +31,6 @@ export function BasalMetabolismCard({ value, resetToken, style }: BasalMetabolis
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, style]}>
|
<View style={[styles.container, style]}>
|
||||||
{/* 渐变背景 */}
|
|
||||||
<LinearGradient
|
|
||||||
colors={['#F0F9FF', '#E0F2FE']}
|
|
||||||
style={styles.gradientBackground}
|
|
||||||
start={{ x: 0, y: 0 }}
|
|
||||||
end={{ x: 1, y: 1 }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 装饰性圆圈 */}
|
|
||||||
<View style={styles.decorativeCircle1} />
|
|
||||||
<View style={styles.decorativeCircle2} />
|
|
||||||
|
|
||||||
{/* 头部区域 */}
|
{/* 头部区域 */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
@@ -88,34 +76,7 @@ const styles = StyleSheet.create({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
gradientBackground: {
|
|
||||||
position: 'absolute',
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
opacity: 0.6,
|
|
||||||
},
|
|
||||||
decorativeCircle1: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: -20,
|
|
||||||
right: -20,
|
|
||||||
width: 60,
|
|
||||||
height: 60,
|
|
||||||
borderRadius: 30,
|
|
||||||
backgroundColor: '#0EA5E9',
|
|
||||||
opacity: 0.1,
|
|
||||||
},
|
|
||||||
decorativeCircle2: {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: -15,
|
|
||||||
left: -15,
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
borderRadius: 20,
|
|
||||||
backgroundColor: '#0EA5E9',
|
|
||||||
opacity: 0.05,
|
|
||||||
},
|
|
||||||
header: {
|
header: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@@ -67,20 +67,39 @@ export const DateSelector: React.FC<DateSelectorProps> = ({
|
|||||||
const maxScrollOffset = Math.max(0, (days.length * itemWidth) - scrollWidth);
|
const maxScrollOffset = Math.max(0, (days.length * itemWidth) - scrollWidth);
|
||||||
const finalOffset = Math.min(centerOffset, maxScrollOffset);
|
const finalOffset = Math.min(centerOffset, maxScrollOffset);
|
||||||
|
|
||||||
daysScrollRef.current.scrollTo({ x: finalOffset, animated });
|
if (animated) {
|
||||||
|
// 使用原生动画API实现更平滑的滚动
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
daysScrollRef.current?.scrollTo({
|
||||||
|
x: finalOffset,
|
||||||
|
animated: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 非动画情况直接跳转
|
||||||
|
daysScrollRef.current?.scrollTo({
|
||||||
|
x: finalOffset,
|
||||||
|
animated: false
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化时滚动到选中项
|
// 初始化时滚动到选中项
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (scrollWidth > 0 && autoScrollToSelected) {
|
if (scrollWidth > 0 && autoScrollToSelected) {
|
||||||
scrollToIndex(selectedIndex, false);
|
scrollToIndex(selectedIndex, true);
|
||||||
}
|
}
|
||||||
}, [scrollWidth, selectedIndex, autoScrollToSelected]);
|
}, [scrollWidth, selectedIndex, autoScrollToSelected]);
|
||||||
|
|
||||||
// 当选中索引变化时,滚动到对应位置
|
// 当选中索引变化时,滚动到对应位置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (scrollWidth > 0 && autoScrollToSelected) {
|
if (scrollWidth > 0 && autoScrollToSelected) {
|
||||||
scrollToIndex(selectedIndex, true);
|
// 添加微小延迟以确保动画效果更明显
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
scrollToIndex(selectedIndex, true);
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, [selectedIndex, autoScrollToSelected]);
|
}, [selectedIndex, autoScrollToSelected]);
|
||||||
|
|
||||||
@@ -94,6 +113,11 @@ export const DateSelector: React.FC<DateSelectorProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 先滚动到目标位置,再更新状态
|
||||||
|
if (autoScrollToSelected) {
|
||||||
|
scrollToIndex(index, true);
|
||||||
|
}
|
||||||
|
|
||||||
// 更新内部状态(如果使用外部控制则不更新)
|
// 更新内部状态(如果使用外部控制则不更新)
|
||||||
if (externalSelectedIndex === undefined) {
|
if (externalSelectedIndex === undefined) {
|
||||||
setInternalSelectedIndex(index);
|
setInternalSelectedIndex(index);
|
||||||
|
|||||||
@@ -14,20 +14,8 @@ export function MoodCard({ moodCheckin, onPress, isLoading = false }: MoodCardPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={onPress} style={styles.moodCardContent} disabled={isLoading}>
|
<TouchableOpacity onPress={onPress} style={styles.moodCardContent} disabled={isLoading}>
|
||||||
<View style={styles.cardHeaderRow}>
|
<Text style={styles.cardTitle}>心情</Text>
|
||||||
<View style={styles.moodIconContainer}>
|
|
||||||
{moodCheckin ? (
|
|
||||||
<Text style={styles.moodIcon}>
|
|
||||||
{moodConfig?.emoji || '😊'}
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<Text style={styles.moodIcon}>😊</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<Text style={styles.cardTitle}>心情</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Text style={styles.moodSubtitle}>记录你的每日心情</Text>
|
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<View style={styles.moodPreview}>
|
<View style={styles.moodPreview}>
|
||||||
@@ -53,39 +41,18 @@ const styles = StyleSheet.create({
|
|||||||
moodCardContent: {
|
moodCardContent: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
cardHeaderRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
moodIconContainer: {
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
borderRadius: 8,
|
|
||||||
backgroundColor: '#DCFCE7',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
marginRight: 10,
|
|
||||||
},
|
|
||||||
moodIcon: {
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
cardTitle: {
|
cardTitle: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
color: '#192126',
|
color: '#192126',
|
||||||
},
|
},
|
||||||
moodSubtitle: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: '#6B7280',
|
|
||||||
marginTop: 4,
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
moodPreview: {
|
moodPreview: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginTop: 4,
|
marginTop: 22,
|
||||||
},
|
},
|
||||||
moodPreviewText: {
|
moodPreviewText: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -100,12 +67,12 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: '#9CA3AF',
|
color: '#9CA3AF',
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
marginTop: 4,
|
marginTop: 22,
|
||||||
},
|
},
|
||||||
moodLoadingText: {
|
moodLoadingText: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: '#9CA3AF',
|
color: '#9CA3AF',
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
marginTop: 4,
|
marginTop: 22,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -68,7 +68,6 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
|
|||||||
<View style={styles.leftSection}>
|
<View style={styles.leftSection}>
|
||||||
<Text style={styles.title}>压力</Text>
|
<Text style={styles.title}>压力</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.emoji}>{getStatusEmoji()}</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 数值显示区域 */}
|
{/* 数值显示区域 */}
|
||||||
@@ -83,7 +82,7 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
|
|||||||
{/* 渐变背景进度条 */}
|
{/* 渐变背景进度条 */}
|
||||||
<View style={[styles.progressBar, { width: '100%' }]}>
|
<View style={[styles.progressBar, { width: '100%' }]}>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={['#EF4444', '#FCD34D', '#10B981']}
|
colors={['#EF4444', '#FCD34D', '#10B981']}
|
||||||
start={{ x: 0, y: 0 }}
|
start={{ x: 0, y: 0 }}
|
||||||
end={{ x: 1, y: 0 }}
|
end={{ x: 1, y: 0 }}
|
||||||
style={styles.gradientBar}
|
style={styles.gradientBar}
|
||||||
@@ -113,8 +112,6 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
padding: 2,
|
|
||||||
shadowColor: '#000',
|
shadowColor: '#000',
|
||||||
shadowOffset: {
|
shadowOffset: {
|
||||||
width: 0,
|
width: 0,
|
||||||
@@ -147,19 +144,16 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '700',
|
fontWeight: '800',
|
||||||
color: '#192126',
|
color: '#192126',
|
||||||
},
|
},
|
||||||
emoji: {
|
|
||||||
fontSize: 16,
|
|
||||||
},
|
|
||||||
valueSection: {
|
valueSection: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'baseline',
|
alignItems: 'baseline',
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
fontSize: 28,
|
fontSize: 24,
|
||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
color: '#192126',
|
color: '#192126',
|
||||||
lineHeight: 32,
|
lineHeight: 32,
|
||||||
@@ -172,7 +166,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
progressContainer: {
|
progressContainer: {
|
||||||
height: 16,
|
height: 16,
|
||||||
marginBottom: 4,
|
|
||||||
},
|
},
|
||||||
progressTrack: {
|
progressTrack: {
|
||||||
height: 8,
|
height: 8,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ interface HealthDataCardProps {
|
|||||||
title: string;
|
title: string;
|
||||||
value: string;
|
value: string;
|
||||||
unit: string;
|
unit: string;
|
||||||
icon: React.ReactNode;
|
|
||||||
style?: object;
|
style?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,7 +13,6 @@ const HealthDataCard: React.FC<HealthDataCardProps> = ({
|
|||||||
title,
|
title,
|
||||||
value,
|
value,
|
||||||
unit,
|
unit,
|
||||||
icon,
|
|
||||||
style
|
style
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
@@ -23,10 +21,6 @@ const HealthDataCard: React.FC<HealthDataCardProps> = ({
|
|||||||
exiting={FadeOut.duration(300)}
|
exiting={FadeOut.duration(300)}
|
||||||
style={[styles.card, style]}
|
style={[styles.card, style]}
|
||||||
>
|
>
|
||||||
|
|
||||||
<View style={styles.iconContainer}>
|
|
||||||
{icon}
|
|
||||||
</View>
|
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<Text style={styles.title}>{title}</Text>
|
<Text style={styles.title}>{title}</Text>
|
||||||
<View style={styles.valueContainer}>
|
<View style={styles.valueContainer}>
|
||||||
@@ -65,9 +59,9 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: '#666',
|
color: '#192126',
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
fontWeight: '600',
|
fontWeight: '800',
|
||||||
},
|
},
|
||||||
valueContainer: {
|
valueContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ const HeartRateCard: React.FC<HeartRateCardProps> = ({
|
|||||||
title="心率"
|
title="心率"
|
||||||
value={heartRate !== null && heartRate !== undefined ? Math.round(heartRate).toString() : '--'}
|
value={heartRate !== null && heartRate !== undefined ? Math.round(heartRate).toString() : '--'}
|
||||||
unit="bpm"
|
unit="bpm"
|
||||||
icon={heartIcon}
|
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ const OxygenSaturationCard: React.FC<OxygenSaturationCardProps> = ({
|
|||||||
title="血氧饱和度"
|
title="血氧饱和度"
|
||||||
value={oxygenSaturation !== null && oxygenSaturation !== undefined ? oxygenSaturation.toFixed(1) : '--'}
|
value={oxygenSaturation !== null && oxygenSaturation !== undefined ? oxygenSaturation.toFixed(1) : '--'}
|
||||||
unit="%"
|
unit="%"
|
||||||
icon={oxygenIcon}
|
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -280,12 +280,12 @@ export async function fetchHealthDataForDate(date: Date): Promise<TodayHealthDat
|
|||||||
sleepDuration,
|
sleepDuration,
|
||||||
hrv,
|
hrv,
|
||||||
// 健身圆环数据
|
// 健身圆环数据
|
||||||
activeCalories: activitySummary?.activeEnergyBurned || 0,
|
activeCalories: Math.round(activitySummary?.activeEnergyBurned || 0),
|
||||||
activeCaloriesGoal: activitySummary?.activeEnergyBurnedGoal || 350,
|
activeCaloriesGoal: Math.round(activitySummary?.activeEnergyBurnedGoal || 350),
|
||||||
exerciseMinutes: activitySummary?.appleExerciseTime || 0,
|
exerciseMinutes: Math.round(activitySummary?.appleExerciseTime || 0),
|
||||||
exerciseMinutesGoal: activitySummary?.appleExerciseTimeGoal || 30,
|
exerciseMinutesGoal: Math.round(activitySummary?.appleExerciseTimeGoal || 30),
|
||||||
standHours: activitySummary?.appleStandHours || 0,
|
standHours: Math.round(activitySummary?.appleStandHours || 0),
|
||||||
standHoursGoal: activitySummary?.appleStandHoursGoal || 12,
|
standHoursGoal: Math.round(activitySummary?.appleStandHoursGoal || 12),
|
||||||
// 血氧饱和度和心率数据
|
// 血氧饱和度和心率数据
|
||||||
oxygenSaturation,
|
oxygenSaturation,
|
||||||
heartRate
|
heartRate
|
||||||
|
|||||||
Reference in New Issue
Block a user