import React, { useEffect, useState } from 'react'; import { StyleSheet, Text, View, ScrollView, TouchableOpacity, Dimensions, ActivityIndicator, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { router } from 'expo-router'; import dayjs from 'dayjs'; import { LinearGradient } from 'expo-linear-gradient'; import Svg, { Circle } from 'react-native-svg'; import { fetchSleepDetailForDate, SleepDetailData, SleepStage, getSleepStageDisplayName, getSleepStageColor, formatSleepTime, formatTime } from '@/services/sleepService'; import { ensureHealthPermissions } from '@/utils/health'; import { Colors } from '@/constants/Colors'; const { width } = Dimensions.get('window'); // 圆形进度条组件 const CircularProgress = ({ size, strokeWidth, progress, color, backgroundColor = '#E5E7EB' }: { size: number; strokeWidth: number; progress: number; // 0-100 color: string; backgroundColor?: string; }) => { const radius = (size - strokeWidth) / 2; const circumference = radius * 2 * Math.PI; const strokeDasharray = circumference; const strokeDashoffset = circumference - (progress / 100) * circumference; return ( {/* 背景圆环 */} {/* 进度圆环 */} ); }; // 睡眠阶段图表组件 const SleepStageChart = ({ sleepData }: { sleepData: SleepDetailData }) => { const chartWidth = width - 80; const maxHeight = 120; // 生成24小时的睡眠阶段数据(模拟数据,实际应根据真实样本计算) const hourlyData = Array.from({ length: 24 }, (_, hour) => { // 如果没有数据,显示空状态 if (sleepData.totalSleepTime === 0) { return null; } // 根据时间判断可能的睡眠状态 if (hour >= 0 && hour <= 6) { // 凌晨0-6点,主要睡眠时间 if (hour <= 2) return SleepStage.Core; if (hour <= 4) return SleepStage.Deep; return SleepStage.REM; } else if (hour >= 22) { // 晚上10点后开始入睡 return SleepStage.Core; } return null; // 清醒时间 }); return ( 🛏️ {sleepData.totalSleepTime > 0 ? formatTime(sleepData.bedtime) : '--:--'} ❤️ 平均心率: {sleepData.averageHeartRate || '--'} BPM ☀️ {sleepData.totalSleepTime > 0 ? formatTime(sleepData.wakeupTime) : '--:--'} {hourlyData.map((stage, index) => { const barHeight = stage ? Math.random() * 0.6 + 0.4 : 0.1; // 随机高度模拟真实数据 const color = stage ? getSleepStageColor(stage) : '#E5E7EB'; return ( ); })} ); }; export default function SleepDetailScreen() { const insets = useSafeAreaInsets(); const [sleepData, setSleepData] = useState(null); const [loading, setLoading] = useState(true); const [selectedDate] = useState(dayjs().toDate()); useEffect(() => { loadSleepData(); }, [selectedDate]); const loadSleepData = async () => { try { setLoading(true); // 确保有健康权限 const hasPermission = await ensureHealthPermissions(); if (!hasPermission) { console.warn('没有健康数据权限'); return; } // 获取睡眠详情数据 const data = await fetchSleepDetailForDate(selectedDate); setSleepData(data); } catch (error) { console.error('加载睡眠数据失败:', error); } finally { setLoading(false); } }; if (loading) { return ( 加载睡眠数据中... ); } // 如果没有数据,使用默认数据结构 const displayData: SleepDetailData = sleepData || { sleepScore: 0, totalSleepTime: 0, sleepQualityPercentage: 0, bedtime: new Date().toISOString(), wakeupTime: new Date().toISOString(), timeInBed: 0, sleepStages: [], averageHeartRate: null, sleepHeartRateData: [], sleepEfficiency: 0, qualityDescription: '暂无睡眠数据', recommendation: '请确保在真实iOS设备上运行并授权访问健康数据,或等待有睡眠数据后再查看。' }; return ( {/* 背景渐变 */} {/* 顶部导航 */} router.back()}> 今天, {dayjs(selectedDate).format('M月DD日')} {/* 睡眠得分圆形显示 */} {displayData.sleepScore} 睡眠得分 {/* 睡眠质量描述 */} {displayData.qualityDescription} {/* 建议文本 */} {displayData.recommendation} {/* 睡眠统计卡片 */} 🌙 睡眠时间 {displayData.totalSleepTime > 0 ? formatSleepTime(displayData.totalSleepTime) : '--'} {displayData.totalSleepTime > 0 ? '良好' : '--'} 💎 睡眠质量 {displayData.sleepQualityPercentage > 0 ? `${displayData.sleepQualityPercentage}%` : '--'} {displayData.sleepQualityPercentage > 0 ? '优秀' : '--'} {/* 睡眠阶段图表 */} {/* 睡眠阶段统计 */} {displayData.sleepStages.length > 0 ? displayData.sleepStages.map((stage, index) => ( {getSleepStageDisplayName(stage.stage)} {stage.percentage}% {formatSleepTime(stage.duration)} {stage.quality === 'excellent' ? '优秀' : stage.quality === 'good' ? '良好' : stage.quality === 'fair' ? '一般' : '偏低'} )) : ( 暂无睡眠阶段数据 )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F8FAFC', }, gradientBackground: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingBottom: 16, backgroundColor: 'transparent', }, backButton: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(255, 255, 255, 0.8)', alignItems: 'center', justifyContent: 'center', }, backButtonText: { fontSize: 24, fontWeight: '300', color: '#374151', }, headerTitle: { fontSize: 16, fontWeight: '600', color: '#111827', }, navButton: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(255, 255, 255, 0.8)', alignItems: 'center', justifyContent: 'center', }, navButtonText: { fontSize: 24, fontWeight: '300', color: '#9CA3AF', }, scrollView: { flex: 1, }, scrollContent: { paddingHorizontal: 20, paddingBottom: 40, }, scoreContainer: { alignItems: 'center', marginVertical: 20, }, circularProgressContainer: { position: 'relative', alignItems: 'center', justifyContent: 'center', }, scoreTextContainer: { position: 'absolute', alignItems: 'center', justifyContent: 'center', }, scoreNumber: { fontSize: 48, fontWeight: '800', color: '#1F2937', lineHeight: 48, }, scoreLabel: { fontSize: 14, color: '#6B7280', marginTop: 4, }, qualityDescription: { fontSize: 18, fontWeight: '600', color: '#1F2937', textAlign: 'center', marginBottom: 16, lineHeight: 24, }, recommendationText: { fontSize: 14, color: '#6B7280', textAlign: 'center', lineHeight: 20, marginBottom: 32, paddingHorizontal: 16, }, statsContainer: { flexDirection: 'row', gap: 16, marginBottom: 32, }, statCard: { flex: 1, backgroundColor: 'rgba(255, 255, 255, 0.9)', borderRadius: 16, padding: 16, alignItems: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 3, }, statIcon: { fontSize: 24, marginBottom: 8, }, statLabel: { fontSize: 12, color: '#6B7280', marginBottom: 4, }, statValue: { fontSize: 18, fontWeight: '700', color: '#1F2937', marginBottom: 4, }, statQuality: { fontSize: 12, color: '#10B981', fontWeight: '500', }, chartContainer: { backgroundColor: 'rgba(255, 255, 255, 0.9)', borderRadius: 16, padding: 16, marginBottom: 24, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 3, }, chartHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, }, chartTimeLabel: { alignItems: 'center', }, chartTimeText: { fontSize: 12, color: '#6B7280', fontWeight: '500', }, chartHeartRate: { alignItems: 'center', }, chartHeartRateText: { fontSize: 12, color: '#EF4444', fontWeight: '600', }, chartBars: { flexDirection: 'row', alignItems: 'flex-end', height: 120, gap: 2, }, chartBar: { borderRadius: 2, minHeight: 8, }, stagesContainer: { backgroundColor: 'rgba(255, 255, 255, 0.9)', borderRadius: 16, padding: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 3, }, stageRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#F3F4F6', }, stageInfo: { flexDirection: 'row', alignItems: 'center', flex: 1, }, stageColorDot: { width: 12, height: 12, borderRadius: 6, marginRight: 12, }, stageName: { fontSize: 14, color: '#374151', fontWeight: '500', }, stageStats: { alignItems: 'flex-end', }, stagePercentage: { fontSize: 16, fontWeight: '700', color: '#1F2937', }, stageDuration: { fontSize: 12, color: '#6B7280', marginTop: 2, }, stageQuality: { fontSize: 11, fontWeight: '600', marginTop: 2, }, loadingContainer: { justifyContent: 'center', alignItems: 'center', }, loadingText: { fontSize: 16, color: '#6B7280', marginTop: 16, }, errorText: { fontSize: 16, color: '#6B7280', marginBottom: 16, }, retryButton: { backgroundColor: Colors.light.primary, borderRadius: 8, paddingHorizontal: 24, paddingVertical: 12, }, retryButtonText: { color: '#FFFFFF', fontSize: 14, fontWeight: '600', }, noDataContainer: { alignItems: 'center', paddingVertical: 24, }, noDataText: { fontSize: 14, color: '#9CA3AF', fontStyle: 'italic', }, });