feat(fasting): 添加周期性断食计划功能
实现完整的周期性断食计划系统,支持每日自动续订和通知管理: - 新增周期性断食状态管理(activeCycle、currentCycleSession、cycleHistory) - 实现周期性断食会话的自动完成和续订逻辑 - 添加独立的周期性断食通知系统,避免与单次断食通知冲突 - 支持暂停/恢复周期性断食计划 - 添加周期性断食数据持久化和水合功能 - 优化断食界面,优先显示周期性断食信息 - 新增空状态引导界面,提升用户体验 - 保持单次断食功能向后兼容
This commit is contained in:
175
hooks/useFastingCycleNotifications.ts
Normal file
175
hooks/useFastingCycleNotifications.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user