import { CircularRing } from '@/components/CircularRing'; import { FastingStartPickerModal } from '@/components/fasting/FastingStartPickerModal'; import { Colors } from '@/constants/Colors'; import { FASTING_PLANS, FastingPlan, getPlanById, getRecommendedStart } from '@/constants/Fasting'; import { ROUTES } from '@/constants/Routes'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useColorScheme } from '@/hooks/useColorScheme'; import { rescheduleActivePlan, scheduleFastingPlan, selectActiveFastingSchedule, } from '@/store/fastingSlice'; import { buildDisplayWindow, calculateFastingWindow, savePreferredPlanId } from '@/utils/fasting'; import { Ionicons } from '@expo/vector-icons'; import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect'; import { LinearGradient } from 'expo-linear-gradient'; import { useLocalSearchParams, useRouter } from 'expo-router'; import React, { useEffect, useMemo, useState } from 'react'; import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; type InfoTab = 'fit' | 'avoid' | 'intro'; const TAB_LABELS: Record = { fit: '适合人群', avoid: '不适合人群', intro: '计划介绍', }; export default function FastingPlanDetailScreen() { const router = useRouter(); const insets = useSafeAreaInsets(); const theme = useColorScheme() ?? 'light'; const colors = Colors[theme]; const dispatch = useAppDispatch(); const activeSchedule = useAppSelector(selectActiveFastingSchedule); const { planId } = useLocalSearchParams<{ planId: string }>(); const fallbackPlan = FASTING_PLANS[0]; const plan: FastingPlan = useMemo( () => (planId ? getPlanById(planId) ?? fallbackPlan : fallbackPlan), [planId, fallbackPlan] ); useEffect(() => { void savePreferredPlanId(plan.id); }, [plan.id]); const [infoTab, setInfoTab] = useState('fit'); const [showPicker, setShowPicker] = useState(false); const glassAvailable = isLiquidGlassAvailable(); const recommendedStart = useMemo(() => getRecommendedStart(plan), [plan]); const window = calculateFastingWindow(recommendedStart, plan.fastingHours); const displayWindow = buildDisplayWindow(window.start, window.end); const handleStartWithRecommended = () => { dispatch(scheduleFastingPlan({ planId: plan.id, start: recommendedStart.toISOString(), origin: 'recommended' })); router.replace(ROUTES.TAB_FASTING); }; const handleOpenPicker = () => { setShowPicker(true); }; const handleConfirmPicker = (date: Date) => { if (activeSchedule?.planId === plan.id) { dispatch(rescheduleActivePlan({ start: date.toISOString(), origin: 'manual' })); } else { dispatch(scheduleFastingPlan({ planId: plan.id, start: date.toISOString(), origin: 'manual' })); } setShowPicker(false); router.replace(ROUTES.TAB_FASTING); }; const renderInfoList = () => { let items: string[] = []; if (infoTab === 'fit') items = plan.audienceFit; if (infoTab === 'avoid') items = plan.audienceAvoid; if (infoTab === 'intro') items = [plan.description, ...plan.nutritionTips]; return ( {items.map((item) => ( {item} ))} ); }; const fastingRatio = plan.fastingHours / 24; return ( {/* 固定悬浮的返回按钮 */} {glassAvailable ? ( ) : ( )} {plan.id} {plan.badge && ( {plan.badge} )} {plan.title} {plan.subtitle} 断食 {plan.fastingHours} 小时 进食 {plan.eatingHours} 小时 每日节奏 {plan.fastingHours} h 断食 进食窗口 {plan.eatingHours} h 断食期 进食期 推荐开始时间 开始 {displayWindow.startDayLabel} {displayWindow.startTimeLabel} 结束 {displayWindow.endDayLabel} {displayWindow.endTimeLabel} 推荐在晚餐后约 2 小时开始,保证进食期覆盖早餐至午后。 {(Object.keys(TAB_LABELS) as InfoTab[]).map((tabKey) => { const isActive = infoTab === tabKey; return ( setInfoTab(tabKey)} activeOpacity={0.9} > {TAB_LABELS[tabKey]} ); })} {renderInfoList()} 自定义开始时间 开始轻断食 setShowPicker(false)} initialDate={recommendedStart} recommendedDate={recommendedStart} onConfirm={handleConfirmPicker} /> ); } const styles = StyleSheet.create({ safeArea: { flex: 1, }, hero: { paddingHorizontal: 24, paddingBottom: 32, borderBottomLeftRadius: 32, borderBottomRightRadius: 32, }, backButtonContainer: { position: 'absolute', top: 0, left: 24, zIndex: 10, }, backButton: { width: 44, height: 44, borderRadius: 22, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 4, }, shadowOpacity: 0.15, shadowRadius: 8, elevation: 8, }, backButtonGlass: { width: 44, height: 44, borderRadius: 22, alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: 'rgba(255,255,255,0.3)', overflow: 'hidden', }, backButtonFallback: { width: 44, height: 44, borderRadius: 22, alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(255,255,255,0.85)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.5)', }, heroContent: { }, heroHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 14, }, planId: { fontSize: 18, fontWeight: '700', color: '#2E3142', marginRight: 12, }, heroBadge: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, }, heroBadgeText: { fontSize: 12, fontWeight: '600', }, heroTitle: { fontSize: 26, fontWeight: '800', color: '#2E3142', marginBottom: 8, }, heroSubtitle: { fontSize: 14, color: '#5B6572', marginBottom: 16, }, tagRow: { flexDirection: 'row', }, tagChip: { marginRight: 10, paddingHorizontal: 14, paddingVertical: 8, borderRadius: 18, }, tagChipText: { fontSize: 12, fontWeight: '600', }, body: { paddingHorizontal: 24, paddingTop: 28, }, chartCard: { alignItems: 'center', marginBottom: 24, }, chartContent: { position: 'absolute', top: 70, alignItems: 'center', }, chartTitle: { fontSize: 14, color: '#6F7D87', marginBottom: 6, }, chartValue: { fontSize: 20, fontWeight: '700', color: '#2E3142', }, chartSubtitle: { fontSize: 12, color: '#6F7D87', marginTop: 4, }, legendRow: { flexDirection: 'row', justifyContent: 'center', marginTop: 20, }, legendItem: { flexDirection: 'row', alignItems: 'center', marginHorizontal: 12, }, legendDot: { width: 12, height: 12, borderRadius: 6, marginRight: 6, }, legendText: { fontSize: 12, color: '#5B6572', }, windowCard: { borderRadius: 20, backgroundColor: '#FFFFFF', padding: 20, marginBottom: 24, shadowColor: '#000', shadowOpacity: 0.04, shadowRadius: 12, shadowOffset: { width: 0, height: 10 }, elevation: 3, }, windowLabel: { fontSize: 16, fontWeight: '700', color: '#2E3142', marginBottom: 12, }, windowRow: { flexDirection: 'row', alignItems: 'center', }, windowCell: { flex: 1, alignItems: 'center', }, windowTitle: { fontSize: 12, color: '#778290', marginBottom: 6, }, windowDay: { fontSize: 16, fontWeight: '600', color: '#2E3142', }, windowTime: { fontSize: 24, fontWeight: '700', marginTop: 6, }, windowDivider: { width: 1, height: 60, backgroundColor: 'rgba(95,105,116,0.2)', }, windowHint: { fontSize: 12, color: '#6F7D87', marginTop: 16, lineHeight: 18, }, tabContainer: { flexDirection: 'row', marginBottom: 20, borderRadius: 20, backgroundColor: '#F2F3F5', padding: 4, }, tabButton: { flex: 1, borderRadius: 16, paddingVertical: 12, alignItems: 'center', }, tabButtonText: { fontSize: 13, fontWeight: '600', }, infoList: { marginBottom: 28, }, infoItem: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 12, }, infoDot: { width: 6, height: 6, borderRadius: 3, marginRight: 10, marginTop: 7, }, infoText: { flex: 1, fontSize: 14, color: '#4A5460', lineHeight: 21, }, actionBlock: { flexDirection: 'row', alignItems: 'center', marginBottom: 50, }, secondaryAction: { flex: 1, borderWidth: 1.4, borderRadius: 24, paddingVertical: 14, alignItems: 'center', marginRight: 12, backgroundColor: '#FFFFFF', }, secondaryActionText: { fontSize: 14, fontWeight: '600', }, primaryAction: { flex: 1, borderRadius: 24, paddingVertical: 14, alignItems: 'center', }, primaryActionText: { fontSize: 16, fontWeight: '700', color: '#FFFFFF', }, });