feat: 更新统计标签和标题,优化健康数据卡片样式,调整步数和健康相关组件的样式
This commit is contained in:
@@ -48,7 +48,7 @@ export default function TabLayout() {
|
||||
case 'goals':
|
||||
return { icon: 'flag.fill', title: '习惯' } as const;
|
||||
case 'statistics':
|
||||
return { icon: 'chart.pie.fill', title: '统计' } as const;
|
||||
return { icon: 'chart.pie.fill', title: '健康' } as const;
|
||||
case 'personal':
|
||||
return { icon: 'person.fill', title: '个人' } as const;
|
||||
default:
|
||||
@@ -132,15 +132,15 @@ export default function TabLayout() {
|
||||
}}>
|
||||
|
||||
<Tabs.Screen
|
||||
name="coach"
|
||||
name="statistics"
|
||||
options={{
|
||||
title: 'Seal',
|
||||
title: '统计',
|
||||
tabBarIcon: ({ color }) => {
|
||||
const isCoachSelected = pathname === '/coach';
|
||||
const isStatisticsSelected = pathname === '/statistics';
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<IconSymbol size={22} name="person.3.fill" color={color} />
|
||||
{isCoachSelected && (
|
||||
<IconSymbol size={22} name="chart.pie.fill" color={color} />
|
||||
{isStatisticsSelected && (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
@@ -151,7 +151,7 @@ export default function TabLayout() {
|
||||
textAlign: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
Seal
|
||||
健康
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
@@ -188,17 +188,16 @@ export default function TabLayout() {
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tabs.Screen
|
||||
name="statistics"
|
||||
name="coach"
|
||||
options={{
|
||||
title: '统计',
|
||||
title: 'Seal',
|
||||
tabBarIcon: ({ color }) => {
|
||||
const isStatisticsSelected = pathname === '/statistics';
|
||||
const isCoachSelected = pathname === '/coach';
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<IconSymbol size={22} name="chart.pie.fill" color={color} />
|
||||
{isStatisticsSelected && (
|
||||
<IconSymbol size={22} name="person.3.fill" color={color} />
|
||||
{isCoachSelected && (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
@@ -209,7 +208,7 @@ export default function TabLayout() {
|
||||
textAlign: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
统计
|
||||
Seal
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
@@ -217,6 +216,7 @@ export default function TabLayout() {
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tabs.Screen
|
||||
name="goals"
|
||||
options={{
|
||||
|
||||
@@ -300,8 +300,6 @@ export default function ExploreScreen() {
|
||||
contentContainerStyle={{ paddingBottom: bottomPadding }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* 体重历史记录卡片 */}
|
||||
<Text style={styles.sectionTitle}>健康数据</Text>
|
||||
<WeightHistoryCard />
|
||||
|
||||
{/* 日期选择器 */}
|
||||
@@ -617,7 +615,6 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
color: '#192126',
|
||||
},
|
||||
heartCard: {
|
||||
|
||||
@@ -115,8 +115,7 @@ const styles = StyleSheet.create({
|
||||
borderBottomRightRadius: 2,
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
fontSize: 14,
|
||||
color: '#0F172A',
|
||||
},
|
||||
statusBadge: {
|
||||
|
||||
@@ -185,7 +185,7 @@ const styles = StyleSheet.create({
|
||||
paddingVertical: 8,
|
||||
},
|
||||
monthTitle: {
|
||||
fontSize: 24,
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
color: '#192126',
|
||||
marginBottom: 14,
|
||||
@@ -234,7 +234,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
dayDate: {
|
||||
fontSize: 12,
|
||||
fontWeight: '800',
|
||||
fontWeight: '600',
|
||||
color: 'gray',
|
||||
},
|
||||
dayDateSelected: {
|
||||
|
||||
@@ -42,7 +42,6 @@ const styles = StyleSheet.create({
|
||||
|
||||
cardTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
color: '#192126',
|
||||
},
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ export function NutritionRadarCard({
|
||||
<View style={styles.cardRightContainer}>
|
||||
<Text style={styles.cardSubtitle}>更新: {dayjs(nutritionSummary?.updatedAt).format('MM-DD HH:mm')}</Text>
|
||||
<TouchableOpacity style={styles.addButton} onPress={handleAddFood}>
|
||||
<Ionicons name="add" size={16} color="#FFFFFF" />
|
||||
<Ionicons name="add" size={12} color="#514b4bff" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@@ -185,11 +185,10 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
},
|
||||
cardRightContainer: {
|
||||
@@ -198,7 +197,7 @@ const styles = StyleSheet.create({
|
||||
gap: 4,
|
||||
},
|
||||
cardSubtitle: {
|
||||
fontSize: 12,
|
||||
fontSize: 10,
|
||||
color: '#9AA3AE',
|
||||
fontWeight: '600',
|
||||
},
|
||||
@@ -230,15 +229,14 @@ const styles = StyleSheet.create({
|
||||
marginRight: 8,
|
||||
},
|
||||
statLabel: {
|
||||
fontSize: 12,
|
||||
fontSize: 10,
|
||||
color: '#9AA3AE',
|
||||
fontWeight: '600',
|
||||
flex: 1,
|
||||
},
|
||||
statValue: {
|
||||
fontSize: 12,
|
||||
color: '#192126',
|
||||
fontWeight: '700',
|
||||
fontWeight: '600',
|
||||
},
|
||||
// 卡路里相关样式
|
||||
calorieSection: {
|
||||
@@ -272,12 +270,12 @@ const styles = StyleSheet.create({
|
||||
gap: 4,
|
||||
},
|
||||
mainValue: {
|
||||
fontSize: 24,
|
||||
fontWeight: '800',
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#192126',
|
||||
},
|
||||
calculationText: {
|
||||
fontSize: 14,
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: '#64748B',
|
||||
},
|
||||
@@ -287,12 +285,12 @@ const styles = StyleSheet.create({
|
||||
gap: 2,
|
||||
},
|
||||
calculationLabel: {
|
||||
fontSize: 10,
|
||||
fontSize: 8,
|
||||
color: '#64748B',
|
||||
fontWeight: '500',
|
||||
},
|
||||
calculationValue: {
|
||||
fontSize: 12,
|
||||
fontSize: 10,
|
||||
fontWeight: '700',
|
||||
color: '#192126',
|
||||
},
|
||||
@@ -315,10 +313,10 @@ const styles = StyleSheet.create({
|
||||
fontSize: 24,
|
||||
},
|
||||
addButton: {
|
||||
width: 18,
|
||||
height: 18,
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 8,
|
||||
backgroundColor: '#9AA3AE',
|
||||
backgroundColor: '#e5e8ecff',
|
||||
marginLeft: 8,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -106,8 +106,7 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
},
|
||||
footprintIcons: {
|
||||
|
||||
@@ -145,7 +145,6 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
color: '#192126',
|
||||
},
|
||||
valueSection: {
|
||||
|
||||
@@ -55,7 +55,6 @@ const styles = StyleSheet.create({
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
marginBottom: 14,
|
||||
fontWeight: '800',
|
||||
},
|
||||
valueContainer: {
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -11,13 +11,12 @@ import { LinearGradient } from 'expo-linear-gradient';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
Image,
|
||||
Modal,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Animated, {
|
||||
Extrapolation,
|
||||
@@ -201,9 +200,6 @@ export function WeightHistoryCard() {
|
||||
return (
|
||||
<TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}>
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.iconSquare}>
|
||||
<Image source={require('@/assets/images/icons/iconWeight.png')} style={{ width: 18, height: 18 }} />
|
||||
</View>
|
||||
<Text style={styles.cardTitle}>体重记录</Text>
|
||||
</View>
|
||||
<View style={styles.emptyContent}>
|
||||
@@ -218,9 +214,6 @@ export function WeightHistoryCard() {
|
||||
return (
|
||||
<TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}>
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.iconSquare}>
|
||||
<Image source={require('@/assets/images/icons/iconWeight.png')} style={{ width: 18, height: 18 }} />
|
||||
</View>
|
||||
<Text style={styles.cardTitle}>体重记录</Text>
|
||||
</View>
|
||||
|
||||
@@ -254,9 +247,6 @@ export function WeightHistoryCard() {
|
||||
return (
|
||||
<TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}>
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.iconSquare}>
|
||||
<Image source={require('@/assets/images/icons/iconWeight.png')} style={{ width: 18, height: 18 }} />
|
||||
</View>
|
||||
<Text style={styles.cardTitle}>体重记录</Text>
|
||||
</View>
|
||||
|
||||
@@ -308,9 +298,6 @@ export function WeightHistoryCard() {
|
||||
return (
|
||||
<TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}>
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.iconSquare}>
|
||||
<Image source={require('@/assets/images/icons/iconWeight.png')} style={{ width: 18, height: 18 }} />
|
||||
</View>
|
||||
<Text style={styles.cardTitle}>体重记录</Text>
|
||||
<View style={styles.headerButtons}>
|
||||
<TouchableOpacity
|
||||
@@ -580,8 +567,8 @@ const styles = StyleSheet.create({
|
||||
card: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 22,
|
||||
padding: 18,
|
||||
marginBottom: 8,
|
||||
padding: 16,
|
||||
marginBottom: 4,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
@@ -591,7 +578,6 @@ const styles = StyleSheet.create({
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
iconSquare: {
|
||||
width: 30,
|
||||
@@ -603,7 +589,6 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
cardTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
color: '#192126',
|
||||
flex: 1,
|
||||
},
|
||||
@@ -675,9 +660,6 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
width: '100%',
|
||||
paddingTop: 16,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#F0F0F0',
|
||||
},
|
||||
infoItem: {
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -162,39 +162,11 @@ async function fetchStepCount(date: Date): Promise<number> {
|
||||
});
|
||||
}
|
||||
|
||||
// 获取指定日期每小时步数数据
|
||||
// 获取指定日期每小时步数数据 (已弃用,使用 fetchHourlyStepSamples 替代)
|
||||
// 保留此函数以防后向兼容需求
|
||||
async function fetchHourlyStepCount(date: Date): Promise<HourlyStepData[]> {
|
||||
return new Promise((resolve) => {
|
||||
const startOfDay = dayjs(date).startOf('day');
|
||||
const endOfDay = dayjs(date).endOf('day');
|
||||
|
||||
AppleHealthKit.getStepCount({
|
||||
startDate: startOfDay.toDate().toISOString(),
|
||||
endDate: endOfDay.toDate().toISOString(),
|
||||
includeManuallyAdded: false,
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
logError('每小时步数', err);
|
||||
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
|
||||
}
|
||||
|
||||
if (!res || !Array.isArray(res) || res.length === 0) {
|
||||
logWarning('每小时步数', '为空');
|
||||
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
|
||||
}
|
||||
|
||||
logSuccess('每小时步数', res);
|
||||
|
||||
// 初始化24小时数据
|
||||
const hourlyData: HourlyStepData[] = Array.from({ length: 24 }, (_, i) => ({
|
||||
hour: i,
|
||||
steps: 0
|
||||
}));
|
||||
|
||||
// 如果返回的是累计数据,我们需要获取样本数据
|
||||
resolve(hourlyData);
|
||||
});
|
||||
});
|
||||
// 直接调用更准确的样本数据获取函数
|
||||
return fetchHourlyStepSamples(date);
|
||||
}
|
||||
|
||||
// 使用样本数据获取每小时步数
|
||||
@@ -203,21 +175,22 @@ async function fetchHourlyStepSamples(date: Date): Promise<HourlyStepData[]> {
|
||||
const startOfDay = dayjs(date).startOf('day');
|
||||
const endOfDay = dayjs(date).endOf('day');
|
||||
|
||||
AppleHealthKit.getSamples(
|
||||
{
|
||||
// 使用正确的 getDailyStepCountSamples 方法,设置 period 为 60 分钟获取每小时数据
|
||||
const options = {
|
||||
startDate: startOfDay.toDate().toISOString(),
|
||||
endDate: endOfDay.toDate().toISOString(),
|
||||
type: 'StepCount',
|
||||
},
|
||||
ascending: false,
|
||||
period: 60, // 60分钟为一个时间段,获取每小时数据
|
||||
includeManuallyAdded: false,
|
||||
};
|
||||
|
||||
AppleHealthKit.getDailyStepCountSamples(
|
||||
options,
|
||||
(err: any, res: any[]) => {
|
||||
if (err) {
|
||||
logError('每小时步数样本', err);
|
||||
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
|
||||
}
|
||||
|
||||
if (!res || !Array.isArray(res) || res.length === 0) {
|
||||
logWarning('每小时步数样本', '为空');
|
||||
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
|
||||
// 如果主方法失败,尝试使用备用方法
|
||||
return null
|
||||
}
|
||||
|
||||
logSuccess('每小时步数样本', res);
|
||||
@@ -228,12 +201,17 @@ async function fetchHourlyStepSamples(date: Date): Promise<HourlyStepData[]> {
|
||||
steps: 0
|
||||
}));
|
||||
|
||||
// 将样本数据按小时分组并累加
|
||||
// 将每小时的步数样本数据映射到对应的小时
|
||||
res.forEach((sample: any) => {
|
||||
if (sample && sample.startDate && sample.value) {
|
||||
if (sample && sample.startDate && sample.value !== undefined) {
|
||||
const hour = dayjs(sample.startDate).hour();
|
||||
if (hour >= 0 && hour < 24) {
|
||||
hourlyData[hour].steps += Math.round(sample.value);
|
||||
// 使用样本中的步数值,如果有 metadata,优先使用 metadata 中的数据
|
||||
const stepValue = sample.metadata && sample.metadata.length > 0
|
||||
? sample.metadata.reduce((total: number, meta: any) => total + (meta.quantity || 0), 0)
|
||||
: sample.value;
|
||||
|
||||
hourlyData[hour].steps = Math.round(stepValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user