feat(fasting): 重构断食通知系统并增强可靠性
- 新增 useFastingNotifications hook 统一管理通知状态和同步逻辑 - 实现四阶段通知提醒:开始前30分钟、开始时、结束前30分钟、结束时 - 添加通知验证机制,确保通知正确设置和避免重复 - 新增 NotificationErrorAlert 组件显示通知错误并提供重试选项 - 实现断食计划持久化存储,应用重启后自动恢复 - 添加开发者测试面板用于验证通知系统可靠性 - 优化通知同步策略,支持选择性更新减少不必要的操作 - 修复个人页面编辑按钮样式问题 - 更新应用版本号至 1.0.18
This commit is contained in:
184
hooks/useFastingNotifications.ts
Normal file
184
hooks/useFastingNotifications.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import { FastingPlan } from '@/constants/Fasting';
|
||||
import {
|
||||
ensureFastingNotificationsReady,
|
||||
resyncFastingNotifications,
|
||||
verifyFastingNotifications,
|
||||
} from '@/services/fastingNotifications';
|
||||
import { FastingSchedule } from '@/store/fastingSlice';
|
||||
import { FastingNotificationIds, loadStoredFastingNotificationIds } from '@/utils/fasting';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
export interface UseFastingNotificationsState {
|
||||
isReady: boolean;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
notificationIds: FastingNotificationIds;
|
||||
lastSyncTime: Date | null;
|
||||
}
|
||||
|
||||
export interface UseFastingNotificationsActions {
|
||||
verifyAndSync: () => Promise<void>;
|
||||
forceSync: () => Promise<void>;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
export const useFastingNotifications = (
|
||||
schedule: FastingSchedule | null,
|
||||
plan: FastingPlan | undefined
|
||||
): UseFastingNotificationsState & UseFastingNotificationsActions => {
|
||||
const [state, setState] = useState<UseFastingNotificationsState>({
|
||||
isReady: false,
|
||||
isLoading: true,
|
||||
error: null,
|
||||
notificationIds: {},
|
||||
lastSyncTime: null,
|
||||
});
|
||||
|
||||
const isInitializedRef = useRef(false);
|
||||
const notificationIdsRef = useRef<FastingNotificationIds>({});
|
||||
const isSyncingRef = useRef(false);
|
||||
|
||||
// 初始化通知系统
|
||||
const initialize = useCallback(async () => {
|
||||
if (isInitializedRef.current) return;
|
||||
|
||||
try {
|
||||
setState(prev => ({ ...prev, isLoading: true, error: null }));
|
||||
|
||||
// 1. 检查通知权限
|
||||
const ready = await ensureFastingNotificationsReady();
|
||||
if (!ready) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isReady: false,
|
||||
isLoading: false,
|
||||
error: '通知权限未授予',
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 加载存储的通知ID
|
||||
const storedIds = await loadStoredFastingNotificationIds();
|
||||
notificationIdsRef.current = storedIds;
|
||||
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isReady: true,
|
||||
isLoading: false,
|
||||
notificationIds: storedIds,
|
||||
}));
|
||||
|
||||
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 }));
|
||||
|
||||
const { isValid, updatedIds } = await verifyFastingNotifications({
|
||||
schedule,
|
||||
plan,
|
||||
storedIds: notificationIdsRef.current,
|
||||
});
|
||||
|
||||
notificationIdsRef.current = updatedIds;
|
||||
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
notificationIds: updatedIds,
|
||||
lastSyncTime: new Date(),
|
||||
}));
|
||||
|
||||
if (!isValid) {
|
||||
console.log('断食通知已重新同步');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('验证断食通知失败', error);
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: error instanceof Error ? error.message : '验证失败',
|
||||
}));
|
||||
|
||||
// 验证失败时不立即强制同步,避免重复调用
|
||||
// forceSync 会在用户点击重试按钮时调用
|
||||
} finally {
|
||||
isSyncingRef.current = false;
|
||||
}
|
||||
}, [state.isReady, schedule, plan]);
|
||||
|
||||
// 强制同步通知
|
||||
const forceSync = useCallback(async () => {
|
||||
if (!state.isReady || isSyncingRef.current) return;
|
||||
|
||||
try {
|
||||
isSyncingRef.current = true;
|
||||
setState(prev => ({ ...prev, error: null }));
|
||||
|
||||
const nextIds = await resyncFastingNotifications({
|
||||
schedule,
|
||||
plan,
|
||||
previousIds: notificationIdsRef.current,
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
notificationIdsRef.current = nextIds;
|
||||
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
notificationIds: nextIds,
|
||||
lastSyncTime: new Date(),
|
||||
}));
|
||||
|
||||
console.log('断食通知已强制同步', {
|
||||
schedule: schedule?.startISO,
|
||||
plan: plan?.id,
|
||||
notificationIds: nextIds,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('强制同步断食通知失败', error);
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: error instanceof Error ? error.message : '同步失败',
|
||||
}));
|
||||
} finally {
|
||||
isSyncingRef.current = false;
|
||||
}
|
||||
}, [state.isReady, schedule, plan]);
|
||||
|
||||
// 清除错误
|
||||
const clearError = useCallback(() => {
|
||||
setState(prev => ({ ...prev, error: null }));
|
||||
}, []);
|
||||
|
||||
// 初始化
|
||||
useEffect(() => {
|
||||
initialize();
|
||||
}, [initialize]);
|
||||
|
||||
// 当计划或方案变化时验证和同步
|
||||
useEffect(() => {
|
||||
if (state.isReady) {
|
||||
verifyAndSync();
|
||||
}
|
||||
}, [state.isReady, schedule?.startISO, schedule?.endISO, plan?.id, verifyAndSync]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
verifyAndSync,
|
||||
forceSync,
|
||||
clearError,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user