import { FloatingSelectionCard } from '@/components/ui/FloatingSelectionCard'; import { Colors } from '@/constants/Colors'; import { useColorScheme } from '@/hooks/useColorScheme'; import dayjs from 'dayjs'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import WheelPickerExpo from 'react-native-wheel-picker-expo'; type FastingStartPickerModalProps = { visible: boolean; onClose: () => void; initialDate?: Date | null; recommendedDate?: Date | null; onConfirm: (date: Date) => void; }; type DayOption = { label: string; offset: number; }; const buildDayOptions = (now: dayjs.Dayjs): DayOption[] => ([ { offset: -1, label: '昨天' }, { offset: 0, label: '今天' }, { offset: 1, label: '明天' }, ].map((item) => ({ ...item, label: item.offset === -1 ? '昨天' : item.offset === 0 ? '今天' : '明天', }))); const HOURS = Array.from({ length: 24 }, (_, i) => i); const MINUTES = Array.from({ length: 12 }, (_, i) => i * 5); export function FastingStartPickerModal({ visible, onClose, initialDate, recommendedDate, onConfirm, }: FastingStartPickerModalProps) { const theme = useColorScheme() ?? 'light'; const colors = Colors[theme]; const now = useMemo(() => dayjs(), []); const dayOptions = useMemo(() => buildDayOptions(now), [now]); const deriveInitialIndexes = (source?: Date | null) => { const seed = source ? dayjs(source) : now; const dayDiff = seed.startOf('day').diff(now.startOf('day'), 'day'); const dayIndex = dayOptions.findIndex((option) => option.offset === dayDiff); const hourIndex = HOURS.findIndex((hour) => hour === seed.hour()); const snappedMinute = seed.minute() - (seed.minute() % 5); const minuteIndex = MINUTES.findIndex((minute) => minute === snappedMinute); return { dayIndex: dayIndex === -1 ? 1 : dayIndex, hourIndex: hourIndex === -1 ? now.hour() : hourIndex, minuteIndex: minuteIndex === -1 ? Math.max(0, Math.min(MINUTES.length - 1, Math.floor(seed.minute() / 5))) : minuteIndex, }; }; const defaultBaseRef = useRef(new Date()); const baseDate = useMemo( () => initialDate ?? recommendedDate ?? defaultBaseRef.current, [initialDate, recommendedDate] ); const baseTimestamp = baseDate.getTime(); const [{ dayIndex, hourIndex, minuteIndex }, setIndexes] = useState(() => deriveInitialIndexes(baseDate) ); const [pickerKey, setPickerKey] = useState(0); const lastAppliedTimestamp = useRef(null); const wasVisibleRef = useRef(false); useEffect(() => { if (!visible) { wasVisibleRef.current = false; return; } const shouldReset = !wasVisibleRef.current || lastAppliedTimestamp.current !== baseTimestamp; if (shouldReset) { const nextIndexes = deriveInitialIndexes(baseDate); setIndexes(nextIndexes); setPickerKey((prev) => prev + 1); lastAppliedTimestamp.current = baseTimestamp; } wasVisibleRef.current = true; // eslint-disable-next-line react-hooks/exhaustive-deps }, [visible, baseTimestamp]); const handleConfirm = () => { const selectedDay = dayOptions[dayIndex] ?? dayOptions[1]; const base = now.startOf('day').add(selectedDay?.offset ?? 0, 'day'); const hour = HOURS[hourIndex] ?? now.hour(); const minute = MINUTES[minuteIndex] ?? 0; const result = base.hour(hour).minute(minute).second(0).millisecond(0); onConfirm(result.toDate()); onClose(); }; const handleUseRecommended = () => { if (!recommendedDate) return; setIndexes(deriveInitialIndexes(recommendedDate)); setPickerKey((prev) => prev + 1); lastAppliedTimestamp.current = recommendedDate.getTime(); }; const textStyle = { fontSize: 18, fontWeight: '600' as const, color: '#2E3142', }; // 自定义渲染函数,用于应用文本样式 const renderItem = ({ fontSize, label, fontColor, textAlign }: { fontSize: number; label: string; fontColor: string; textAlign: 'center' | 'auto' | 'left' | 'right' | 'justify' }) => ( {label} ); return ( ({ label: item.label, value: item.offset }))} onChange={({ index }) => setIndexes((prev) => ({ ...prev, dayIndex: index }))} backgroundColor="transparent" renderItem={renderItem} haptics /> ({ label: hour.toString().padStart(2, '0'), value: hour }))} onChange={({ index }) => setIndexes((prev) => ({ ...prev, hourIndex: index }))} backgroundColor="transparent" renderItem={renderItem} haptics /> ({ label: minute.toString().padStart(2, '0'), value: minute, }))} onChange={({ index }) => setIndexes((prev) => ({ ...prev, minuteIndex: index }))} backgroundColor="transparent" renderItem={renderItem} haptics /> 使用推荐时间 确定 ); } const styles = StyleSheet.create({ pickerRow: { flexDirection: 'row', justifyContent: 'space-between', width: '100%', marginBottom: 24, }, footerRow: { flexDirection: 'row', justifyContent: 'space-between', width: '100%', }, recommendButton: { flex: 1, borderWidth: 1.2, borderRadius: 24, paddingVertical: 12, marginRight: 12, alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(255,255,255,0.95)', }, recommendButtonText: { fontSize: 14, fontWeight: '600', }, confirmButton: { flex: 1, borderRadius: 24, paddingVertical: 12, alignItems: 'center', justifyContent: 'center', }, confirmButtonText: { fontSize: 15, fontWeight: '700', color: '#fff', }, });