实现完整的周期性断食计划系统,支持每日自动续订和通知管理: - 新增周期性断食状态管理(activeCycle、currentCycleSession、cycleHistory) - 实现周期性断食会话的自动完成和续订逻辑 - 添加独立的周期性断食通知系统,避免与单次断食通知冲突 - 支持暂停/恢复周期性断食计划 - 添加周期性断食数据持久化和水合功能 - 优化断食界面,优先显示周期性断食信息 - 新增空状态引导界面,提升用户体验 - 保持单次断食功能向后兼容
175 lines
5.3 KiB
TypeScript
175 lines
5.3 KiB
TypeScript
import { FastingPlan } from '@/constants/Fasting';
|
|
import { FastingCycleNotificationManager } from '@/services/fastingCycleNotifications';
|
|
import { FastingCycle, FastingCycleSession } from '@/store/fastingSlice';
|
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
|
export interface UseFastingCycleNotificationsState {
|
|
isReady: boolean;
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
lastSyncTime: Date | null;
|
|
}
|
|
|
|
export interface UseFastingCycleNotificationsActions {
|
|
verifyAndSync: () => Promise<void>;
|
|
forceSync: () => Promise<void>;
|
|
clearError: () => void;
|
|
}
|
|
|
|
export const useFastingCycleNotifications = (
|
|
cycle: FastingCycle | null,
|
|
session: FastingCycleSession | null,
|
|
plan: FastingPlan | undefined
|
|
): UseFastingCycleNotificationsState & UseFastingCycleNotificationsActions => {
|
|
const [state, setState] = useState<UseFastingCycleNotificationsState>({
|
|
isReady: false,
|
|
isLoading: true,
|
|
error: null,
|
|
lastSyncTime: null,
|
|
});
|
|
|
|
const isInitializedRef = useRef(false);
|
|
const isSyncingRef = useRef(false);
|
|
|
|
// 初始化通知系统
|
|
const initialize = useCallback(async () => {
|
|
if (isInitializedRef.current) return;
|
|
|
|
try {
|
|
setState(prev => ({ ...prev, isLoading: true, error: null }));
|
|
|
|
// 周期性断食通知不需要额外的权限检查,因为它使用相同的通知系统
|
|
setState(prev => ({
|
|
...prev,
|
|
isReady: true,
|
|
isLoading: false,
|
|
}));
|
|
|
|
isInitializedRef.current = true;
|
|
} catch (error) {
|
|
console.error('初始化周期性断食通知失败', error);
|
|
setState(prev => ({
|
|
...prev,
|
|
isReady: false,
|
|
isLoading: false,
|
|
error: error instanceof Error ? error.message : '初始化失败',
|
|
}));
|
|
}
|
|
}, []);
|
|
|
|
// 验证和同步通知
|
|
const verifyAndSync = useCallback(async () => {
|
|
if (!state.isReady || isSyncingRef.current) return;
|
|
|
|
try {
|
|
isSyncingRef.current = true;
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
if (cycle && session) {
|
|
const isValid = await FastingCycleNotificationManager.verifyCycleNotifications(cycle, session);
|
|
|
|
if (!isValid) {
|
|
console.log('周期性断食通知需要重新同步');
|
|
try {
|
|
await FastingCycleNotificationManager.scheduleCycleNotifications(cycle, session);
|
|
} catch (syncError) {
|
|
// 如果同步失败,记录错误但不阻止验证流程
|
|
console.error('重新安排通知失败', syncError);
|
|
setState(prev => ({
|
|
...prev,
|
|
error: '重新安排通知失败:' + (syncError instanceof Error ? syncError.message : '未知错误'),
|
|
}));
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
// 如果没有周期性计划,确保清理所有相关通知
|
|
await FastingCycleNotificationManager.cancelAllCycleNotifications();
|
|
}
|
|
|
|
setState(prev => ({
|
|
...prev,
|
|
lastSyncTime: new Date(),
|
|
}));
|
|
} catch (error) {
|
|
console.error('验证周期性断食通知失败', error);
|
|
setState(prev => ({
|
|
...prev,
|
|
error: '同步周期性断食通知失败:' + (error instanceof Error ? error.message : '未知错误'),
|
|
}));
|
|
} finally {
|
|
isSyncingRef.current = false;
|
|
}
|
|
}, [state.isReady, cycle, session]);
|
|
|
|
// 强制同步通知
|
|
const forceSync = useCallback(async () => {
|
|
if (!state.isReady) {
|
|
console.warn('通知系统未就绪,无法强制同步');
|
|
return;
|
|
}
|
|
|
|
if (isSyncingRef.current) {
|
|
console.warn('通知同步正在进行中,请稍后再试');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
isSyncingRef.current = true;
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
if (cycle && session) {
|
|
await FastingCycleNotificationManager.scheduleCycleNotifications(cycle, session);
|
|
console.log('周期性断食通知已强制同步', {
|
|
cycle: cycle.planId,
|
|
session: session.cycleDate,
|
|
});
|
|
} else {
|
|
await FastingCycleNotificationManager.cancelAllCycleNotifications();
|
|
}
|
|
|
|
setState(prev => ({
|
|
...prev,
|
|
lastSyncTime: new Date(),
|
|
}));
|
|
} catch (error) {
|
|
console.error('强制同步周期性断食通知失败', error);
|
|
setState(prev => ({
|
|
...prev,
|
|
error: '强制同步周期性断食通知失败:' + (error instanceof Error ? error.message : '未知错误'),
|
|
}));
|
|
throw error; // 重新抛出错误以便调用方处理
|
|
} finally {
|
|
isSyncingRef.current = false;
|
|
}
|
|
}, [state.isReady, cycle, session]);
|
|
|
|
// 清除错误
|
|
const clearError = useCallback(() => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
}, []);
|
|
|
|
// 初始化
|
|
useEffect(() => {
|
|
initialize();
|
|
}, [initialize]);
|
|
|
|
// 当周期性计划或会话变化时验证和同步
|
|
useEffect(() => {
|
|
if (!state.isReady) return;
|
|
|
|
// 使用防抖延迟执行,避免在快速状态变化时重复触发
|
|
const debounceTimer = setTimeout(() => {
|
|
verifyAndSync();
|
|
}, 1000); // 1秒防抖
|
|
|
|
return () => clearTimeout(debounceTimer);
|
|
}, [state.isReady, cycle?.planId, cycle?.enabled, session?.cycleDate, session?.startISO, session?.endISO]);
|
|
|
|
return {
|
|
...state,
|
|
verifyAndSync,
|
|
forceSync,
|
|
clearError,
|
|
};
|
|
}; |