feat(fasting): 重构断食通知系统并增强可靠性
- 新增 useFastingNotifications hook 统一管理通知状态和同步逻辑 - 实现四阶段通知提醒:开始前30分钟、开始时、结束前30分钟、结束时 - 添加通知验证机制,确保通知正确设置和避免重复 - 新增 NotificationErrorAlert 组件显示通知错误并提供重试选项 - 实现断食计划持久化存储,应用重启后自动恢复 - 添加开发者测试面板用于验证通知系统可靠性 - 优化通知同步策略,支持选择性更新减少不必要的操作 - 修复个人页面编辑按钮样式问题 - 更新应用版本号至 1.0.18
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
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';
|
||||
import AsyncStorage from '@/utils/kvStore';
|
||||
import { FASTING_STORAGE_KEYS } from '@/constants/Fasting';
|
||||
|
||||
dayjs.extend(duration);
|
||||
dayjs.extend(isSameOrAfter);
|
||||
@@ -100,6 +101,47 @@ export const buildDisplayWindow = (start?: Date | null, end?: Date | null) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const loadActiveFastingSchedule = async (): Promise<FastingSchedule | null> => {
|
||||
try {
|
||||
const stored = await AsyncStorage.getItem(FASTING_STORAGE_KEYS.activeSchedule);
|
||||
if (!stored) return null;
|
||||
|
||||
const parsed = JSON.parse(stored) as Partial<FastingSchedule>;
|
||||
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<string | null> => {
|
||||
try {
|
||||
return await AsyncStorage.getItem(FASTING_STORAGE_KEYS.preferredPlanId);
|
||||
@@ -118,8 +160,10 @@ export const savePreferredPlanId = async (planId: string) => {
|
||||
};
|
||||
|
||||
export type FastingNotificationIds = {
|
||||
startId?: string | null;
|
||||
endId?: string | null;
|
||||
preStartId?: string | null; // 开始前30分钟
|
||||
startId?: string | null; // 开始时
|
||||
preEndId?: string | null; // 结束前30分钟
|
||||
endId?: string | null; // 结束时
|
||||
};
|
||||
|
||||
export const getFastingNotificationsRegistered = async (): Promise<boolean> => {
|
||||
@@ -146,13 +190,17 @@ export const setFastingNotificationsRegistered = async (registered: boolean) =>
|
||||
|
||||
export const loadStoredFastingNotificationIds = async (): Promise<FastingNotificationIds> => {
|
||||
try {
|
||||
const [startId, endId] = await Promise.all([
|
||||
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) {
|
||||
@@ -163,12 +211,28 @@ export const loadStoredFastingNotificationIds = async (): Promise<FastingNotific
|
||||
|
||||
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 {
|
||||
@@ -182,7 +246,9 @@ export const saveFastingNotificationIds = async (ids: FastingNotificationIds) =>
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user