feat(background-task): 完善iOS后台任务系统并优化断食通知和UI体验
- 修复iOS后台任务注册时机问题,确保任务能正常触发 - 添加后台任务调试辅助工具和完整测试指南 - 优化断食通知系统,增加防抖机制避免频繁重调度 - 改进断食自动续订逻辑,使用固定时间而非相对时间计算 - 优化统计页面布局,添加身体指标section标题 - 增强饮水详情页面视觉效果,改进卡片样式和配色 - 添加用户反馈入口到个人设置页面 - 完善锻炼摘要卡片条件渲染逻辑 - 增强日志记录和错误处理机制 这些改进显著提升了应用的稳定性、性能和用户体验,特别是在iOS后台任务执行和断食功能方面。
This commit is contained in:
@@ -263,16 +263,31 @@ const selectiveSyncNotifications = async ({
|
||||
const start = dayjs(schedule.startISO);
|
||||
const end = dayjs(schedule.endISO);
|
||||
|
||||
if (end.isBefore(now)) {
|
||||
if (end.isBefore(now.subtract(1, 'hour'))) {
|
||||
await clearFastingNotificationIds();
|
||||
return {};
|
||||
}
|
||||
|
||||
const updatedIds: FastingNotificationIds = { ...validIds };
|
||||
|
||||
// 先取消所有无效的旧通知,避免重复
|
||||
const invalidIds = Object.entries(storedIds).filter(
|
||||
([key, id]) => id && !Object.values(validIds).includes(id)
|
||||
);
|
||||
|
||||
for (const [_, id] of invalidIds) {
|
||||
if (id) {
|
||||
try {
|
||||
await notificationService.cancelNotification(id);
|
||||
} catch (error) {
|
||||
console.warn('取消无效通知失败', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 检查开始前30分钟通知
|
||||
const preStart = start.subtract(REMINDER_OFFSET_MINUTES, 'minute');
|
||||
if (preStart.isAfter(now) && !validIds.preStartId) {
|
||||
if (preStart.isAfter(now.add(1, 'minute')) && !validIds.preStartId) {
|
||||
try {
|
||||
const preStartId = await notificationService.scheduleNotificationAtDate(
|
||||
{
|
||||
@@ -289,13 +304,14 @@ const selectiveSyncNotifications = async ({
|
||||
preStart.toDate()
|
||||
);
|
||||
updatedIds.preStartId = preStartId;
|
||||
console.log(`已安排断食开始前30分钟通知: ${preStart.format('YYYY-MM-DD HH:mm')}`);
|
||||
} catch (error) {
|
||||
console.error('安排断食开始前30分钟通知失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查开始时通知
|
||||
if (start.isAfter(now) && !validIds.startId) {
|
||||
if (start.isAfter(now.add(1, 'minute')) && !validIds.startId) {
|
||||
try {
|
||||
const startId = await notificationService.scheduleNotificationAtDate(
|
||||
{
|
||||
@@ -312,6 +328,7 @@ const selectiveSyncNotifications = async ({
|
||||
start.toDate()
|
||||
);
|
||||
updatedIds.startId = startId;
|
||||
console.log(`已安排断食开始时通知: ${start.format('YYYY-MM-DD HH:mm')}`);
|
||||
} catch (error) {
|
||||
console.error('安排断食开始时通知失败', error);
|
||||
}
|
||||
@@ -319,7 +336,7 @@ const selectiveSyncNotifications = async ({
|
||||
|
||||
// 3. 检查结束前30分钟通知
|
||||
const preEnd = end.subtract(REMINDER_OFFSET_MINUTES, 'minute');
|
||||
if (preEnd.isAfter(now) && !validIds.preEndId) {
|
||||
if (preEnd.isAfter(now.add(1, 'minute')) && !validIds.preEndId) {
|
||||
try {
|
||||
const preEndId = await notificationService.scheduleNotificationAtDate(
|
||||
{
|
||||
@@ -336,13 +353,14 @@ const selectiveSyncNotifications = async ({
|
||||
preEnd.toDate()
|
||||
);
|
||||
updatedIds.preEndId = preEndId;
|
||||
console.log(`已安排断食结束前30分钟通知: ${preEnd.format('YYYY-MM-DD HH:mm')}`);
|
||||
} catch (error) {
|
||||
console.error('安排断食结束前30分钟通知失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 检查结束时通知
|
||||
if (end.isAfter(now) && !validIds.endId) {
|
||||
if (end.isAfter(now.add(1, 'minute')) && !validIds.endId) {
|
||||
try {
|
||||
const endId = await notificationService.scheduleNotificationAtDate(
|
||||
{
|
||||
@@ -359,6 +377,7 @@ const selectiveSyncNotifications = async ({
|
||||
end.toDate()
|
||||
);
|
||||
updatedIds.endId = endId;
|
||||
console.log(`已安排断食结束时通知: ${end.format('YYYY-MM-DD HH:mm')}`);
|
||||
} catch (error) {
|
||||
console.error('安排断食结束时通知失败', error);
|
||||
}
|
||||
@@ -398,8 +417,9 @@ export const verifyFastingNotifications = async ({
|
||||
const start = dayjs(schedule.startISO);
|
||||
const end = dayjs(schedule.endISO);
|
||||
|
||||
// 如果断食期已结束,应该清空所有通知
|
||||
if (end.isBefore(now)) {
|
||||
// 如果断食期已结束超过1小时,应该清空所有通知
|
||||
// 这样可以避免在自动续订过程中过早清空通知
|
||||
if (end.isBefore(now.subtract(1, 'hour'))) {
|
||||
if (Object.values(storedIds).some(id => id)) {
|
||||
await cancelNotificationIds(storedIds);
|
||||
await clearFastingNotificationIds();
|
||||
@@ -431,9 +451,15 @@ export const verifyFastingNotifications = async ({
|
||||
const validIds: FastingNotificationIds = {};
|
||||
|
||||
for (const expected of expectedNotifications) {
|
||||
// 跳过已过期的通知
|
||||
if (expected.time.isBefore(now)) {
|
||||
// 跳过已过期的通知(过期超过5分钟)
|
||||
if (expected.time.isBefore(now.subtract(5, 'minute'))) {
|
||||
if (expected.id) {
|
||||
// 取消已过期的通知
|
||||
try {
|
||||
await notificationService.cancelNotification(expected.id);
|
||||
} catch (error) {
|
||||
console.warn('取消过期通知失败', error);
|
||||
}
|
||||
needsResync = true;
|
||||
}
|
||||
continue;
|
||||
@@ -451,6 +477,20 @@ export const verifyFastingNotifications = async ({
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查通知时间是否匹配(容差1分钟)
|
||||
const notification = scheduledNotifications.find(n => n.identifier === expected.id);
|
||||
if (notification?.trigger && 'date' in notification.trigger) {
|
||||
const scheduledTime = dayjs(notification.trigger.date);
|
||||
const timeDiff = Math.abs(scheduledTime.diff(expected.time, 'minute'));
|
||||
|
||||
// 如果时间差异超过1分钟,说明需要重新安排
|
||||
if (timeDiff > 1) {
|
||||
console.log(`通知时间不匹配,需要重新安排: ${expected.type}, 期望: ${expected.time.format('HH:mm')}, 实际: ${scheduledTime.format('HH:mm')}`);
|
||||
needsResync = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 通知存在且有效
|
||||
switch (expected.type) {
|
||||
case 'pre_start':
|
||||
|
||||
Reference in New Issue
Block a user