- 在用药模块首次添加时显示医疗免责声明弹窗 - 新增断食参考文献页面,展示权威医学机构来源 - 在个人中心添加WHO医学来源入口 - 使用本地存储记录用户已读免责声明状态 - 支持Liquid Glass毛玻璃效果和降级方案 - 新增中英文国际化翻译支持
833 lines
25 KiB
TypeScript
833 lines
25 KiB
TypeScript
import { FastingOverviewCard } from '@/components/fasting/FastingOverviewCard';
|
||
import { FastingPlanList } from '@/components/fasting/FastingPlanList';
|
||
import { FastingStartPickerModal } from '@/components/fasting/FastingStartPickerModal';
|
||
import { NotificationErrorAlert } from '@/components/ui/NotificationErrorAlert';
|
||
import { FASTING_PLANS, FastingPlan, getPlanById, getRecommendedStart } from '@/constants/Fasting';
|
||
import { ROUTES } from '@/constants/Routes';
|
||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||
import { useCountdown } from '@/hooks/useCountdown';
|
||
import { useFastingCycleNotifications } from '@/hooks/useFastingCycleNotifications';
|
||
import { useFastingNotifications } from '@/hooks/useFastingNotifications';
|
||
import {
|
||
clearActiveSchedule,
|
||
completeCurrentCycleSession,
|
||
hydrateFastingCycle,
|
||
pauseFastingCycle,
|
||
rescheduleActivePlan,
|
||
resumeFastingCycle,
|
||
scheduleFastingPlan,
|
||
selectActiveCyclePlan,
|
||
// 周期性断食相关的 selectors
|
||
selectActiveFastingCycle,
|
||
selectActiveFastingPlan,
|
||
selectActiveFastingSchedule,
|
||
selectCurrentCyclePlan,
|
||
selectCurrentCycleSession,
|
||
selectCurrentFastingPlan,
|
||
selectCurrentFastingTimes,
|
||
selectCycleHistory,
|
||
selectIsInCycleMode,
|
||
startFastingCycle,
|
||
stopFastingCycle,
|
||
updateFastingCycleTime
|
||
} from '@/store/fastingSlice';
|
||
import {
|
||
buildDisplayWindow,
|
||
getFastingPhase,
|
||
getPhaseLabel,
|
||
// 周期性断食相关的工具函数
|
||
loadActiveFastingCycle,
|
||
loadCurrentCycleSession,
|
||
loadCycleHistory,
|
||
loadPreferredPlanId,
|
||
saveActiveFastingCycle,
|
||
saveCurrentCycleSession,
|
||
saveCycleHistory,
|
||
savePreferredPlanId
|
||
} from '@/utils/fasting';
|
||
import { Ionicons } from '@expo/vector-icons';
|
||
import { useFocusEffect } from '@react-navigation/native';
|
||
import dayjs from 'dayjs';
|
||
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
|
||
import { useRouter } from 'expo-router';
|
||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||
|
||
export default function FastingTabScreen() {
|
||
const router = useRouter();
|
||
const dispatch = useAppDispatch();
|
||
const insets = useSafeAreaInsets();
|
||
const scrollViewRef = React.useRef<ScrollView>(null);
|
||
|
||
// 单次断食计划的状态
|
||
const activeSchedule = useAppSelector(selectActiveFastingSchedule);
|
||
const activePlan = useAppSelector(selectActiveFastingPlan);
|
||
|
||
// 周期性断食计划的状态
|
||
const activeCycle = useAppSelector(selectActiveFastingCycle);
|
||
const currentCycleSession = useAppSelector(selectCurrentCycleSession);
|
||
const cycleHistory = useAppSelector(selectCycleHistory);
|
||
const activeCyclePlan = useAppSelector(selectActiveCyclePlan);
|
||
const currentCyclePlan = useAppSelector(selectCurrentCyclePlan);
|
||
|
||
// 统一的当前断食信息(优先显示周期性)
|
||
const currentPlan = useAppSelector(selectCurrentFastingPlan);
|
||
const currentTimes = useAppSelector(selectCurrentFastingTimes);
|
||
const isInCycleMode = useAppSelector(selectIsInCycleMode);
|
||
|
||
const defaultPlan = FASTING_PLANS.find((plan) => plan.id === '14-10') ?? FASTING_PLANS[0];
|
||
const [preferredPlanId, setPreferredPlanId] = useState<string | undefined>(currentPlan?.id ?? undefined);
|
||
|
||
// 数据持久化
|
||
useEffect(() => {
|
||
if (!currentPlan?.id) return;
|
||
setPreferredPlanId(currentPlan.id);
|
||
void savePreferredPlanId(currentPlan.id);
|
||
}, [currentPlan?.id]);
|
||
|
||
useEffect(() => {
|
||
let cancelled = false;
|
||
|
||
const hydratePreferredPlan = async () => {
|
||
try {
|
||
const savedPlanId = await loadPreferredPlanId();
|
||
if (cancelled) return;
|
||
if (currentPlan?.id) return;
|
||
if (savedPlanId && getPlanById(savedPlanId)) {
|
||
setPreferredPlanId(savedPlanId);
|
||
}
|
||
} catch (error) {
|
||
console.warn('读取断食首选计划失败', error);
|
||
}
|
||
};
|
||
|
||
hydratePreferredPlan();
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, [currentPlan?.id]);
|
||
|
||
// 加载周期性断食数据
|
||
useEffect(() => {
|
||
let cancelled = false;
|
||
|
||
const hydrateCycleData = async () => {
|
||
try {
|
||
if (cancelled) return;
|
||
|
||
const [cycleData, sessionData, historyData] = await Promise.all([
|
||
loadActiveFastingCycle(),
|
||
loadCurrentCycleSession(),
|
||
loadCycleHistory(),
|
||
]);
|
||
|
||
if (cancelled) return;
|
||
|
||
dispatch(hydrateFastingCycle({
|
||
activeCycle: cycleData,
|
||
currentCycleSession: sessionData,
|
||
cycleHistory: historyData,
|
||
}));
|
||
} catch (error) {
|
||
console.warn('加载周期性断食数据失败', error);
|
||
}
|
||
};
|
||
|
||
hydrateCycleData();
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, [dispatch]);
|
||
|
||
// 保存周期性断食数据,增加错误处理
|
||
useEffect(() => {
|
||
const saveCycleData = async () => {
|
||
try {
|
||
if (activeCycle) {
|
||
await saveActiveFastingCycle(activeCycle);
|
||
} else {
|
||
await saveActiveFastingCycle(null);
|
||
}
|
||
} catch (error) {
|
||
console.error('保存周期性断食计划失败', error);
|
||
// TODO: 可以在这里添加用户提示
|
||
}
|
||
};
|
||
saveCycleData();
|
||
}, [activeCycle]);
|
||
|
||
useEffect(() => {
|
||
const saveSessionData = async () => {
|
||
try {
|
||
if (currentCycleSession) {
|
||
await saveCurrentCycleSession(currentCycleSession);
|
||
} else {
|
||
await saveCurrentCycleSession(null);
|
||
}
|
||
} catch (error) {
|
||
console.error('保存断食会话失败', error);
|
||
// TODO: 可以在这里添加用户提示
|
||
}
|
||
};
|
||
saveSessionData();
|
||
}, [currentCycleSession]);
|
||
|
||
useEffect(() => {
|
||
const saveHistoryData = async () => {
|
||
try {
|
||
await saveCycleHistory(cycleHistory);
|
||
} catch (error) {
|
||
console.error('保存断食历史失败', error);
|
||
// TODO: 可以在这里添加用户提示
|
||
}
|
||
};
|
||
saveHistoryData();
|
||
}, [cycleHistory]);
|
||
|
||
// 使用单次断食通知管理 hook
|
||
const {
|
||
isReady: notificationsReady,
|
||
isLoading: notificationsLoading,
|
||
error: notificationError,
|
||
notificationIds,
|
||
lastSyncTime,
|
||
verifyAndSync,
|
||
forceSync,
|
||
clearError,
|
||
} = useFastingNotifications(activeSchedule, activePlan);
|
||
|
||
// 使用周期性断食通知管理 hook
|
||
const {
|
||
isReady: cycleNotificationsReady,
|
||
isLoading: cycleNotificationsLoading,
|
||
error: cycleNotificationError,
|
||
lastSyncTime: cycleLastSyncTime,
|
||
verifyAndSync: verifyAndSyncCycle,
|
||
forceSync: forceSyncCycle,
|
||
clearError: clearCycleError,
|
||
} = useFastingCycleNotifications(activeCycle, currentCycleSession, currentCyclePlan);
|
||
|
||
// 每次进入页面时验证通知
|
||
// 添加节流机制,避免频繁触发验证
|
||
const lastVerifyTimeRef = React.useRef<number>(0);
|
||
const lastCycleVerifyTimeRef = React.useRef<number>(0);
|
||
|
||
useFocusEffect(
|
||
useCallback(() => {
|
||
const now = Date.now();
|
||
const timeSinceLastVerify = now - lastVerifyTimeRef.current;
|
||
|
||
// 如果距离上次验证不足 30 秒,跳过本次验证
|
||
if (timeSinceLastVerify < 30000) {
|
||
return;
|
||
}
|
||
|
||
lastVerifyTimeRef.current = now;
|
||
verifyAndSync();
|
||
}, [verifyAndSync])
|
||
);
|
||
|
||
useFocusEffect(
|
||
useCallback(() => {
|
||
const now = Date.now();
|
||
const timeSinceLastVerify = now - lastCycleVerifyTimeRef.current;
|
||
|
||
// 如果距离上次验证不足 30 秒,跳过本次验证
|
||
if (timeSinceLastVerify < 30000) {
|
||
return;
|
||
}
|
||
|
||
lastCycleVerifyTimeRef.current = now;
|
||
verifyAndSyncCycle();
|
||
}, [verifyAndSyncCycle])
|
||
);
|
||
|
||
// 使用统一的当前断食时间
|
||
const scheduleStart = useMemo(() => {
|
||
if (currentTimes) {
|
||
return new Date(currentTimes.startISO);
|
||
}
|
||
return undefined;
|
||
}, [currentTimes]);
|
||
|
||
const scheduleEnd = useMemo(() => {
|
||
if (currentTimes) {
|
||
return new Date(currentTimes.endISO);
|
||
}
|
||
return undefined;
|
||
}, [currentTimes]);
|
||
|
||
const phase = getFastingPhase(scheduleStart ?? null, scheduleEnd ?? null);
|
||
const countdownTarget = phase === 'fasting' ? scheduleEnd : scheduleStart;
|
||
const { formatted: countdownValue } = useCountdown({ target: countdownTarget ?? null });
|
||
|
||
const progress = useMemo(() => {
|
||
if (!scheduleStart || !scheduleEnd) return 0;
|
||
const total = scheduleEnd.getTime() - scheduleStart.getTime();
|
||
if (total <= 0) return 0;
|
||
const now = Date.now();
|
||
if (now <= scheduleStart.getTime()) return 0;
|
||
if (now >= scheduleEnd.getTime()) return 1;
|
||
return (now - scheduleStart.getTime()) / total;
|
||
}, [scheduleStart, scheduleEnd]);
|
||
|
||
const displayWindow = buildDisplayWindow(scheduleStart ?? null, scheduleEnd ?? null);
|
||
|
||
const [showPicker, setShowPicker] = useState(false);
|
||
|
||
// 显示通知错误(如果有)
|
||
useEffect(() => {
|
||
if (notificationError) {
|
||
console.warn('断食通知错误:', notificationError);
|
||
// 可以在这里添加用户提示,比如 Toast 或 Snackbar
|
||
}
|
||
}, [notificationError]);
|
||
|
||
const recommendedDate = useMemo(() => {
|
||
const planToUse = currentPlan || defaultPlan;
|
||
return getRecommendedStart(planToUse);
|
||
}, [currentPlan, defaultPlan]);
|
||
|
||
// 调试信息(开发环境)
|
||
useEffect(() => {
|
||
if (__DEV__ && lastSyncTime) {
|
||
console.log('单次断食通知状态:', {
|
||
ready: notificationsReady,
|
||
loading: notificationsLoading,
|
||
error: notificationError,
|
||
notificationIds,
|
||
lastSyncTime,
|
||
schedule: activeSchedule?.startISO,
|
||
plan: activePlan?.id,
|
||
});
|
||
}
|
||
}, [notificationsReady, notificationsLoading, notificationError, notificationIds, lastSyncTime, activeSchedule?.startISO, activePlan?.id]);
|
||
|
||
useEffect(() => {
|
||
if (__DEV__ && cycleLastSyncTime) {
|
||
console.log('周期性断食通知状态:', {
|
||
ready: cycleNotificationsReady,
|
||
loading: cycleNotificationsLoading,
|
||
error: cycleNotificationError,
|
||
lastSyncTime: cycleLastSyncTime,
|
||
cycle: activeCycle?.planId,
|
||
session: currentCycleSession?.cycleDate,
|
||
});
|
||
}
|
||
}, [cycleNotificationsReady, cycleNotificationsLoading, cycleNotificationError, cycleLastSyncTime, activeCycle?.planId, currentCycleSession?.cycleDate]);
|
||
|
||
// 周期性断食的自动续订逻辑
|
||
// 移除1小时限制,但需要用户手动确认开始下一轮周期
|
||
useEffect(() => {
|
||
if (!currentCycleSession || !activeCycle || !currentCyclePlan) return;
|
||
if (!activeCycle.enabled) return; // 如果周期已暂停,不自动完成
|
||
if (phase !== 'completed') return;
|
||
|
||
const end = dayjs(currentCycleSession.endISO);
|
||
if (!end.isValid()) return;
|
||
|
||
const now = dayjs();
|
||
if (now.isBefore(end)) return;
|
||
|
||
// 检查当前会话是否已经标记为完成
|
||
if (currentCycleSession.completed) {
|
||
if (__DEV__) {
|
||
console.log('当前会话已完成,跳过自动完成');
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (__DEV__) {
|
||
console.log('自动完成当前断食周期:', {
|
||
cycleDate: currentCycleSession.cycleDate,
|
||
planId: currentCycleSession.planId,
|
||
endTime: end.format('YYYY-MM-DD HH:mm'),
|
||
timeSinceEnd: now.diff(end, 'minute') + '分钟',
|
||
});
|
||
}
|
||
|
||
// 完成当前周期并创建下一个周期
|
||
// 这会自动创建下一天的会话,不需要用户手动操作
|
||
dispatch(completeCurrentCycleSession());
|
||
}, [dispatch, currentCycleSession, activeCycle, currentCyclePlan, phase]);
|
||
|
||
// 保留原有的单次断食自动续订逻辑(向后兼容)
|
||
useEffect(() => {
|
||
if (!activeSchedule || !activePlan) return;
|
||
if (phase !== 'completed') return;
|
||
|
||
const start = dayjs(activeSchedule.startISO);
|
||
const end = dayjs(activeSchedule.endISO);
|
||
if (!start.isValid() || !end.isValid()) return;
|
||
|
||
const now = dayjs();
|
||
if (now.isBefore(end)) return;
|
||
|
||
// 检查是否在短时间内已经续订过,避免重复续订
|
||
const timeSinceEnd = now.diff(end, 'minute');
|
||
if (timeSinceEnd > 60) {
|
||
// 如果周期结束超过1小时,说明用户可能不再需要自动续订
|
||
if (__DEV__) {
|
||
console.log('断食周期结束超过1小时,不自动续订');
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 使用每日固定时间计算下一个周期
|
||
// 保持原始的开始时间(小时和分钟),只增加日期
|
||
const originalStartHour = start.hour();
|
||
const originalStartMinute = start.minute();
|
||
|
||
// 计算下一个开始时间:明天的同一时刻
|
||
let nextStart = now.startOf('day').hour(originalStartHour).minute(originalStartMinute);
|
||
|
||
// 如果计算出的时间在当前时间之前,则使用后天的同一时刻
|
||
if (nextStart.isBefore(now)) {
|
||
nextStart = nextStart.add(1, 'day');
|
||
}
|
||
|
||
const nextEnd = nextStart.add(activePlan.fastingHours, 'hour');
|
||
|
||
if (__DEV__) {
|
||
console.log('自动续订断食周期:', {
|
||
oldStart: start.format('YYYY-MM-DD HH:mm'),
|
||
oldEnd: end.format('YYYY-MM-DD HH:mm'),
|
||
nextStart: nextStart.format('YYYY-MM-DD HH:mm'),
|
||
nextEnd: nextEnd.format('YYYY-MM-DD HH:mm'),
|
||
});
|
||
}
|
||
|
||
dispatch(rescheduleActivePlan({
|
||
start: nextStart.toISOString(),
|
||
origin: 'auto',
|
||
}));
|
||
}, [dispatch, activeSchedule, activePlan, phase]);
|
||
|
||
const handleAdjustStart = () => {
|
||
setShowPicker(true);
|
||
};
|
||
|
||
const handleConfirmStart = (date: Date) => {
|
||
// 如果没有当前计划,使用默认计划
|
||
const planToUse = currentPlan || defaultPlan;
|
||
|
||
// 如果处于周期性模式,更新周期性时间
|
||
if (isInCycleMode && activeCycle) {
|
||
const hour = date.getHours();
|
||
const minute = date.getMinutes();
|
||
dispatch(updateFastingCycleTime({ startHour: hour, startMinute: minute }));
|
||
} else if (activeSchedule) {
|
||
// 单次断食模式,重新安排
|
||
dispatch(rescheduleActivePlan({ start: date.toISOString(), origin: 'manual' }));
|
||
} else {
|
||
// 创建新的单次断食计划
|
||
dispatch(scheduleFastingPlan({ planId: planToUse.id, start: date.toISOString(), origin: 'manual' }));
|
||
}
|
||
};
|
||
|
||
const handleSelectPlan = (plan: FastingPlan) => {
|
||
router.push(`${ROUTES.FASTING_PLAN_DETAIL}/${plan.id}`);
|
||
};
|
||
|
||
const handleViewMeal = () => {
|
||
router.push(ROUTES.FOOD_LIBRARY);
|
||
};
|
||
|
||
const handleResetPlan = () => {
|
||
// 如果没有活跃计划,不执行任何操作
|
||
if (!currentPlan) return;
|
||
|
||
if (isInCycleMode) {
|
||
// 停止周期性断食
|
||
dispatch(stopFastingCycle());
|
||
} else {
|
||
// 清除单次断食
|
||
dispatch(clearActiveSchedule());
|
||
}
|
||
};
|
||
|
||
// 新增:启动周期性断食
|
||
const handleStartCycle = async (plan: FastingPlan, startHour: number, startMinute: number) => {
|
||
try {
|
||
dispatch(startFastingCycle({
|
||
planId: plan.id,
|
||
startHour,
|
||
startMinute
|
||
}));
|
||
|
||
// 等待数据保存完成
|
||
// 注意:dispatch 是同步的,但我们需要确保数据被正确保存
|
||
console.log('周期性断食计划已启动', {
|
||
planId: plan.id,
|
||
startHour,
|
||
startMinute
|
||
});
|
||
} catch (error) {
|
||
console.error('启动周期性断食失败', error);
|
||
// TODO: 添加用户错误提示
|
||
}
|
||
};
|
||
|
||
// 新增:暂停/恢复周期性断食
|
||
const handleToggleCycle = async () => {
|
||
if (!activeCycle) return;
|
||
|
||
try {
|
||
if (activeCycle.enabled) {
|
||
dispatch(pauseFastingCycle());
|
||
console.log('周期性断食已暂停');
|
||
} else {
|
||
dispatch(resumeFastingCycle());
|
||
console.log('周期性断食已恢复');
|
||
}
|
||
} catch (error) {
|
||
console.error('切换周期性断食状态失败', error);
|
||
// TODO: 添加用户错误提示
|
||
}
|
||
};
|
||
|
||
return (
|
||
<View style={[styles.safeArea]}>
|
||
<ScrollView
|
||
ref={scrollViewRef}
|
||
contentContainerStyle={[styles.scrollContainer, {
|
||
paddingTop: insets.top,
|
||
paddingBottom: 120
|
||
}]}
|
||
showsVerticalScrollIndicator={false}
|
||
>
|
||
<View style={styles.headerRow}>
|
||
<Text style={styles.screenTitle}>轻断食</Text>
|
||
<Text style={styles.screenSubtitle}>改善代谢 · 科学控脂 · 饮食不焦虑</Text>
|
||
</View>
|
||
|
||
{/* 通知错误提示 */}
|
||
<NotificationErrorAlert
|
||
error={notificationError}
|
||
onRetry={forceSync}
|
||
onDismiss={clearError}
|
||
/>
|
||
|
||
{currentPlan ? (
|
||
<FastingOverviewCard
|
||
plan={currentPlan}
|
||
phaseLabel={getPhaseLabel(phase)}
|
||
countdownLabel={phase === 'fasting' ? '距离进食还有' : '距离断食还有'}
|
||
countdownValue={countdownValue}
|
||
startDayLabel={displayWindow.startDayLabel}
|
||
startTimeLabel={displayWindow.startTimeLabel}
|
||
endDayLabel={displayWindow.endDayLabel}
|
||
endTimeLabel={displayWindow.endTimeLabel}
|
||
onAdjustStartPress={handleAdjustStart}
|
||
onViewMealsPress={handleViewMeal}
|
||
onResetPress={handleResetPlan}
|
||
progress={progress}
|
||
/>
|
||
) : (
|
||
<View style={styles.emptyStateCard}>
|
||
<View style={styles.emptyStateHeader}>
|
||
<Text style={styles.emptyStateTitle}>开始您的断食之旅</Text>
|
||
<Text style={styles.emptyStateSubtitle}>选择适合的断食计划,开启健康生活</Text>
|
||
</View>
|
||
|
||
<View style={styles.emptyStateContent}>
|
||
<View style={styles.emptyStateIcon}>
|
||
<Ionicons name="time-outline" size={48} color="#6F7D87" />
|
||
</View>
|
||
<Text style={styles.emptyStateDescription}>
|
||
断食可以帮助改善代谢、控制体重,让身体获得充分的休息和修复时间。
|
||
</Text>
|
||
<Text style={styles.defaultPlanInfo}>
|
||
默认使用 14-10 热门计划(断食14小时,进食10小时)
|
||
</Text>
|
||
</View>
|
||
|
||
<View style={styles.emptyStateActions}>
|
||
<TouchableOpacity
|
||
style={styles.primaryButton}
|
||
onPress={() => setShowPicker(true)}
|
||
activeOpacity={0.8}
|
||
>
|
||
<Text style={styles.primaryButtonText}>开始断食计划</Text>
|
||
</TouchableOpacity>
|
||
|
||
<TouchableOpacity
|
||
style={styles.secondaryButton}
|
||
onPress={() => {
|
||
// 滚动到计划列表
|
||
setTimeout(() => {
|
||
scrollViewRef.current?.scrollTo({ y: 600, animated: true });
|
||
}, 100);
|
||
}}
|
||
activeOpacity={0.8}
|
||
>
|
||
<Text style={styles.secondaryButtonText}>浏览断食方案</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{currentPlan && (
|
||
<View style={styles.highlightCard}>
|
||
<View style={styles.highlightHeader}>
|
||
<Text style={styles.highlightTitle}>计划亮点</Text>
|
||
<Text style={styles.highlightSubtitle}>{currentPlan.subtitle}</Text>
|
||
</View>
|
||
{currentPlan.highlights.map((highlight) => (
|
||
<View key={highlight} style={styles.highlightItem}>
|
||
<View style={[styles.highlightDot, { backgroundColor: currentPlan.theme.accent }]} />
|
||
<Text style={styles.highlightText}>{highlight}</Text>
|
||
</View>
|
||
))}
|
||
<View style={styles.resetRow}>
|
||
<Text style={styles.resetHint}>
|
||
如果计划与作息不符,可重新选择方案或调整开始时间。
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
<FastingPlanList
|
||
plans={FASTING_PLANS}
|
||
activePlanId={activePlan?.id ?? currentPlan?.id}
|
||
onSelectPlan={handleSelectPlan}
|
||
/>
|
||
|
||
{/* 参考文献入口 */}
|
||
<View style={styles.referencesSection}>
|
||
<TouchableOpacity
|
||
style={styles.referencesButton}
|
||
onPress={() => router.push(ROUTES.FASTING_REFERENCES)}
|
||
activeOpacity={0.8}
|
||
>
|
||
{isLiquidGlassAvailable() ? (
|
||
<GlassView
|
||
style={styles.referencesGlass}
|
||
glassEffectStyle="clear"
|
||
tintColor="rgba(46, 49, 66, 0.05)"
|
||
isInteractive={true}
|
||
>
|
||
<View style={styles.referencesContent}>
|
||
<Ionicons name="library-outline" size={20} color="#2E3142" />
|
||
<Text style={styles.referencesText}>参考文献与医学来源</Text>
|
||
<Ionicons name="chevron-forward" size={16} color="#6F7D87" />
|
||
</View>
|
||
</GlassView>
|
||
) : (
|
||
<View style={[styles.referencesGlass, styles.referencesFallback]}>
|
||
<View style={styles.referencesContent}>
|
||
<Ionicons name="library-outline" size={20} color="#2E3142" />
|
||
<Text style={styles.referencesText}>参考文献与医学来源</Text>
|
||
<Ionicons name="chevron-forward" size={16} color="#6F7D87" />
|
||
</View>
|
||
</View>
|
||
)}
|
||
</TouchableOpacity>
|
||
</View>
|
||
</ScrollView>
|
||
|
||
<FastingStartPickerModal
|
||
visible={showPicker}
|
||
onClose={() => setShowPicker(false)}
|
||
initialDate={scheduleStart}
|
||
recommendedDate={recommendedDate}
|
||
onConfirm={handleConfirmStart}
|
||
/>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
safeArea: {
|
||
flex: 1,
|
||
backgroundColor: 'white'
|
||
},
|
||
scrollContainer: {
|
||
paddingHorizontal: 20,
|
||
paddingTop: 12,
|
||
|
||
},
|
||
headerRow: {
|
||
marginBottom: 20,
|
||
},
|
||
screenTitle: {
|
||
fontSize: 28,
|
||
fontWeight: '800',
|
||
color: '#2E3142',
|
||
marginBottom: 6,
|
||
},
|
||
screenSubtitle: {
|
||
fontSize: 14,
|
||
color: '#6F7D87',
|
||
fontWeight: '500',
|
||
},
|
||
highlightCard: {
|
||
marginTop: 28,
|
||
padding: 20,
|
||
borderRadius: 24,
|
||
backgroundColor: '#FFFFFF',
|
||
shadowColor: '#000',
|
||
shadowOffset: { width: 0, height: 12 },
|
||
shadowOpacity: 0.06,
|
||
shadowRadius: 20,
|
||
elevation: 4,
|
||
},
|
||
highlightHeader: {
|
||
marginBottom: 12,
|
||
},
|
||
highlightTitle: {
|
||
fontSize: 18,
|
||
fontWeight: '700',
|
||
color: '#2E3142',
|
||
},
|
||
highlightSubtitle: {
|
||
fontSize: 13,
|
||
color: '#6F7D87',
|
||
marginTop: 6,
|
||
},
|
||
highlightItem: {
|
||
flexDirection: 'row',
|
||
alignItems: 'flex-start',
|
||
marginBottom: 10,
|
||
},
|
||
highlightDot: {
|
||
width: 6,
|
||
height: 6,
|
||
borderRadius: 3,
|
||
marginTop: 7,
|
||
marginRight: 10,
|
||
},
|
||
highlightText: {
|
||
flex: 1,
|
||
fontSize: 14,
|
||
color: '#4A5460',
|
||
lineHeight: 20,
|
||
},
|
||
resetRow: {
|
||
marginTop: 16,
|
||
},
|
||
resetHint: {
|
||
fontSize: 12,
|
||
color: '#8A96A3',
|
||
},
|
||
emptyStateCard: {
|
||
borderRadius: 28,
|
||
padding: 24,
|
||
backgroundColor: '#FFFFFF',
|
||
shadowColor: '#000',
|
||
shadowOffset: { width: 0, height: 16 },
|
||
shadowOpacity: 0.08,
|
||
shadowRadius: 24,
|
||
elevation: 6,
|
||
marginBottom: 20,
|
||
},
|
||
emptyStateHeader: {
|
||
alignItems: 'center',
|
||
marginBottom: 24,
|
||
},
|
||
emptyStateTitle: {
|
||
fontSize: 24,
|
||
fontWeight: '700',
|
||
color: '#2E3142',
|
||
marginBottom: 8,
|
||
textAlign: 'center',
|
||
},
|
||
emptyStateSubtitle: {
|
||
fontSize: 16,
|
||
color: '#6F7D87',
|
||
textAlign: 'center',
|
||
lineHeight: 22,
|
||
},
|
||
emptyStateContent: {
|
||
alignItems: 'center',
|
||
marginBottom: 32,
|
||
},
|
||
emptyStateIcon: {
|
||
width: 80,
|
||
height: 80,
|
||
borderRadius: 40,
|
||
backgroundColor: 'rgba(111, 125, 135, 0.1)',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
marginBottom: 20,
|
||
},
|
||
emptyStateDescription: {
|
||
fontSize: 15,
|
||
color: '#4A5460',
|
||
textAlign: 'center',
|
||
lineHeight: 22,
|
||
paddingHorizontal: 20,
|
||
marginBottom: 12,
|
||
},
|
||
defaultPlanInfo: {
|
||
fontSize: 13,
|
||
color: '#8A96A3',
|
||
textAlign: 'center',
|
||
lineHeight: 18,
|
||
paddingHorizontal: 20,
|
||
fontStyle: 'italic',
|
||
},
|
||
emptyStateActions: {
|
||
gap: 12,
|
||
},
|
||
primaryButton: {
|
||
backgroundColor: '#2E3142',
|
||
paddingVertical: 16,
|
||
paddingHorizontal: 24,
|
||
borderRadius: 24,
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
primaryButtonText: {
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
color: '#FFFFFF',
|
||
},
|
||
secondaryButton: {
|
||
backgroundColor: 'transparent',
|
||
paddingVertical: 16,
|
||
paddingHorizontal: 24,
|
||
borderRadius: 24,
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
borderWidth: 1.5,
|
||
borderColor: '#2E3142',
|
||
},
|
||
secondaryButtonText: {
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
color: '#2E3142',
|
||
},
|
||
referencesSection: {
|
||
marginTop: 24,
|
||
marginBottom: 20,
|
||
},
|
||
referencesButton: {
|
||
borderRadius: 20,
|
||
overflow: 'hidden',
|
||
},
|
||
referencesGlass: {
|
||
borderRadius: 20,
|
||
paddingVertical: 16,
|
||
paddingHorizontal: 20,
|
||
},
|
||
referencesFallback: {
|
||
backgroundColor: 'rgba(246, 248, 250, 0.8)',
|
||
borderWidth: 1,
|
||
borderColor: 'rgba(46, 49, 66, 0.1)',
|
||
},
|
||
referencesContent: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
},
|
||
referencesText: {
|
||
flex: 1,
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
color: '#2E3142',
|
||
marginLeft: 12,
|
||
marginRight: 8,
|
||
},
|
||
});
|