import { Colors } from '@/constants/Colors'; import { useColorScheme } from '@/hooks/useColorScheme'; import { fetchHRVSamples, HRVData } from '@/utils/health'; import { convertHrvToStressIndex, getStressLevelInfo } from '@/utils/stress'; import dayjs from 'dayjs'; import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; import { LinearGradient } from 'expo-linear-gradient'; import React, { useEffect, useState } from 'react'; import { ActivityIndicator, Modal, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; interface StressAnalysisModalProps { visible: boolean; onClose: () => void; hrvValue: number; updateTime: Date; } interface StressStats { percentage: number; count: number; range: string; } interface HistoryData { goodEvents: StressStats; energetic: StressStats; stressed: StressStats; totalSamples: number; } export function StressAnalysisModal({ visible, onClose, hrvValue, updateTime }: StressAnalysisModalProps) { const colorScheme = useColorScheme(); const colors = Colors[colorScheme ?? 'light']; const [loading, setLoading] = useState(true); const [historyData, setHistoryData] = useState({ goodEvents: { percentage: 0, count: 0, range: '>75毫秒' }, energetic: { percentage: 0, count: 0, range: '40-75毫秒' }, stressed: { percentage: 0, count: 0, range: '<40毫秒' }, totalSamples: 0 }); // 当前压力状态 const stressIndex = convertHrvToStressIndex(hrvValue); const stressInfo = getStressLevelInfo(stressIndex); useEffect(() => { if (visible) { loadHistoryData(); } }, [visible]); const loadHistoryData = async () => { setLoading(true); try { const endDate = new Date(); const startDate = dayjs().subtract(30, 'day').toDate(); const samples = await fetchHRVSamples(startDate, endDate); processHistoryData(samples); } catch (error) { console.error('Failed to load HRV history:', error); } finally { setLoading(false); } }; const processHistoryData = (samples: HRVData[]) => { if (!samples.length) return; let goodCount = 0; let energeticCount = 0; let stressedCount = 0; samples.forEach(sample => { const val = sample.value; if (val > 75) { goodCount++; } else if (val >= 40) { energeticCount++; } else { stressedCount++; } }); const total = samples.length; setHistoryData({ goodEvents: { percentage: Math.round((goodCount / total) * 100), count: goodCount, range: '>75毫秒' }, energetic: { percentage: Math.round((energeticCount / total) * 100), count: energeticCount, range: '40-75毫秒' }, stressed: { percentage: Math.round((stressedCount / total) * 100), count: stressedCount, range: '<40毫秒' }, totalSamples: total }); }; const getStatusColor = (level: string) => { switch (level) { case 'low': return '#10B981'; case 'moderate': return '#3B82F6'; case 'high': return '#F59E0B'; default: return colors.text; } }; return ( {/* 标题区域 */} 压力分析 {/* 当前状态卡片 */} 当前状态 更新于 {dayjs(updateTime).format('HH:mm')} {stressInfo.label} {stressInfo.description} HRV {Math.round(hrvValue)}ms {/* 最近30天HRV情况 */} 最近30天压力分布 {loading ? ( ) : ( <> {/* 彩色横条图 */} {historyData.totalSamples > 0 ? ( {historyData.stressed.percentage > 0 && ( )} {historyData.energetic.percentage > 0 && ( )} {historyData.goodEvents.percentage > 0 && ( )} ) : ( )} 鸭梨山大 活力满满 好事发生 {/* 数据统计卡片 */} {/* 好事发生 & 活力满满 */} 好事发生 {historyData.goodEvents.percentage}% HRV {historyData.goodEvents.range} {historyData.goodEvents.count}次 活力满满 {historyData.energetic.percentage}% HRV {historyData.energetic.range} {historyData.energetic.count}次 {/* 鸭梨山大 */} 鸭梨山大 {historyData.stressed.percentage}% HRV {historyData.stressed.range} {historyData.stressed.count}次 )} {/* 底部继续按钮 */} {isLiquidGlassAvailable() ? ( 继续 ) : ( 继续 )} ); } const styles = StyleSheet.create({ modalContainer: { flex: 1, }, content: { flex: 1, paddingHorizontal: 20, }, title: { fontSize: 24, fontWeight: '800', color: '#111827', textAlign: 'center', marginTop: 24, marginBottom: 32, fontFamily: 'AliBold', }, currentStatusCard: { backgroundColor: '#FFFFFF', borderRadius: 20, padding: 20, marginBottom: 32, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.06, shadowRadius: 12, elevation: 4, }, statusHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, }, statusLabel: { fontSize: 16, fontWeight: '700', color: '#374151', }, updateTime: { fontSize: 12, color: '#9CA3AF', }, statusValueContainer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, statusText: { fontSize: 28, fontWeight: '800', marginBottom: 4, }, statusDesc: { fontSize: 14, color: '#6B7280', maxWidth: 200, }, hrvValueBox: { alignItems: 'flex-end', }, hrvValueLabel: { fontSize: 12, color: '#9CA3AF', fontWeight: '600', marginBottom: 2, }, hrvValue: { fontSize: 32, fontWeight: '800', color: '#111827', lineHeight: 36, }, hrvUnit: { fontSize: 14, fontWeight: '600', color: '#6B7280', marginLeft: 2, }, sectionTitle: { fontSize: 20, fontWeight: '700', color: '#111827', marginBottom: 20, fontFamily: 'AliBold', }, chartContainer: { marginBottom: 32, }, colorBar: { height: 16, borderRadius: 8, overflow: 'hidden', marginBottom: 16, backgroundColor: '#F3F4F6', }, progressBarContainer: { flexDirection: 'row', width: '100%', height: '100%', }, progressSegment: { height: '100%', }, gradientBar: { flex: 1, }, legend: { flexDirection: 'row', justifyContent: 'space-around', }, legendItem: { flexDirection: 'row', alignItems: 'center', }, legendDot: { width: 10, height: 10, borderRadius: 5, marginRight: 8, }, legendText: { fontSize: 13, fontWeight: '500', color: '#4B5563', }, statsCard: { backgroundColor: '#FFFFFF', borderRadius: 20, padding: 24, marginBottom: 32, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.04, shadowRadius: 12, elevation: 3, }, statsRow: { flexDirection: 'row', gap: 24, marginBottom: 32, }, statItem: { flex: 1, }, statTitle: { fontSize: 15, fontWeight: '600', marginBottom: 12, }, statPercentage: { fontSize: 32, fontWeight: '800', color: '#111827', marginBottom: 8, fontFamily: 'AliBold', }, statDetails: { marginBottom: 8, }, statRange: { fontSize: 12, fontWeight: '600', paddingHorizontal: 8, paddingVertical: 4, borderRadius: 6, alignSelf: 'flex-start', overflow: 'hidden', }, statCount: { fontSize: 13, fontWeight: '500', color: '#6B7280', }, bottomContainer: { paddingHorizontal: 20, paddingBottom: Platform.OS === 'ios' ? 34 : 20, backgroundColor: 'transparent', }, continueButton: { borderRadius: 28, overflow: 'hidden', marginBottom: 12, shadowColor: '#8B5CF6', shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.3, shadowRadius: 16, elevation: 8, }, glassButton: { paddingVertical: 18, alignItems: 'center', justifyContent: 'center', flexDirection: 'row', borderRadius: 28, }, buttonGradient: { paddingVertical: 18, alignItems: 'center', justifyContent: 'center', flexDirection: 'row', }, buttonText: { fontSize: 18, fontWeight: '700', color: '#FFFFFF', letterSpacing: 0.5, }, homeIndicator: { width: 134, height: 5, backgroundColor: Platform.OS === 'ios' ? 'rgba(0, 0, 0, 0.3)' : '#000', borderRadius: 3, alignSelf: 'center', }, });