- 修复iOS后台任务注册时机问题,确保任务能正常触发 - 添加后台任务调试辅助工具和完整测试指南 - 优化断食通知系统,增加防抖机制避免频繁重调度 - 改进断食自动续订逻辑,使用固定时间而非相对时间计算 - 优化统计页面布局,添加身体指标section标题 - 增强饮水详情页面视觉效果,改进卡片样式和配色 - 添加用户反馈入口到个人设置页面 - 完善锻炼摘要卡片条件渲染逻辑 - 增强日志记录和错误处理机制 这些改进显著提升了应用的稳定性、性能和用户体验,特别是在iOS后台任务执行和断食功能方面。
190 lines
5.4 KiB
TypeScript
190 lines
5.4 KiB
TypeScript
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) return;
|
|
|
|
// 使用防抖延迟执行,避免在快速状态变化时重复触发
|
|
const debounceTimer = setTimeout(() => {
|
|
verifyAndSync();
|
|
}, 1000); // 1秒防抖
|
|
|
|
return () => clearTimeout(debounceTimer);
|
|
}, [state.isReady, schedule?.startISO, schedule?.endISO, plan?.id]);
|
|
|
|
return {
|
|
...state,
|
|
verifyAndSync,
|
|
forceSync,
|
|
clearError,
|
|
};
|
|
}; |