import { Colors } from '@/constants/Colors'; import { ROUTES } from '@/constants/Routes'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useAuthGuard } from '@/hooks/useAuthGuard'; import { useColorScheme } from '@/hooks/useColorScheme'; import { fetchWeightHistory } from '@/store/userSlice'; import { BMI_CATEGORIES } from '@/utils/bmi'; import { Ionicons } from '@expo/vector-icons'; import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; import { Image } from 'expo-image'; import { LinearGradient } from 'expo-linear-gradient'; import { useRouter } from 'expo-router'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Dimensions, Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import Svg, { Circle, Defs, Path, Stop, LinearGradient as SvgLinearGradient } from 'react-native-svg'; import { WeightProgressBar } from './WeightProgressBar'; const { width: screenWidth } = Dimensions.get('window'); const CARD_WIDTH = screenWidth - 40; const CHART_WIDTH = CARD_WIDTH - 36; const CHART_HEIGHT = 70; const PADDING = 10; // 主题色 const THEME_PRIMARY = '#4F5BD5'; const THEME_SECONDARY = '#6B6CFF'; const THEME_SUCCESS = '#22C55E'; const THEME_TEXT_PRIMARY = '#1c1f3a'; const THEME_TEXT_SECONDARY = '#6f7ba7'; export function WeightHistoryCard() { const { t } = useTranslation(); const dispatch = useAppDispatch(); const router = useRouter(); const userProfile = useAppSelector((s) => s.user.profile); const weightHistory = useAppSelector((s) => s.user.weightHistory); const [showBMIModal, setShowBMIModal] = useState(false); const isLgAvaliable = isLiquidGlassAvailable(); const { pushIfAuthedElseLogin, isLoggedIn } = useAuthGuard(); const colorScheme = useColorScheme(); const themeColors = Colors[colorScheme ?? 'light']; const hasWeight = userProfile?.weight && parseFloat(userProfile.weight) > 0; useEffect(() => { if (isLoggedIn) { loadWeightHistory(); } }, [userProfile?.weight, isLoggedIn]); const loadWeightHistory = async () => { try { await dispatch(fetchWeightHistory() as any).unwrap(); } catch (error) { console.error('Failed to load weight history:', error); } }; // 点击添加按钮 - 需要登录 const handleAddWeight = () => { pushIfAuthedElseLogin(ROUTES.WEIGHT_RECORDS); }; const handleHideBMIModal = () => { setShowBMIModal(false); }; // 点击卡片 - 直接跳转,不需要登录 const navigateToWeightRecords = () => { router.push(ROUTES.WEIGHT_RECORDS); }; // Process weight history data const sortedHistory = [...weightHistory] .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()) .slice(-7); // 是否有数据 const hasData = sortedHistory.length > 0; // 计算减重进度 const currentWeight = userProfile?.weight ? parseFloat(userProfile.weight) : 0; const initialWeight = userProfile?.initialWeight ? parseFloat(userProfile.initialWeight) : (sortedHistory.length > 0 ? parseFloat(sortedHistory[0].weight) : 0); const targetWeight = userProfile?.targetWeight ? parseFloat(userProfile.targetWeight) : 0; // 计算进度百分比 const hasTargetWeight = targetWeight > 0 && initialWeight > targetWeight; const totalToLose = initialWeight - targetWeight; const actualLost = initialWeight - currentWeight; const weightProgress = hasTargetWeight && totalToLose > 0 ? actualLost / totalToLose : 0; // Generate chart data const weights = hasData ? sortedHistory.map(item => parseFloat(item.weight)) : []; const minWeight = hasData ? Math.min(...weights) : 0; const maxWeight = hasData ? Math.max(...weights) : 0; const weightRange = maxWeight - minWeight || 1; const points = hasData ? sortedHistory.map((item, index) => { const x = PADDING + (index / Math.max(sortedHistory.length - 1, 1)) * (CHART_WIDTH - 2 * PADDING); const normalizedWeight = (parseFloat(item.weight) - minWeight) / weightRange; const y = PADDING + 10 + (1 - normalizedWeight) * (CHART_HEIGHT - 2 * PADDING - 20); return { x, y, weight: item.weight, date: item.createdAt }; }) : []; // 生成平滑曲线路径(使用贝塞尔曲线) const generateSmoothPath = (pts: typeof points) => { if (pts.length === 0) return ''; if (pts.length === 1) return `M ${pts[0].x} ${pts[0].y}`; let path = `M ${pts[0].x} ${pts[0].y}`; for (let i = 0; i < pts.length - 1; i++) { const p0 = pts[Math.max(0, i - 1)]; const p1 = pts[i]; const p2 = pts[i + 1]; const p3 = pts[Math.min(pts.length - 1, i + 2)]; const cp1x = p1.x + (p2.x - p0.x) / 6; const cp1y = p1.y + (p2.y - p0.y) / 6; const cp2x = p2.x - (p3.x - p1.x) / 6; const cp2y = p2.y - (p3.y - p1.y) / 6; path += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x} ${p2.y}`; } return path; }; const smoothPath = generateSmoothPath(points); const singlePointPath = points.length === 1 ? `M ${PADDING} ${points[0].y} L ${CHART_WIDTH - PADDING} ${points[0].y}` : smoothPath; // 空状态下的占位曲线路径(水平虚线效果) const emptyLinePath = `M ${PADDING} ${CHART_HEIGHT / 2} L ${CHART_WIDTH - PADDING} ${CHART_HEIGHT / 2}`; return ( {t('statistics.components.weight.title')} {isLgAvaliable ? ( { e.stopPropagation(); handleAddWeight(); }} activeOpacity={0.8} > ) : ( { e.stopPropagation(); handleAddWeight(); }} activeOpacity={0.8} > )} {/* 当前体重显示 */} {hasWeight ? currentWeight.toFixed(1) : '--'} kg {sortedHistory.length > 1 && ( = 0 ? 'rgba(34, 197, 94, 0.1)' : 'rgba(255, 107, 107, 0.1)' } ]}> = 0 ? 'trending-down' : 'trending-up'} size={12} color={actualLost >= 0 ? THEME_SUCCESS : '#FF6B6B'} /> = 0 ? THEME_SUCCESS : '#FF6B6B' } ]}> {actualLost >= 0 ? '-' : '+'}{Math.abs(actualLost).toFixed(1)}kg )} {/* 图表显示 */} {hasData ? ( <> {/* 平滑曲线 */} {/* 数据点 */} {points.map((point, index) => { const isLastPoint = index === points.length - 1; return ( {/* 外圈光晕 */} {isLastPoint && ( )} {/* 数据点 */} ); })} ) : ( /* 空状态 - 虚线占位 */ )} {/* 图表信息 */} {hasData ? sortedHistory.length : '--'}{t('statistics.components.weight.days')} {hasData ? `${minWeight.toFixed(1)}-${maxWeight.toFixed(1)}kg` : '--'} {/* 减重进度条 - 始终显示 */} {/* BMI information modal */} {/* Title */} {t('statistics.components.weight.bmiModal.title')} {/* Introduction section */} {t('statistics.components.weight.bmiModal.description')} {t('statistics.components.weight.bmiModal.formula')} {/* BMI classification standards */} {t('statistics.components.weight.bmiModal.classificationTitle')} {BMI_CATEGORIES.map((category, index) => { const colors = [ { bg: '#FEF3C7', text: '#B45309', border: '#F59E0B' }, // Underweight { bg: '#E8F5E8', text: Colors.light.accentGreen, border: Colors.light.accentGreen }, // Normal { bg: '#FEF3C7', text: '#B45309', border: '#F59E0B' }, // Overweight { bg: '#FEE2E2', text: '#B91C1C', border: '#EF4444' } // Obese ][index]; return ( {category.name} {category.range} {category.advice} ); })} {/* Health tips */} {t('statistics.components.weight.bmiModal.healthTipsTitle')} {t('statistics.components.weight.bmiModal.tips.nutrition')} {t('statistics.components.weight.bmiModal.tips.exercise')} {t('statistics.components.weight.bmiModal.tips.sleep')} {t('statistics.components.weight.bmiModal.tips.monitoring')} {/* Disclaimer */} {t('statistics.components.weight.bmiModal.disclaimer')} {/* Bottom continue button */} {t('statistics.components.weight.bmiModal.continueButton')} ); } const styles = StyleSheet.create({ card: { backgroundColor: '#FFFFFF', borderRadius: 24, padding: 18, shadowColor: 'rgba(30, 41, 59, 0.08)', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.12, shadowRadius: 12, elevation: 4, marginTop: 16, }, cardHeader: { flexDirection: 'row', alignItems: 'center', }, iconContainer: { width: 28, height: 28, borderRadius: 8, backgroundColor: 'rgba(79, 91, 213, 0.1)', alignItems: 'center', justifyContent: 'center', marginRight: 10, }, iconSquare: { width: 16, height: 16, tintColor: THEME_PRIMARY, }, cardTitle: { fontSize: 15, color: THEME_TEXT_PRIMARY, flex: 1, fontWeight: '700', fontFamily: 'AliBold', }, headerButtons: { flexDirection: 'row', alignItems: 'center', gap: 8, }, chartToggleButton: { width: 28, height: 28, borderRadius: 14, alignItems: 'center', justifyContent: 'center', }, addButton: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(79, 91, 213, 0.1)', }, addButtonGlass: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(79, 91, 213, 0.15)', }, currentWeightSection: { flexDirection: 'row', alignItems: 'center', marginTop: 12, gap: 12, }, weightValueContainer: { flexDirection: 'row', alignItems: 'baseline', }, weightValue: { fontSize: 32, fontWeight: '800', color: THEME_TEXT_PRIMARY, fontFamily: 'AliBold', }, weightUnit: { fontSize: 14, fontWeight: '600', color: THEME_TEXT_SECONDARY, fontFamily: 'AliRegular', marginLeft: 4, }, changeTag: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 8, paddingVertical: 4, borderRadius: 12, gap: 4, }, changeText: { fontSize: 12, fontWeight: '700', fontFamily: 'AliBold', }, emptyContent: { alignItems: 'center', }, emptyTitle: { fontSize: 16, fontWeight: '700', color: THEME_TEXT_PRIMARY, marginBottom: 6, }, emptyDescription: { fontSize: 14, color: THEME_TEXT_SECONDARY, textAlign: 'center', marginBottom: 16, lineHeight: 20, }, recordButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: THEME_PRIMARY, paddingHorizontal: 16, paddingVertical: 10, borderRadius: 20, gap: 6, }, recordButtonText: { color: '#FFFFFF', fontSize: 14, fontWeight: '700', fontFamily: 'AliBold', }, chartContainer: { width: '100%', alignItems: 'center', marginTop: 12, }, chartInfo: { flexDirection: 'row', justifyContent: 'space-around', width: '100%', marginTop: -14, }, infoItem: { alignItems: 'center', backgroundColor: 'rgba(79, 91, 213, 0.06)', paddingHorizontal: 12, paddingVertical: 4, borderRadius: 10, }, infoLabel: { fontSize: 11, color: THEME_TEXT_SECONDARY, fontWeight: '500', fontFamily: 'AliRegular', }, infoValue: { fontSize: 14, fontWeight: '700', color: THEME_TEXT_PRIMARY, }, // BMI modal styles bmiModalContainer: { flex: 1, }, bmiModalContent: { flex: 1, padding: 20, }, bmiModalTitle: { fontSize: 28, fontWeight: '800', color: '#111827', textAlign: 'center', marginBottom: 24, letterSpacing: -0.5, fontFamily: 'AliBold', }, bmiModalIntroSection: { marginBottom: 32, }, bmiModalDescription: { fontSize: 16, color: '#374151', lineHeight: 24, textAlign: 'center', marginBottom: 16, fontFamily: 'AliRegular', }, bmiModalFormulaContainer: { backgroundColor: '#F3F4F6', borderRadius: 12, padding: 16, alignItems: 'center', }, bmiModalFormulaText: { fontSize: 14, fontWeight: '600', color: '#374151', fontFamily: 'AliBold', }, bmiModalSectionTitle: { fontSize: 20, fontWeight: '700', color: '#111827', marginBottom: 16, letterSpacing: -0.5, fontFamily: 'AliBold', }, bmiModalStatsCard: { marginBottom: 32, }, bmiModalStatItem: { borderRadius: 12, padding: 16, marginBottom: 12, borderWidth: 1, }, bmiModalStatHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, }, bmiModalStatTitle: { fontSize: 16, fontWeight: '700', fontFamily: 'AliBold', }, bmiModalStatRange: { fontSize: 14, fontWeight: '600', fontFamily: 'AliBold', }, bmiModalStatAdvice: { fontSize: 14, lineHeight: 20, fontFamily: 'AliRegular', }, bmiModalHealthTips: { marginBottom: 32, }, bmiModalTipsItem: { flexDirection: 'row', alignItems: 'center', marginBottom: 16, paddingHorizontal: 16, paddingVertical: 12, backgroundColor: '#F8FAFC', borderRadius: 12, }, bmiModalTipsText: { fontSize: 14, color: '#374151', marginLeft: 12, flex: 1, lineHeight: 20, fontFamily: 'AliRegular', }, bmiModalDisclaimer: { flexDirection: 'row', alignItems: 'flex-start', backgroundColor: '#FEF3C7', borderRadius: 12, padding: 16, marginBottom: 20, }, bmiModalDisclaimerText: { fontSize: 13, color: '#B45309', marginLeft: 8, flex: 1, lineHeight: 18, fontFamily: 'AliRegular', }, bmiModalBottomContainer: { padding: 20, paddingBottom: 34, }, bmiModalContinueButton: { marginBottom: 8, }, bmiModalButtonBackground: { backgroundColor: THEME_TEXT_PRIMARY, borderRadius: 16, paddingVertical: 16, alignItems: 'center', }, bmiModalButtonText: { fontSize: 16, fontWeight: '700', color: '#FFFFFF', fontFamily: 'AliBold', }, bmiModalHomeIndicator: { height: 5, backgroundColor: '#D1D5DB', borderRadius: 3, alignSelf: 'center', width: 36, }, });