diff --git a/app/(tabs)/coach.tsx b/app/(tabs)/coach.tsx index 28fa6f0..e091f1f 100644 --- a/app/(tabs)/coach.tsx +++ b/app/(tabs)/coach.tsx @@ -17,7 +17,7 @@ import { Text, TextInput, TouchableOpacity, - View, + View } from 'react-native'; import Markdown from 'react-native-markdown-display'; import Animated, { FadeInDown, FadeInUp, Layout } from 'react-native-reanimated'; @@ -32,6 +32,7 @@ import { deleteConversation, getConversationDetail, listConversations, type AiCo import { loadAiCoachSessionCache, saveAiCoachSessionCache } from '@/services/aiCoachSession'; import { api, getAuthToken, postTextStream } from '@/services/api'; import dayjs from 'dayjs'; +import { LinearGradient } from 'expo-linear-gradient'; import { ActionSheet } from '../../components/ui/ActionSheet'; type Role = 'user' | 'assistant'; @@ -1121,7 +1122,7 @@ export default function CoachScreen() { style={[ styles.bubble, { - backgroundColor: isUser ? theme.primary : 'rgba(187,242,70,0.16)', + backgroundColor: theme.card, // 16% opacity borderTopLeftRadius: isUser ? 16 : 6, borderTopRightRadius: isUser ? 6 : 16, maxWidth: isUser ? '82%' : '90%', @@ -1347,7 +1348,7 @@ export default function CoachScreen() { )} {isSelected && isPending && ( - + )} {isSelected && !isPending && ( @@ -1594,7 +1595,13 @@ export default function CoachScreen() { } return ( - + + {/* 顶部标题区域,显示教练名称、新建会话和历史按钮 */} @@ -1680,7 +1687,7 @@ export default function CoachScreen() { contentContainerStyle={{ paddingHorizontal: 6, gap: 8 }} > {chips.map((c) => ( - + {c.label} ))} @@ -1722,11 +1729,11 @@ export default function CoachScreen() { )} - + @@ -1936,7 +1943,7 @@ const styles = StyleSheet.create({ borderRadius: 8, alignItems: 'center', justifyContent: 'center', - backgroundColor: 'rgba(187,242,70,0.6)' + backgroundColor: `${Colors.light.accentGreen}99` // 60% opacity }, dietOptionsContainer: { gap: 8, @@ -1948,13 +1955,13 @@ const styles = StyleSheet.create({ borderRadius: 12, backgroundColor: 'rgba(255,255,255,0.9)', borderWidth: 1, - borderColor: 'rgba(187,242,70,0.3)', + borderColor: `${Colors.light.accentGreen}4D`, // 30% opacity }, dietOptionIconContainer: { width: 40, height: 40, borderRadius: 20, - backgroundColor: 'rgba(187,242,70,0.2)', + backgroundColor: `${Colors.light.accentGreen}33`, // 20% opacity alignItems: 'center', justifyContent: 'center', marginRight: 12, @@ -2003,7 +2010,7 @@ const styles = StyleSheet.create({ borderRadius: 10, alignItems: 'center', justifyContent: 'center', - backgroundColor: 'rgba(187,242,70,0.6)', + backgroundColor: `${Colors.light.accentGreen}99`, // 60% opacity alignSelf: 'flex-end', }, // markdown 基础样式承载容器的字体尺寸保持与气泡一致 @@ -2316,17 +2323,17 @@ const styles = StyleSheet.create({ choiceButton: { backgroundColor: 'rgba(255,255,255,0.9)', borderWidth: 1, - borderColor: 'rgba(187,242,70,0.3)', + borderColor: `${Colors.light.accentGreen}4D`, // 30% opacity borderRadius: 12, padding: 12, }, choiceButtonRecommended: { - borderColor: 'rgba(187,242,70,0.6)', - backgroundColor: 'rgba(187,242,70,0.1)', + borderColor: `${Colors.light.accentGreen}99`, // 60% opacity + backgroundColor: `${Colors.light.accentGreen}1A`, // 10% opacity }, choiceButtonSelected: { - borderColor: '#2D5016', - backgroundColor: 'rgba(187,242,70,0.2)', + borderColor: Colors.light.accentGreenDark, + backgroundColor: `${Colors.light.accentGreen}33`, // 20% opacity borderWidth: 2, }, choiceButtonDisabled: { @@ -2346,10 +2353,10 @@ const styles = StyleSheet.create({ flex: 1, }, choiceLabelRecommended: { - color: '#2D5016', + color: Colors.light.accentGreenDark, }, choiceLabelSelected: { - color: '#2D5016', + color: Colors.light.accentGreenDark, fontWeight: '700', }, choiceLabelDisabled: { @@ -2361,7 +2368,7 @@ const styles = StyleSheet.create({ gap: 8, }, recommendedBadge: { - backgroundColor: 'rgba(187,242,70,0.8)', + backgroundColor: `${Colors.light.accentGreen}CC`, // 80% opacity borderRadius: 6, paddingHorizontal: 8, paddingVertical: 2, @@ -2369,10 +2376,10 @@ const styles = StyleSheet.create({ recommendedText: { fontSize: 12, fontWeight: '700', - color: '#2D5016', + color: Colors.light.accentGreenDark, }, selectedBadge: { - backgroundColor: '#2D5016', + backgroundColor: Colors.light.accentGreenDark, borderRadius: 6, paddingHorizontal: 8, paddingVertical: 2, @@ -2405,6 +2412,13 @@ const styles = StyleSheet.create({ fontWeight: '600', color: '#FF4444', }, + gradientBackground: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + }, }); const markdownStyles = { diff --git a/app/(tabs)/personal.tsx b/app/(tabs)/personal.tsx index 4594b75..47ec55c 100644 --- a/app/(tabs)/personal.tsx +++ b/app/(tabs)/personal.tsx @@ -9,6 +9,7 @@ import { Ionicons } from '@expo/vector-icons'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; import { useFocusEffect } from '@react-navigation/native'; +import { LinearGradient } from 'expo-linear-gradient'; import React, { useMemo, useState } from 'react'; import { Alert, Image, Linking, SafeAreaView, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -287,11 +288,17 @@ export default function PersonalScreen() { ]; return ( - + + - + @@ -311,6 +318,13 @@ const styles = StyleSheet.create({ container: { flex: 1, }, + gradientBackground: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + }, safeArea: { flex: 1, }, diff --git a/app/(tabs)/statistics.tsx b/app/(tabs)/statistics.tsx index 0826e56..0b8f93d 100644 --- a/app/(tabs)/statistics.tsx +++ b/app/(tabs)/statistics.tsx @@ -3,6 +3,7 @@ import { BMICard } from '@/components/BMICard'; import { CircularRing } from '@/components/CircularRing'; import { NutritionRadarCard } from '@/components/NutritionRadarCard'; import { ProgressBar } from '@/components/ProgressBar'; +import { StressAnalysisModal } from '@/components/StressAnalysisModal'; import { StressMeter } from '@/components/StressMeter'; import { WeightHistoryCard } from '@/components/WeightHistoryCard'; import { Colors } from '@/constants/Colors'; @@ -17,6 +18,7 @@ import { Ionicons } from '@expo/vector-icons'; import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; import { useFocusEffect } from '@react-navigation/native'; import dayjs from 'dayjs'; +import { LinearGradient } from 'expo-linear-gradient'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { SafeAreaView, @@ -85,6 +87,9 @@ export default function ExploreScreen() { const [nutritionSummary, setNutritionSummary] = useState(null); const [isNutritionLoading, setIsNutritionLoading] = useState(false); + // 压力分析浮窗状态 + const [showStressModal, setShowStressModal] = useState(false); + // 记录最近一次请求的“日期键”,避免旧请求覆盖新结果 const latestRequestKeyRef = useRef(null); @@ -110,7 +115,7 @@ export default function ExploreScreen() { } else { derivedDate = days[selectedIndex]?.date?.toDate() ?? new Date(); } - + const requestKey = getDateKey(derivedDate); latestRequestKeyRef.current = requestKey; @@ -204,10 +209,28 @@ export default function ExploreScreen() { } }; + // 处理压力卡片点击 + const handleStressCardPress = () => { + setShowStressModal(true); + }; + + // 关闭压力分析浮窗 + const handleCloseStressModal = () => { + setShowStressModal(false); + }; + + // 使用统一的渐变背景色 + const backgroundGradientColors = [colorTokens.backgroundGradientStart, colorTokens.backgroundGradientEnd] as const; return ( - - + + + {/* 左列 */} - - + 消耗卡路里 {activeCalories != null ? ( @@ -306,20 +330,20 @@ export default function ExploreScreen() { weight={userProfile?.weight ? parseFloat(userProfile.weight) : undefined} height={userProfile?.height ? parseFloat(userProfile.height) : undefined} style={styles.masonryCardNoBg} - compact={true} + // compact={true} /> 训练时间 - + @@ -347,16 +371,32 @@ export default function ExploreScreen() { + + {/* 压力分析浮窗 */} + ); } const primary = Colors.light.primary; +const lightColors = Colors.light; +const darkColors = Colors.dark; const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#F6F7F8', + }, + gradientBackground: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, }, safeArea: { flex: 1, @@ -388,10 +428,10 @@ const styles = StyleSheet.create({ justifyContent: 'center', }, dayPillNormal: { - backgroundColor: '#C8F852', + backgroundColor: lightColors.datePickerNormal, }, dayPillSelected: { - backgroundColor: '#192126', + backgroundColor: lightColors.datePickerSelected, }, dayLabel: { fontSize: 16, @@ -414,7 +454,7 @@ const styles = StyleSheet.create({ width: 8, height: 8, borderRadius: 4, - backgroundColor: '#192126', + backgroundColor: lightColors.datePickerSelected, marginTop: 10, marginBottom: 4, alignSelf: 'center', diff --git a/app/ai-posture-assessment.tsx b/app/ai-posture-assessment.tsx index d39e368..10f801c 100644 --- a/app/ai-posture-assessment.tsx +++ b/app/ai-posture-assessment.tsx @@ -341,7 +341,7 @@ function UploadTile({ > {uploading ? ( - + 上传中... ) : value ? ( @@ -349,7 +349,7 @@ function UploadTile({ ) : ( - + 拍摄或选择照片 点击拍摄,长按从相册选择 @@ -415,7 +415,7 @@ const styles = StyleSheet.create({ paddingHorizontal: 14, height: 40, borderRadius: 12, - backgroundColor: '#BBF246', + backgroundColor: Colors.light.accentGreen, }, permPrimaryText: { color: '#192126', @@ -518,7 +518,7 @@ const styles = StyleSheet.create({ justifyContent: 'center', backgroundColor: '#FFFFFF', borderWidth: 2, - borderColor: '#BBF246', + borderColor: Colors.light.accentGreen, }, placeholderTitle: { color: '#192126', diff --git a/app/ai-posture-processing.tsx b/app/ai-posture-processing.tsx index fd25c39..661d905 100644 --- a/app/ai-posture-processing.tsx +++ b/app/ai-posture-processing.tsx @@ -166,7 +166,7 @@ const styles = StyleSheet.create({ borderRadius: INNER_RING_SIZE / 2, borderWidth: 2, borderColor: 'rgba(187,242,70,0.65)', - shadowColor: '#BBF246', + shadowColor: Colors.light.accentGreen, shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0.35, shadowRadius: 24, @@ -207,8 +207,8 @@ const styles = StyleSheet.create({ width: 14, height: 14, borderRadius: 7, - backgroundColor: '#BBF246', - shadowColor: '#BBF246', + backgroundColor: Colors.light.accentGreen, + shadowColor: Colors.light.accentGreen, shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0.4, shadowRadius: 16, diff --git a/app/challenge/day.tsx b/app/challenge/day.tsx index 7a672d6..ad1df71 100644 --- a/app/challenge/day.tsx +++ b/app/challenge/day.tsx @@ -1,4 +1,5 @@ import { HeaderBar } from '@/components/ui/HeaderBar'; +import { Colors } from '@/constants/Colors'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { completeDay, setCustom } from '@/store/challengeSlice'; import type { Exercise, ExerciseCustomConfig } from '@/utils/pilatesPlan'; @@ -158,7 +159,7 @@ const styles = StyleSheet.create({ counterValue: { minWidth: 40, textAlign: 'center', fontWeight: '700', color: '#111827' }, setPill: { paddingHorizontal: 10, paddingVertical: 6, borderRadius: 999 }, setPillTodo: { backgroundColor: '#F3F4F6' }, - setPillDone: { backgroundColor: '#BBF246' }, + setPillDone: { backgroundColor: Colors.light.accentGreen }, setPillText: { fontSize: 12, fontWeight: '700' }, setPillTextTodo: { color: '#6B7280' }, setPillTextDone: { color: '#192126' }, @@ -167,7 +168,7 @@ const styles = StyleSheet.create({ tipsBox: { marginTop: 10, backgroundColor: '#F9FAFB', borderRadius: 8, padding: 10 }, tipText: { fontSize: 12, color: '#6B7280', lineHeight: 18 }, bottomBar: { position: 'absolute', left: 0, right: 0, bottom: 0, padding: 20, backgroundColor: 'transparent' }, - finishBtn: { backgroundColor: '#BBF246', paddingVertical: 14, borderRadius: 999, alignItems: 'center' }, + finishBtn: { backgroundColor: Colors.light.accentGreen, paddingVertical: 14, borderRadius: 999, alignItems: 'center' }, finishBtnText: { color: '#192126', fontWeight: '800', fontSize: 16 }, }); diff --git a/app/challenge/index.tsx b/app/challenge/index.tsx index 6599cd5..5cf0a0c 100644 --- a/app/challenge/index.tsx +++ b/app/challenge/index.tsx @@ -1,4 +1,5 @@ import { HeaderBar } from '@/components/ui/HeaderBar'; +import { Colors } from '@/constants/Colors'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useAuthGuard } from '@/hooks/useAuthGuard'; import { initChallenge } from '@/store/challengeSlice'; @@ -113,7 +114,7 @@ const styles = StyleSheet.create({ }, summaryLeft: { flexDirection: 'row', alignItems: 'center' }, progressPill: { width: 120, height: 10, borderRadius: 999, backgroundColor: '#E5E7EB', overflow: 'hidden' }, - progressFill: { height: '100%', backgroundColor: '#BBF246' }, + progressFill: { height: '100%', backgroundColor: Colors.light.accentGreen }, progressText: { marginLeft: 12, fontWeight: '700', color: '#111827' }, summaryRight: {}, summaryItem: { fontSize: 12, color: '#6B7280' }, @@ -133,7 +134,7 @@ const styles = StyleSheet.create({ dayNumberLocked: { color: '#9CA3AF' }, dayMinutes: { marginTop: 4, fontSize: 12, color: '#6B7280' }, bottomBar: { padding: 20 }, - startButton: { backgroundColor: '#BBF246', paddingVertical: 14, borderRadius: 999, alignItems: 'center' }, + startButton: { backgroundColor: Colors.light.accentGreen, paddingVertical: 14, borderRadius: 999, alignItems: 'center' }, startButtonText: { color: '#192126', fontWeight: '800', fontSize: 16 }, }); diff --git a/app/training-plan.tsx b/app/training-plan.tsx index b34c968..7a844db 100644 --- a/app/training-plan.tsx +++ b/app/training-plan.tsx @@ -178,7 +178,7 @@ function PlanCard({ plan, onPress, onDelete, onActivate, onSchedule, isActive, i - {isActive ? '已激活' : '激活'} + {isActive ? '已激活' : '激活'} { Alert.alert('确认删除', '确定要删除这个训练计划吗?此操作无法撤销。', [ diff --git a/assets/images/Sealife@2x.jpeg b/assets/images/Sealife@2x.jpeg new file mode 100644 index 0000000..46132b1 Binary files /dev/null and b/assets/images/Sealife@2x.jpeg differ diff --git a/components/BMICard.tsx b/components/BMICard.tsx index 980ce05..37aec24 100644 --- a/components/BMICard.tsx +++ b/components/BMICard.tsx @@ -1,20 +1,21 @@ +import { Colors } from '@/constants/Colors'; import { useAuthGuard } from '@/hooks/useAuthGuard'; import { - BMI_CATEGORIES, - canCalculateBMI, - getBMIResult, - type BMIResult + BMI_CATEGORIES, + canCalculateBMI, + getBMIResult, + type BMIResult } from '@/utils/bmi'; import { Ionicons } from '@expo/vector-icons'; import React, { useState } from 'react'; import { - Dimensions, - Modal, - Pressable, - StyleSheet, - Text, - TouchableOpacity, - View + Dimensions, + Modal, + Pressable, + StyleSheet, + Text, + TouchableOpacity, + View } from 'react-native'; import Toast from 'react-native-toast-message'; @@ -62,7 +63,7 @@ export function BMICard({ weight, height, style, compact = false }: BMICardProps if (!canCalculate) { // 缺少数据的情况 return ( - @@ -193,7 +194,7 @@ export function BMICard({ weight, height, style, compact = false }: BMICardProps style={styles.modalBackdrop} onPress={handleHideInfoModal} > - e.stopPropagation()} > @@ -234,7 +235,7 @@ export function BMICard({ weight, height, style, compact = false }: BMICardProps {BMI_CATEGORIES.map((category, index) => { const colors = index === 0 ? { bg: '#FEF3C7', text: '#B45309' } : - index === 1 ? { bg: '#E8F5E8', text: '#2D5016' } : + index === 1 ? { bg: '#E8F5E8', text: Colors.light.accentGreenDark } : index === 2 ? { bg: '#FEF3C7', text: '#B45309' } : { bg: '#FEE2E2', text: '#B91C1C' }; @@ -289,6 +290,7 @@ const styles = StyleSheet.create({ padding: 18, marginBottom: 16, overflow: 'hidden', + backgroundColor: '#FFFFFF', }, cardHeader: { flexDirection: 'row', @@ -320,7 +322,7 @@ const styles = StyleSheet.create({ // 缺少数据时的样式 incompleteContent: { - minHeight: 120, + minHeight: 80, backgroundColor: '#FFFFFF', borderRadius: 22, padding: 18, @@ -345,7 +347,7 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - backgroundColor: '#F3F4F6', + backgroundColor: '#ffffff', borderRadius: 12, paddingVertical: 12, paddingHorizontal: 16, @@ -359,7 +361,7 @@ const styles = StyleSheet.create({ // 有完整数据时的样式 completeContent: { - minHeight: 120, + minHeight: 80, borderRadius: 22, padding: 18, margin: -18, @@ -398,12 +400,12 @@ const styles = StyleSheet.create({ // 紧凑模式样式 compactIncompleteContent: { - minHeight: 110, + minHeight: 80, padding: 14, margin: -14, }, compactCompleteContent: { - minHeight: 110, + minHeight: 80, padding: 14, margin: -14, }, @@ -427,7 +429,7 @@ const styles = StyleSheet.create({ flex: 1, }, compactBMIValue: { - fontSize: 28, + fontSize: 24, fontWeight: '800', marginBottom: 4, }, @@ -491,13 +493,13 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, - + // 内容区域样式 modalContent: { paddingHorizontal: 20, paddingBottom: 24, }, - + // 介绍部分 introSection: { marginBottom: 20, @@ -522,7 +524,7 @@ const styles = StyleSheet.create({ fontWeight: '600', textAlign: 'center', }, - + // 分类部分 categoriesSection: { marginBottom: 18, @@ -567,7 +569,7 @@ const styles = StyleSheet.create({ fontWeight: '500', opacity: 0.9, }, - + // 健康提示 healthTips: { backgroundColor: '#F9FAFB', @@ -593,7 +595,7 @@ const styles = StyleSheet.create({ color: '#374151', lineHeight: 18, }, - + // 免责声明紧凑版 disclaimerCompact: { flexDirection: 'row', diff --git a/components/ProgressBar.tsx b/components/ProgressBar.tsx index a3ea27e..e11e7cf 100644 --- a/components/ProgressBar.tsx +++ b/components/ProgressBar.tsx @@ -1,3 +1,4 @@ +import { Colors } from '@/constants/Colors'; import React, { memo, useEffect, useMemo, useRef, useState } from 'react'; import { Animated, Easing, LayoutChangeEvent, StyleSheet, Text, View, ViewStyle } from 'react-native'; @@ -16,7 +17,7 @@ function ProgressBarImpl({ height = 18, style, trackColor = '#EDEDED', - fillColor = '#BBF246', + fillColor = Colors.light.accentGreen, animated = true, showLabel = true, }: ProgressBarProps) { diff --git a/components/RadarChart.tsx b/components/RadarChart.tsx index 4eb435f..6dd7a3d 100644 --- a/components/RadarChart.tsx +++ b/components/RadarChart.tsx @@ -1,3 +1,4 @@ +import { Colors } from '@/constants/Colors'; import React, { useMemo } from 'react'; import { StyleSheet, View } from 'react-native'; import Svg, * as SvgLib from 'react-native-svg'; @@ -102,7 +103,7 @@ export function RadarChart({ - + diff --git a/components/StressAnalysisModal.tsx b/components/StressAnalysisModal.tsx new file mode 100644 index 0000000..0545e93 --- /dev/null +++ b/components/StressAnalysisModal.tsx @@ -0,0 +1,267 @@ +import { Colors } from '@/constants/Colors'; +import { useColorScheme } from '@/hooks/useColorScheme'; +import { LinearGradient } from 'expo-linear-gradient'; +import React from 'react'; +import { + Modal, + ScrollView, + StyleSheet, + Text, + TouchableOpacity, + View +} from 'react-native'; + +interface StressAnalysisModalProps { + visible: boolean; + onClose: () => void; + hrvValue: number; + updateTime: Date; +} + +export function StressAnalysisModal({ visible, onClose, hrvValue, updateTime }: StressAnalysisModalProps) { + const colorScheme = useColorScheme(); + const colors = Colors[colorScheme ?? 'light']; + + // 模拟30天HRV数据 + const hrvData = { + goodEvents: { percentage: 26, count: 53, range: '>80毫秒' }, + energetic: { percentage: 47, count: 97, range: '43-80毫秒' }, + stressed: { percentage: 27, count: 56, range: '<43毫秒' }, + }; + + + + return ( + + + + {/* 标题 */} + 压力情况分析 + + + {/* 最近30天HRV情况 */} + 最近30天HRV情况 + + {/* 彩色横条图 */} + + + + + + + + 好事发生 + + + + 活力满满 + + + + 鸭梨山大 + + + + + {/* 数据统计卡片 */} + + {/* 好事发生 & 活力满满 */} + + + 好事发生 + {hrvData.goodEvents.percentage}% + + ❤️ {hrvData.goodEvents.range} + + {hrvData.goodEvents.count}次 + + + + 活力满满 + {hrvData.energetic.percentage}% + + ❤️ {hrvData.energetic.range} + + {hrvData.energetic.count}次 + + + + {/* 鸭梨山大 */} + + 鸭梨山大 + {hrvData.stressed.percentage}% + + ❤️ {hrvData.stressed.range} + + {hrvData.stressed.count}次 + + + + + {/* 底部继续按钮 */} + + + + 继续 + + + + + + + ); +} + +const styles = StyleSheet.create({ + modalContainer: { + flex: 1, + }, + content: { + flex: 1, + paddingHorizontal: 20, + }, + title: { + fontSize: 24, + fontWeight: '800', + color: '#111827', + textAlign: 'center', + marginTop: 20, + marginBottom: 32, + }, + + sectionTitle: { + fontSize: 22, + fontWeight: '800', + color: '#111827', + marginBottom: 20, + }, + chartContainer: { + marginBottom: 32, + }, + colorBar: { + height: 16, + borderRadius: 8, + overflow: 'hidden', + marginBottom: 16, + }, + gradientBar: { + flex: 1, + }, + legend: { + flexDirection: 'row', + justifyContent: 'space-around', + }, + legendItem: { + flexDirection: 'row', + alignItems: 'center', + }, + legendDot: { + width: 12, + height: 12, + borderRadius: 6, + marginRight: 6, + }, + legendText: { + fontSize: 14, + fontWeight: '600', + color: '#374151', + }, + statsCard: { + backgroundColor: '#FFFFFF', + borderRadius: 16, + padding: 20, + marginBottom: 32, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.05, + shadowRadius: 8, + elevation: 2, + }, + statsRow: { + flexDirection: 'row', + gap: 20, + marginBottom: 24, + }, + statItem: { + flex: 1, + }, + statTitle: { + fontSize: 16, + fontWeight: '700', + marginBottom: 8, + }, + statPercentage: { + fontSize: 36, + fontWeight: '800', + color: '#111827', + marginBottom: 4, + }, + statDetails: { + marginBottom: 4, + }, + statRange: { + fontSize: 14, + fontWeight: '600', + color: '#DC2626', + backgroundColor: '#FEE2E2', + paddingHorizontal: 8, + paddingVertical: 3, + borderRadius: 10, + alignSelf: 'flex-start', + }, + statCount: { + fontSize: 16, + fontWeight: '600', + color: '#6B7280', + }, + bottomContainer: { + paddingHorizontal: 20, + paddingBottom: 34, + }, + continueButton: { + borderRadius: 25, + overflow: 'hidden', + marginBottom: 8, + }, + buttonGradient: { + paddingVertical: 18, + alignItems: 'center', + justifyContent: 'center', + }, + buttonBackground: { + backgroundColor: Colors.light.accentGreen, // 应用主色调 + paddingVertical: 18, + alignItems: 'center', + justifyContent: 'center', + }, + buttonText: { + fontSize: 18, + fontWeight: '700', + color: '#192126', // 主色调上的文字颜色 + }, + homeIndicator: { + width: 134, + height: 5, + backgroundColor: '#000', + borderRadius: 3, + alignSelf: 'center', + }, +}); \ No newline at end of file diff --git a/components/StressMeter.tsx b/components/StressMeter.tsx index b4e7541..5c8cbed 100644 --- a/components/StressMeter.tsx +++ b/components/StressMeter.tsx @@ -1,25 +1,27 @@ import { Ionicons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; interface StressMeterProps { - value: number; + value: number | null; updateTime?: Date; style?: any; + onPress?: () => void; } -export function StressMeter({ value, updateTime, style }: StressMeterProps) { - - // 计算进度条位置(0-100%) - // HRV值范围:30-110ms,对应进度条0-100% - const progressPercentage = Math.min(100, Math.max(0, ((value - 30) / 80) * 100)); - - // 根据HRV值计算状态 - const getHrvStatus = () => { - if (value >= 70) { +export function StressMeter({ value, updateTime, style, onPress }: StressMeterProps) { + // 将HRV值转换为压力指数(0-100) + // HRV值范围:30-110ms,映射到压力指数100-0 + // HRV值越高,压力越小;HRV值越低,压力越大 + const stressIndex = value ? Math.round(Math.min(100, Math.max(0, 100 - ((value - 30) / 80) * 100))) : null; + // 根据压力指数计算状态 + const getStressStatus = () => { + if (stressIndex === null) { + return '未知'; + } else if (stressIndex <= 30) { return '放松'; - } else if (value >= 50) { + } else if (stressIndex <= 70) { return '正常'; } else { return '紧张'; @@ -28,13 +30,14 @@ export function StressMeter({ value, updateTime, style }: StressMeterProps) { // 根据状态获取表情 const getStatusEmoji = () => { - // 当HRV值为0时,不展示表情 - if (value === 0) { + // 当HRV值为null或0时,不展示表情 + if (value === null || value === 0) { return ''; } - - const status = getHrvStatus(); + + const status = getStressStatus(); switch (status) { + case '未知': return ''; case '放松': return '😌'; case '正常': return '😊'; case '紧张': return '😰'; @@ -42,24 +45,18 @@ export function StressMeter({ value, updateTime, style }: StressMeterProps) { } }; - // 格式化更新时间 - const formatUpdateTime = (date?: Date) => { - if (!date) return ''; - const hours = date.getHours().toString().padStart(2, '0'); - const minutes = date.getMinutes().toString().padStart(2, '0'); - return `${hours}:${minutes}`; - }; + // 计算进度条位置(0-100%) + // 压力指数越高,进度条越满 + const progressPercentage = stressIndex === null ? 0 : stressIndex; + return ( - - {/* 渐变背景 */} - - + + {/* 头部区域 */} @@ -73,30 +70,32 @@ export function StressMeter({ value, updateTime, style }: StressMeterProps) { {/* 数值显示区域 */} - {value} - 毫秒 + {stressIndex === null ? '--' : stressIndex} + 指数 {/* 进度条区域 */} {/* 渐变背景进度条 */} - + + + {/* 白色圆形指示器 */} - {/* 更新时间 */} + {/* 更新时间 {updateTime && ( {formatUpdateTime(updateTime)} - )} - + )} */} + ); } @@ -181,7 +180,12 @@ const styles = StyleSheet.create({ position: 'relative', overflow: 'visible', }, - gradientTrack: { + progressBar: { + height: '100%', + borderRadius: 4, + overflow: 'hidden', + }, + gradientBar: { height: '100%', borderRadius: 4, }, diff --git a/components/WeightHistoryCard.tsx b/components/WeightHistoryCard.tsx index d4b79d6..834e3d7 100644 --- a/components/WeightHistoryCard.tsx +++ b/components/WeightHistoryCard.tsx @@ -1,3 +1,4 @@ +import { Colors } from '@/constants/Colors'; import { ROUTES } from '@/constants/Routes'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useAuthGuard } from '@/hooks/useAuthGuard'; @@ -20,11 +21,6 @@ const CHART_WIDTH = CARD_WIDTH - 36; // 减去卡片内边距 const CHART_HEIGHT = 100; const PADDING = 10; -type WeightHistoryItem = { - weight: string; - source: string; - createdAt: string; -}; export function WeightHistoryCard() { const dispatch = useAppDispatch(); @@ -177,7 +173,7 @@ export function WeightHistoryCard() { - + @@ -209,14 +205,6 @@ export function WeightHistoryCard() { - setShowChart(true)} - activeOpacity={0.8} - > - - 查看趋势图 - )} @@ -240,7 +228,7 @@ export function WeightHistoryCard() { {/* 折线 */} @@ -271,7 +259,7 @@ export function WeightHistoryCard() { cy={point.y - 15} r={10} fill="rgba(255,255,255,0.9)" - stroke="#BBF246" + stroke={Colors.light.accentGreen} strokeWidth={1} /> {onBack ? ( - + ) : ( diff --git a/constants/Colors.ts b/constants/Colors.ts index 4d55d51..7e7ae9a 100644 --- a/constants/Colors.ts +++ b/constants/Colors.ts @@ -6,7 +6,7 @@ // 原子调色板(与设计图一致) export const palette = { // Primary - primary: '#BBF246', + primary: '#87CEEB', ink: '#192126', // Secondary / Neutrals @@ -18,10 +18,11 @@ export const palette = { purple: '#A48AED', red: '#ED4747', orange: '#FCC46F', - blue: '#95CCE3', + blue: '#87CEEB', // 更贴近logo背景的天空蓝 + blueSecondary: '#4682B4', // 钢蓝色,用于选中状态 } as const; -const primaryColor = palette.primary; // 应用主题色 +const primaryColor = palette.blue; // 应用主题色 const tintColorLight = primaryColor; const tintColorDark = '#FFFFFF'; @@ -35,6 +36,8 @@ export const Colors = { surface: '#FFFFFF', card: '#FFFFFF', + buttonBackground: palette.blue, + // 品牌与可交互主色 tint: tintColorLight, primary: primaryColor, @@ -51,6 +54,12 @@ export const Colors = { danger: palette.red, info: palette.blue, accentPurple: palette.purple, + accentGreen: palette.blue, + accentGreenDark: palette.blueSecondary, // 深绿色,用于文本和强调 + + // 日期选择器主题色 + datePickerNormal: palette.blue, + datePickerSelected: palette.blueSecondary, // 结构色 border: palette.neutral100 + '33', // 20% 透明度 @@ -64,10 +73,14 @@ export const Colors = { tabBarActiveBackground: primaryColor, // tab 激活时的背景色 // 页面氛围与装饰(新) - pageBackgroundEmphasis: '#F9FBF2', - heroSurfaceTint: 'rgba(187,242,70,0.18)', + pageBackgroundEmphasis: '#F0F8FF', // 淡蓝色背景强调 + heroSurfaceTint: 'rgba(135,206,235,0.18)', // 蓝色调的表面色彩 ornamentPrimary: 'rgba(187,242,70,0.22)', ornamentAccent: 'rgba(164,138,237,0.16)', + + // 统一背景渐变色(基于 StressAnalysisModal 的设计) + backgroundGradientStart: '#d3e9eeff', // 浅蓝色起始 (与logo背景匹配) + backgroundGradientEnd: '#ffffff', // 白色结束 }, dark: { // 基础文本/背景 @@ -94,6 +107,11 @@ export const Colors = { danger: palette.red, info: palette.blue, accentPurple: palette.purple, + accentGreenDark: '#2D5016', // 深绿色,用于文本和强调 + + // 日期选择器主题色 + datePickerNormal: palette.blue, + datePickerSelected: palette.blueSecondary, // 结构色 border: '#2A2F32', @@ -111,5 +129,9 @@ export const Colors = { heroSurfaceTint: 'rgba(187,242,70,0.12)', ornamentPrimary: 'rgba(187,242,70,0.18)', ornamentAccent: 'rgba(164,138,237,0.14)', + + // 统一背景渐变色(深色模式) + backgroundGradientStart: '#0A0B0C', // 深黑色起始 + backgroundGradientEnd: '#151718', // 背景色结束 }, } as const; diff --git a/ios/digitalpilates/Images.xcassets/AppIcon.appiconset/Contents.json b/ios/digitalpilates/Images.xcassets/AppIcon.appiconset/Contents.json index 3ab4f52..d6620a8 100644 --- a/ios/digitalpilates/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/digitalpilates/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "未命名项目 (1).jpeg", + "filename" : "Sealife.jpeg", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/ios/digitalpilates/Images.xcassets/AppIcon.appiconset/Sealife.jpeg b/ios/digitalpilates/Images.xcassets/AppIcon.appiconset/Sealife.jpeg new file mode 100644 index 0000000..2d9574b Binary files /dev/null and b/ios/digitalpilates/Images.xcassets/AppIcon.appiconset/Sealife.jpeg differ diff --git a/ios/digitalpilates/Images.xcassets/AppIcon.appiconset/未命名项目 (1).jpeg b/ios/digitalpilates/Images.xcassets/AppIcon.appiconset/未命名项目 (1).jpeg deleted file mode 100644 index 7bfe852..0000000 Binary files a/ios/digitalpilates/Images.xcassets/AppIcon.appiconset/未命名项目 (1).jpeg and /dev/null differ diff --git a/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/Contents.json b/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/Contents.json index 2eaf6ec..dd508a4 100644 --- a/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/Contents.json +++ b/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "未命名项目 (1).jpeg", + "filename" : "Sealife.jpeg", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "未命名项目.jpeg", + "filename" : "未命名项目 (5).jpeg", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "未命名项目 1.jpeg", + "filename" : "未命名项目 (5) 1.jpeg", "idiom" : "universal", "scale" : "3x" } diff --git a/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/Sealife.jpeg b/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/Sealife.jpeg new file mode 100644 index 0000000..2d9574b Binary files /dev/null and b/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/Sealife.jpeg differ diff --git a/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 (1).jpeg b/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 (1).jpeg deleted file mode 100644 index 7bfe852..0000000 Binary files a/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 (1).jpeg and /dev/null differ diff --git a/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 (5) 1.jpeg b/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 (5) 1.jpeg new file mode 100644 index 0000000..46132b1 Binary files /dev/null and b/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 (5) 1.jpeg differ diff --git a/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 (5).jpeg b/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 (5).jpeg new file mode 100644 index 0000000..46132b1 Binary files /dev/null and b/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 (5).jpeg differ diff --git a/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 1.jpeg b/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 1.jpeg deleted file mode 100644 index 82e0954..0000000 Binary files a/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目 1.jpeg and /dev/null differ diff --git a/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目.jpeg b/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目.jpeg deleted file mode 100644 index 82e0954..0000000 Binary files a/ios/digitalpilates/Images.xcassets/SplashScreenLogo.imageset/未命名项目.jpeg and /dev/null differ diff --git a/ios/digitalpilates/Images.xcassets/logo.imageset/Contents.json b/ios/digitalpilates/Images.xcassets/logo.imageset/Contents.json index 2eaf6ec..dd508a4 100644 --- a/ios/digitalpilates/Images.xcassets/logo.imageset/Contents.json +++ b/ios/digitalpilates/Images.xcassets/logo.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "未命名项目 (1).jpeg", + "filename" : "Sealife.jpeg", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "未命名项目.jpeg", + "filename" : "未命名项目 (5).jpeg", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "未命名项目 1.jpeg", + "filename" : "未命名项目 (5) 1.jpeg", "idiom" : "universal", "scale" : "3x" } diff --git a/ios/digitalpilates/Images.xcassets/logo.imageset/Sealife.jpeg b/ios/digitalpilates/Images.xcassets/logo.imageset/Sealife.jpeg new file mode 100644 index 0000000..2d9574b Binary files /dev/null and b/ios/digitalpilates/Images.xcassets/logo.imageset/Sealife.jpeg differ diff --git a/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 (1).jpeg b/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 (1).jpeg deleted file mode 100644 index 7bfe852..0000000 Binary files a/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 (1).jpeg and /dev/null differ diff --git a/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 (5) 1.jpeg b/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 (5) 1.jpeg new file mode 100644 index 0000000..46132b1 Binary files /dev/null and b/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 (5) 1.jpeg differ diff --git a/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 (5).jpeg b/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 (5).jpeg new file mode 100644 index 0000000..46132b1 Binary files /dev/null and b/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 (5).jpeg differ diff --git a/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 1.jpeg b/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 1.jpeg deleted file mode 100644 index 82e0954..0000000 Binary files a/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目 1.jpeg and /dev/null differ diff --git a/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目.jpeg b/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目.jpeg deleted file mode 100644 index 82e0954..0000000 Binary files a/ios/digitalpilates/Images.xcassets/logo.imageset/未命名项目.jpeg and /dev/null differ diff --git a/utils/bmi.ts b/utils/bmi.ts index a21977b..862ae6d 100644 --- a/utils/bmi.ts +++ b/utils/bmi.ts @@ -57,7 +57,7 @@ const BMI_COLORS = { backgroundColor: '#FFF4E6', // 浅橙色背景 }, normal: { - color: '#2D5016', // 深绿色文字 + color: '#2D5016', // 深绿色文字,保持硬编码因为这是工具函数的固定配色 backgroundColor: '#E8F5E8', // 浅绿色背景 }, overweight: {