import { FASTING_STORAGE_KEYS } from '@/constants/Fasting'; import type { FastingSchedule } from '@/store/fastingSlice'; import AsyncStorage from '@/utils/kvStore'; import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'; import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; dayjs.extend(duration); dayjs.extend(isSameOrAfter); dayjs.extend(isSameOrBefore); export type FastingPhase = 'upcoming' | 'fasting' | 'completed'; export const calculateFastingWindow = (start: Date, fastingHours: number) => { const startDate = dayjs(start).second(0).millisecond(0); const endDate = startDate.add(fastingHours, 'hour'); return { start: startDate.toDate(), end: endDate.toDate(), }; }; export const getFastingPhase = (start?: Date | null, end?: Date | null, now: Date = new Date()): FastingPhase => { if (!start || !end) { return 'completed'; } const nowJs = dayjs(now); const startJs = dayjs(start); const endJs = dayjs(end); if (nowJs.isBefore(startJs)) { return 'upcoming'; } if (nowJs.isSameOrAfter(startJs) && nowJs.isBefore(endJs)) { return 'fasting'; } return 'completed'; }; export const getPhaseLabel = (phase: FastingPhase) => { switch (phase) { case 'fasting': return '断食中'; case 'upcoming': return '可饮食'; case 'completed': return '可饮食'; default: return '可饮食'; } }; export const formatDayDescriptor = (date: Date | null | undefined, now: Date = new Date()) => { if (!date) return '--'; const target = dayjs(date); const today = dayjs(now).startOf('day'); if (target.isSame(today, 'day')) { return '今天'; } if (target.isSame(today.subtract(1, 'day'), 'day')) { return '昨天'; } if (target.isSame(today.add(1, 'day'), 'day')) { return '明天'; } return target.format('MM-DD'); }; export const formatTime = (date: Date | null | undefined) => { if (!date) return '--:--'; return dayjs(date).format('HH:mm'); }; export const formatCountdown = (target: Date | null | undefined, now: Date = new Date()) => { if (!target) return '--:--:--'; const diff = dayjs(target).diff(now); if (diff <= 0) { return '00:00:00'; } const dur = dayjs.duration(diff); const hours = String(Math.floor(dur.asHours())).padStart(2, '0'); const minutes = String(dur.minutes()).padStart(2, '0'); const seconds = String(dur.seconds()).padStart(2, '0'); return `${hours}:${minutes}:${seconds}`; }; export const buildDisplayWindow = (start?: Date | null, end?: Date | null) => { return { startDayLabel: formatDayDescriptor(start ?? null), startTimeLabel: formatTime(start ?? null), endDayLabel: formatDayDescriptor(end ?? null), endTimeLabel: formatTime(end ?? null), }; }; export const loadActiveFastingSchedule = async (): Promise => { try { const stored = await AsyncStorage.getItem(FASTING_STORAGE_KEYS.activeSchedule); if (!stored) return null; const parsed = JSON.parse(stored) as Partial; if ( !parsed || typeof parsed.planId !== 'string' || typeof parsed.startISO !== 'string' || typeof parsed.endISO !== 'string' ) { return null; } return { planId: parsed.planId, startISO: parsed.startISO, endISO: parsed.endISO, createdAtISO: parsed.createdAtISO ?? parsed.startISO, updatedAtISO: parsed.updatedAtISO ?? parsed.endISO, origin: parsed.origin ?? 'manual', }; } catch (error) { console.warn('读取断食计划失败', error); return null; } }; export const persistActiveFastingSchedule = async (schedule: FastingSchedule | null) => { try { if (schedule) { await AsyncStorage.setItem(FASTING_STORAGE_KEYS.activeSchedule, JSON.stringify(schedule)); } else { await AsyncStorage.removeItem(FASTING_STORAGE_KEYS.activeSchedule); } } catch (error) { console.warn('保存断食计划失败', error); } }; export const loadPreferredPlanId = async (): Promise => { try { return await AsyncStorage.getItem(FASTING_STORAGE_KEYS.preferredPlanId); } catch (error) { console.warn('加载断食首选计划ID失败', error); return null; } }; export const savePreferredPlanId = async (planId: string) => { try { await AsyncStorage.setItem(FASTING_STORAGE_KEYS.preferredPlanId, planId); } catch (error) { console.warn('保存断食首选计划ID失败', error); } }; export type FastingNotificationIds = { preStartId?: string | null; // 开始前30分钟 startId?: string | null; // 开始时 preEndId?: string | null; // 结束前30分钟 endId?: string | null; // 结束时 }; export const getFastingNotificationsRegistered = async (): Promise => { try { const value = await AsyncStorage.getItem(FASTING_STORAGE_KEYS.notificationsRegistered); return value === 'true'; } catch (error) { console.warn('读取断食通知注册状态失败', error); return false; } }; export const setFastingNotificationsRegistered = async (registered: boolean) => { try { if (registered) { await AsyncStorage.setItem(FASTING_STORAGE_KEYS.notificationsRegistered, 'true'); } else { await AsyncStorage.removeItem(FASTING_STORAGE_KEYS.notificationsRegistered); } } catch (error) { console.warn('更新断食通知注册状态失败', error); } }; export const loadStoredFastingNotificationIds = async (): Promise => { try { const [preStartId, startId, preEndId, endId] = await Promise.all([ AsyncStorage.getItem(FASTING_STORAGE_KEYS.preStartNotificationId), AsyncStorage.getItem(FASTING_STORAGE_KEYS.startNotificationId), AsyncStorage.getItem(FASTING_STORAGE_KEYS.preEndNotificationId), AsyncStorage.getItem(FASTING_STORAGE_KEYS.endNotificationId), ]); return { preStartId: preStartId ?? undefined, startId: startId ?? undefined, preEndId: preEndId ?? undefined, endId: endId ?? undefined, }; } catch (error) { console.warn('读取断食通知ID失败', error); return {}; } }; export const saveFastingNotificationIds = async (ids: FastingNotificationIds) => { try { // 保存开始前30分钟通知ID if (ids.preStartId) { await AsyncStorage.setItem(FASTING_STORAGE_KEYS.preStartNotificationId, ids.preStartId); } else { await AsyncStorage.removeItem(FASTING_STORAGE_KEYS.preStartNotificationId); } // 保存开始时通知ID if (ids.startId) { await AsyncStorage.setItem(FASTING_STORAGE_KEYS.startNotificationId, ids.startId); } else { await AsyncStorage.removeItem(FASTING_STORAGE_KEYS.startNotificationId); } // 保存结束前30分钟通知ID if (ids.preEndId) { await AsyncStorage.setItem(FASTING_STORAGE_KEYS.preEndNotificationId, ids.preEndId); } else { await AsyncStorage.removeItem(FASTING_STORAGE_KEYS.preEndNotificationId); } // 保存结束时通知ID if (ids.endId) { await AsyncStorage.setItem(FASTING_STORAGE_KEYS.endNotificationId, ids.endId); } else { await AsyncStorage.removeItem(FASTING_STORAGE_KEYS.endNotificationId); } } catch (error) { console.warn('保存断食通知ID失败', error); } }; export const clearFastingNotificationIds = async () => { try { await Promise.all([ AsyncStorage.removeItem(FASTING_STORAGE_KEYS.preStartNotificationId), AsyncStorage.removeItem(FASTING_STORAGE_KEYS.startNotificationId), AsyncStorage.removeItem(FASTING_STORAGE_KEYS.preEndNotificationId), AsyncStorage.removeItem(FASTING_STORAGE_KEYS.endNotificationId), ]); } catch (error) { console.warn('清除断食通知ID失败', error); } };