新增完整的轻断食功能,包括: - 断食计划列表和详情页面,支持12-12、14-10、16-8、18-6四种计划 - 断食状态实时追踪和倒计时显示 - 自定义开始时间选择器 - 断食通知提醒功能 - Redux状态管理和数据持久化 - 新增tab导航入口和路由配置
163 lines
4.4 KiB
TypeScript
163 lines
4.4 KiB
TypeScript
import dayjs from 'dayjs';
|
|
|
|
import { FastingPlan } from '@/constants/Fasting';
|
|
import { FastingSchedule } from '@/store/fastingSlice';
|
|
import {
|
|
clearFastingNotificationIds,
|
|
getFastingNotificationsRegistered,
|
|
loadStoredFastingNotificationIds,
|
|
saveFastingNotificationIds,
|
|
setFastingNotificationsRegistered,
|
|
FastingNotificationIds,
|
|
} from '@/utils/fasting';
|
|
import { getNotificationEnabled } from '@/utils/userPreferences';
|
|
import { notificationService, NotificationTypes } from './notifications';
|
|
|
|
const REMINDER_OFFSET_MINUTES = 10;
|
|
|
|
const cancelNotificationIds = async (ids?: FastingNotificationIds) => {
|
|
if (!ids) return;
|
|
const { startId, endId } = ids;
|
|
try {
|
|
if (startId) {
|
|
await notificationService.cancelNotification(startId);
|
|
}
|
|
} catch (error) {
|
|
console.warn('取消断食开始提醒失败', error);
|
|
}
|
|
|
|
try {
|
|
if (endId) {
|
|
await notificationService.cancelNotification(endId);
|
|
}
|
|
} catch (error) {
|
|
console.warn('取消断食结束提醒失败', error);
|
|
}
|
|
};
|
|
|
|
export const ensureFastingNotificationsReady = async (): Promise<boolean> => {
|
|
try {
|
|
const notificationsEnabled = await getNotificationEnabled();
|
|
if (!notificationsEnabled) {
|
|
return false;
|
|
}
|
|
|
|
const registered = await getFastingNotificationsRegistered();
|
|
if (registered) {
|
|
return true;
|
|
}
|
|
|
|
const status = await notificationService.getPermissionStatus();
|
|
if (status !== 'granted') {
|
|
const requestStatus = await notificationService.requestPermission();
|
|
if (requestStatus !== 'granted') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
await setFastingNotificationsRegistered(true);
|
|
return true;
|
|
} catch (error) {
|
|
console.warn('初始化断食通知失败', error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
type ResyncOptions = {
|
|
schedule: FastingSchedule | null;
|
|
plan?: FastingPlan;
|
|
previousIds?: FastingNotificationIds;
|
|
enabled: boolean;
|
|
};
|
|
|
|
export const resyncFastingNotifications = async ({
|
|
schedule,
|
|
plan,
|
|
previousIds,
|
|
enabled,
|
|
}: ResyncOptions): Promise<FastingNotificationIds> => {
|
|
const storedIds = previousIds ?? (await loadStoredFastingNotificationIds());
|
|
await cancelNotificationIds(storedIds);
|
|
|
|
if (!enabled) {
|
|
await setFastingNotificationsRegistered(false);
|
|
await clearFastingNotificationIds();
|
|
return {};
|
|
}
|
|
|
|
const preferenceEnabled = await getNotificationEnabled();
|
|
if (!preferenceEnabled) {
|
|
await setFastingNotificationsRegistered(false);
|
|
await clearFastingNotificationIds();
|
|
return {};
|
|
}
|
|
|
|
if (!schedule || !plan) {
|
|
await clearFastingNotificationIds();
|
|
return {};
|
|
}
|
|
|
|
const now = dayjs();
|
|
const start = dayjs(schedule.startISO);
|
|
const end = dayjs(schedule.endISO);
|
|
|
|
if (end.isBefore(now)) {
|
|
await clearFastingNotificationIds();
|
|
return {};
|
|
}
|
|
|
|
const notificationIds: FastingNotificationIds = {};
|
|
|
|
if (start.isAfter(now)) {
|
|
const preStart = start.subtract(REMINDER_OFFSET_MINUTES, 'minute');
|
|
const triggerMoment = preStart.isAfter(now) ? preStart : start;
|
|
|
|
try {
|
|
const startId = await notificationService.scheduleNotificationAtDate(
|
|
{
|
|
title: `${plan.title} 即将开始`,
|
|
body: preStart.isAfter(now)
|
|
? `还有 ${REMINDER_OFFSET_MINUTES} 分钟就要进入断食窗口,喝一杯温水,准备迎接更轻盈的自己!`
|
|
: `现在开始 ${plan.title},放下零食,给身体一次真正的休息时间。`,
|
|
data: {
|
|
type: NotificationTypes.FASTING_START,
|
|
planId: plan.id,
|
|
},
|
|
sound: true,
|
|
priority: 'high',
|
|
},
|
|
triggerMoment.toDate()
|
|
);
|
|
notificationIds.startId = startId;
|
|
} catch (error) {
|
|
console.error('安排断食开始通知失败', error);
|
|
}
|
|
}
|
|
|
|
if (end.isAfter(now)) {
|
|
try {
|
|
const endId = await notificationService.scheduleNotificationAtDate(
|
|
{
|
|
title: '补能时刻到啦',
|
|
body: `${plan.title} 已完成!用一顿高蛋白 + 低GI 的餐食奖励自己,让代谢持续高效运转。`,
|
|
data: {
|
|
type: NotificationTypes.FASTING_END,
|
|
planId: plan.id,
|
|
},
|
|
sound: true,
|
|
priority: 'high',
|
|
},
|
|
end.toDate()
|
|
);
|
|
notificationIds.endId = endId;
|
|
} catch (error) {
|
|
console.error('安排断食结束通知失败', error);
|
|
}
|
|
}
|
|
|
|
await saveFastingNotificationIds(notificationIds);
|
|
return notificationIds;
|
|
};
|
|
|
|
export type { FastingNotificationIds };
|