From 91b7b0cb9918cbe04455ba23191f0a19794ca677 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Mon, 25 Aug 2025 17:41:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E4=BB=A5=E4=BC=98=E5=8C=96=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=E5=92=8C=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 CoachScreen 中移除不必要的 router 引入,简化代码结构 - 在 PersonalScreen 中移除未使用的 colorScheme 引入,优化组件性能 - 更新 NutritionRadarCard 组件,新增卡路里计算功能,提升营养数据展示 - 修改 Statistics 组件,调整样式以增强视觉效果 - 移除 iOS 项目中的多余健康数据权限设置,简化配置 --- app/(tabs)/coach.tsx | 8 +- app/(tabs)/personal.tsx | 19 +- app/(tabs)/statistics.tsx | 12 +- components/NutritionRadarCard.tsx | 234 +++++++++++++++++- .../digitalpilates.entitlements | 4 - 5 files changed, 244 insertions(+), 33 deletions(-) diff --git a/app/(tabs)/coach.tsx b/app/(tabs)/coach.tsx index cfc4cf9..1bec07f 100644 --- a/app/(tabs)/coach.tsx +++ b/app/(tabs)/coach.tsx @@ -2,7 +2,7 @@ import { Ionicons } from '@expo/vector-icons'; import { BlurView } from 'expo-blur'; import * as Haptics from 'expo-haptics'; import * as ImagePicker from 'expo-image-picker'; -import { useLocalSearchParams, useRouter } from 'expo-router'; +import { useLocalSearchParams } from 'expo-router'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ActivityIndicator, @@ -115,10 +115,8 @@ const CardType = { type CardType = typeof CardType[keyof typeof CardType]; -// const COACH_AVATAR = require('@/assets/images/logo.png'); export default function CoachScreen() { - const router = useRouter(); const params = useLocalSearchParams<{ name?: string }>(); const insets = useSafeAreaInsets(); @@ -290,10 +288,10 @@ export default function CoachScreen() { if (Platform.OS === 'ios') { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); } - router.push('/mood/calendar'); + pushIfAuthedElseLogin('/mood/calendar'); } }, - ], [router, planDraft, checkin]); + ], [planDraft, checkin]); const scrollToEnd = useCallback(() => { requestAnimationFrame(() => { diff --git a/app/(tabs)/personal.tsx b/app/(tabs)/personal.tsx index 223b46b..a8dbf40 100644 --- a/app/(tabs)/personal.tsx +++ b/app/(tabs)/personal.tsx @@ -1,10 +1,8 @@ import ActivityHeatMap from '@/components/ActivityHeatMap'; import { PRIVACY_POLICY_URL, USER_AGREEMENT_URL } from '@/constants/Agree'; -import { Colors } from '@/constants/Colors'; import { getTabBarBottomPadding } from '@/constants/TabBar'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useAuthGuard } from '@/hooks/useAuthGuard'; -import { useColorScheme } from '@/hooks/useColorScheme'; import { useNotifications } from '@/hooks/useNotifications'; import { DEFAULT_MEMBER_NAME, fetchActivityHistory, fetchMyProfile } from '@/store/userSlice'; import { Ionicons } from '@expo/vector-icons'; @@ -21,16 +19,14 @@ export default function PersonalScreen() { const { confirmLogout, confirmDeleteAccount, isLoggedIn, pushIfAuthedElseLogin } = useAuthGuard(); const insets = useSafeAreaInsets(); const tabBarHeight = useBottomTabBarHeight(); - const colorScheme = useColorScheme(); - + // 推送通知相关 const { - isInitialized, permissionStatus, requestPermission, sendNotification, } = useNotifications(); - + const [notificationEnabled, setNotificationEnabled] = useState(false); // 计算底部间距 @@ -38,9 +34,6 @@ export default function PersonalScreen() { return getTabBarBottomPadding(tabBarHeight) + (insets?.bottom ?? 0); }, [tabBarHeight, insets?.bottom]); - // 颜色主题 - const colors = Colors[colorScheme ?? 'light']; - // 直接使用 Redux 中的用户信息,避免重复状态管理 const userProfile = useAppSelector((state) => state.user.profile); @@ -116,9 +109,9 @@ export default function PersonalScreen() { - @@ -183,7 +176,7 @@ export default function PersonalScreen() { {item.type === 'switch' ? ( {})} + onValueChange={item.onSwitchChange || (() => { })} trackColor={{ false: '#E5E5E5', true: '#9370DB' }} thumbColor="#FFFFFF" style={styles.switch} diff --git a/app/(tabs)/statistics.tsx b/app/(tabs)/statistics.tsx index 1d35b28..fb1e1f3 100644 --- a/app/(tabs)/statistics.tsx +++ b/app/(tabs)/statistics.tsx @@ -352,6 +352,14 @@ export default function ExploreScreen() { {/* 营养摄入雷达图卡片 */} { + console.log('选择餐次:', mealType); + // 这里可以导航到营养记录页面 + pushIfAuthedElseLogin('/nutrition/records'); + }} /> {/* 真正瀑布流布局 */} @@ -720,13 +728,11 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', marginBottom: 16, }, - healthMetricsContainer: { - marginBottom: 16, - }, masonryContainer: { marginBottom: 16, flexDirection: 'row', gap: 12, + marginTop: 16, }, masonryColumn: { flex: 1, diff --git a/components/NutritionRadarCard.tsx b/components/NutritionRadarCard.tsx index 4033d32..5dda4b8 100644 --- a/components/NutritionRadarCard.tsx +++ b/components/NutritionRadarCard.tsx @@ -1,5 +1,7 @@ +import { AnimatedNumber } from '@/components/AnimatedNumber'; import { ROUTES } from '@/constants/Routes'; import { NutritionSummary } from '@/services/dietRecords'; +import { Ionicons } from '@expo/vector-icons'; import Feather from '@expo/vector-icons/Feather'; import dayjs from 'dayjs'; import { router } from 'expo-router'; @@ -9,6 +11,14 @@ import { RadarCategory, RadarChart } from './RadarChart'; export type NutritionRadarCardProps = { nutritionSummary: NutritionSummary | null; + /** 基础代谢消耗的卡路里 */ + burnedCalories?: number; + /** 卡路里缺口 */ + calorieDeficit?: number; + /** 动画重置令牌 */ + resetToken?: number; + /** 餐次点击回调 */ + onMealPress?: (mealType: 'breakfast' | 'lunch' | 'dinner' | 'snack') => void; }; // 营养维度定义 @@ -21,7 +31,13 @@ const NUTRITION_DIMENSIONS: RadarCategory[] = [ { key: 'sodium', label: '钠' }, ]; -export function NutritionRadarCard({ nutritionSummary }: NutritionRadarCardProps) { +export function NutritionRadarCard({ + nutritionSummary, + burnedCalories = 1618, + calorieDeficit = 0, + resetToken, + onMealPress +}: NutritionRadarCardProps) { const radarValues = useMemo(() => { // 基于推荐日摄入量计算分数 const recommendations = { @@ -35,13 +51,21 @@ export function NutritionRadarCard({ nutritionSummary }: NutritionRadarCardProps if (!nutritionSummary) return [0, 0, 0, 0, 0, 0]; + // 检查每个营养素是否有实际值,没有则返回0 + const calories = nutritionSummary.totalCalories || 0; + const protein = nutritionSummary.totalProtein || 0; + const carbohydrate = nutritionSummary.totalCarbohydrate || 0; + const fat = nutritionSummary.totalFat || 0; + const fiber = nutritionSummary.totalFiber || 0; + const sodium = nutritionSummary.totalSodium || 0; + return [ - Math.min(5, (nutritionSummary.totalCalories / recommendations.calories) * 5), - Math.min(5, (nutritionSummary.totalProtein / recommendations.protein) * 5), - Math.min(5, (nutritionSummary.totalCarbohydrate / recommendations.carbohydrate) * 5), - Math.min(5, (nutritionSummary.totalFat / recommendations.fat) * 5), - Math.min(5, (nutritionSummary.totalFiber / recommendations.fiber) * 5), - Math.min(5, Math.max(0, 5 - (nutritionSummary.totalSodium / recommendations.sodium) * 5)), // 钠含量越低越好 + calories > 0 ? Math.min(5, (calories / recommendations.calories) * 5) : 0, + protein > 0 ? Math.min(5, (protein / recommendations.protein) * 5) : 0, + carbohydrate > 0 ? Math.min(5, (carbohydrate / recommendations.carbohydrate) * 5) : 0, + fat > 0 ? Math.min(5, (fat / recommendations.fat) * 5) : 0, + fiber > 0 ? Math.min(5, (fiber / recommendations.fiber) * 5) : 0, + sodium > 0 ? Math.min(5, Math.max(0, 5 - (sodium / recommendations.sodium) * 5)) : 0, // 钠含量越低越好 ]; }, [nutritionSummary]); @@ -56,6 +80,34 @@ export function NutritionRadarCard({ nutritionSummary }: NutritionRadarCardProps ]; }, [nutritionSummary]); + // 计算还能吃的卡路里 + const consumedCalories = nutritionSummary?.totalCalories || 0; + const remainingCalories = burnedCalories - consumedCalories - calorieDeficit; + + // 餐次数据 + const meals = [ + { + type: 'breakfast' as const, + name: '早餐', + emoji: '🥚', + }, + { + type: 'lunch' as const, + name: '午餐', + emoji: '🍔', + }, + { + type: 'dinner' as const, + name: '晚餐', + emoji: '🥣', + }, + { + type: 'snack' as const, + name: '加餐', + emoji: '🍎', + }, + ]; + const handleNavigateToRecords = () => { router.push(ROUTES.NUTRITION_RECORDS); }; @@ -90,6 +142,74 @@ export function NutritionRadarCard({ nutritionSummary }: NutritionRadarCardProps ))} + + {/* 卡路里计算区域 */} + + + 还能吃(千卡) + + Math.round(v).toString()} + /> + = + + + 消耗 + + Math.round(v).toString()} + /> + - + + + 饮食 + + Math.round(v).toString()} + /> + - + + + 缺口 + + Math.round(v).toString()} + /> + + + + {/* 餐次选择区域 */} + {/* + {meals.map((meal) => ( + onMealPress?.(meal.type)} + activeOpacity={0.7} + > + + {meal.emoji} + + + + + {meal.name} + + ))} + */} + ); } @@ -99,7 +219,6 @@ const styles = StyleSheet.create({ backgroundColor: '#FFFFFF', borderRadius: 22, padding: 18, - marginBottom: 16, shadowColor: '#000', shadowOffset: { width: 0, @@ -168,4 +287,103 @@ const styles = StyleSheet.create({ color: '#192126', fontWeight: '700', }, + // 卡路里相关样式 + calorieSection: { + marginTop: 12, + }, + + calorieTitleContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + calorieIcon: { + fontSize: 16, + marginRight: 8, + }, + calorieTitle: { + fontSize: 16, + fontWeight: '800', + color: '#192126', + }, + calorieContent: { + }, + calorieSubtitle: { + fontSize: 10, + color: '#64748B', + marginBottom: 8, + fontWeight: '600', + }, + calculationRow: { + flexDirection: 'row', + alignItems: 'center', + flexWrap: 'wrap', + gap: 4, + }, + mainValue: { + fontSize: 24, + fontWeight: '800', + color: '#192126', + }, + calculationText: { + fontSize: 14, + fontWeight: '600', + color: '#64748B', + }, + calculationItem: { + flexDirection: 'row', + alignItems: 'center', + gap: 2, + }, + calculationLabel: { + fontSize: 10, + color: '#64748B', + fontWeight: '500', + }, + calculationValue: { + fontSize: 12, + fontWeight: '700', + color: '#192126', + }, + mealsContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingTop: 12, + borderTopWidth: 1, + borderTopColor: '#F1F5F9', + }, + mealItem: { + alignItems: 'center', + flex: 1, + }, + mealIconContainer: { + position: 'relative', + marginBottom: 6, + }, + mealEmoji: { + fontSize: 24, + }, + addButton: { + position: 'absolute', + top: -2, + right: -2, + width: 16, + height: 16, + borderRadius: 8, + backgroundColor: '#10B981', + alignItems: 'center', + justifyContent: 'center', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, + mealName: { + fontSize: 10, + color: '#64748B', + fontWeight: '600', + }, }); diff --git a/ios/digitalpilates/digitalpilates.entitlements b/ios/digitalpilates/digitalpilates.entitlements index 3ab3686..f31d5d9 100644 --- a/ios/digitalpilates/digitalpilates.entitlements +++ b/ios/digitalpilates/digitalpilates.entitlements @@ -10,10 +10,6 @@ com.apple.developer.healthkit - com.apple.developer.healthkit.access - - health-records - com.apple.developer.healthkit.background-delivery