feat(background-task): 完善iOS后台任务系统并优化断食通知和UI体验
- 修复iOS后台任务注册时机问题,确保任务能正常触发 - 添加后台任务调试辅助工具和完整测试指南 - 优化断食通知系统,增加防抖机制避免频繁重调度 - 改进断食自动续订逻辑,使用固定时间而非相对时间计算 - 优化统计页面布局,添加身体指标section标题 - 增强饮水详情页面视觉效果,改进卡片样式和配色 - 添加用户反馈入口到个人设置页面 - 完善锻炼摘要卡片条件渲染逻辑 - 增强日志记录和错误处理机制 这些改进显著提升了应用的稳定性、性能和用户体验,特别是在iOS后台任务执行和断食功能方面。
This commit is contained in:
@@ -8,23 +8,23 @@ import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useCountdown } from '@/hooks/useCountdown';
|
||||
import { useFastingNotifications } from '@/hooks/useFastingNotifications';
|
||||
import {
|
||||
clearActiveSchedule,
|
||||
rescheduleActivePlan,
|
||||
scheduleFastingPlan,
|
||||
selectActiveFastingPlan,
|
||||
selectActiveFastingSchedule,
|
||||
clearActiveSchedule,
|
||||
rescheduleActivePlan,
|
||||
scheduleFastingPlan,
|
||||
selectActiveFastingPlan,
|
||||
selectActiveFastingSchedule,
|
||||
} from '@/store/fastingSlice';
|
||||
import {
|
||||
buildDisplayWindow,
|
||||
calculateFastingWindow,
|
||||
getFastingPhase,
|
||||
getPhaseLabel,
|
||||
loadPreferredPlanId,
|
||||
savePreferredPlanId
|
||||
buildDisplayWindow,
|
||||
calculateFastingWindow,
|
||||
getFastingPhase,
|
||||
getPhaseLabel,
|
||||
loadPreferredPlanId,
|
||||
savePreferredPlanId
|
||||
} from '@/utils/fasting';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { useRouter } from 'expo-router';
|
||||
import dayjs from 'dayjs';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
@@ -87,8 +87,19 @@ export default function FastingTabScreen() {
|
||||
} = useFastingNotifications(activeSchedule, currentPlan);
|
||||
|
||||
// 每次进入页面时验证通知
|
||||
// 添加节流机制,避免频繁触发验证
|
||||
const lastVerifyTimeRef = React.useRef<number>(0);
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const now = Date.now();
|
||||
const timeSinceLastVerify = now - lastVerifyTimeRef.current;
|
||||
|
||||
// 如果距离上次验证不足 30 秒,跳过本次验证
|
||||
if (timeSinceLastVerify < 30000) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastVerifyTimeRef.current = now;
|
||||
verifyAndSync();
|
||||
}, [verifyAndSync])
|
||||
);
|
||||
@@ -155,6 +166,8 @@ export default function FastingTabScreen() {
|
||||
}
|
||||
}, [notificationsReady, notificationsLoading, notificationError, notificationIds, lastSyncTime, activeSchedule?.startISO, currentPlan?.id]);
|
||||
|
||||
// 自动续订断食周期
|
||||
// 修改为使用每日固定时间,而非相对时间计算
|
||||
useEffect(() => {
|
||||
if (!activeSchedule || !currentPlan) return;
|
||||
if (phase !== 'completed') return;
|
||||
@@ -166,38 +179,42 @@ export default function FastingTabScreen() {
|
||||
const now = dayjs();
|
||||
if (now.isBefore(end)) return;
|
||||
|
||||
const fastingHours = currentPlan.fastingHours;
|
||||
const eatingHours = currentPlan.eatingHours;
|
||||
const cycleHours = fastingHours + eatingHours;
|
||||
|
||||
if (fastingHours <= 0 || cycleHours <= 0) return;
|
||||
|
||||
let nextStart = start;
|
||||
let nextEnd = end;
|
||||
let iterations = 0;
|
||||
const maxIterations = 60;
|
||||
|
||||
while (!now.isBefore(nextEnd)) {
|
||||
nextStart = nextStart.add(cycleHours, 'hour');
|
||||
nextEnd = nextStart.add(fastingHours, 'hour');
|
||||
iterations += 1;
|
||||
|
||||
if (iterations >= maxIterations) {
|
||||
if (__DEV__) {
|
||||
console.warn('自动续订断食周期失败: 超出最大迭代次数', {
|
||||
start: activeSchedule.startISO,
|
||||
end: activeSchedule.endISO,
|
||||
planId: currentPlan.id,
|
||||
});
|
||||
}
|
||||
return;
|
||||
// 检查是否在短时间内已经续订过,避免重复续订
|
||||
const timeSinceEnd = now.diff(end, 'minute');
|
||||
if (timeSinceEnd > 60) {
|
||||
// 如果周期结束超过1小时,说明用户可能不再需要自动续订
|
||||
if (__DEV__) {
|
||||
console.log('断食周期结束超过1小时,不自动续订');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (iterations === 0) return;
|
||||
// 使用每日固定时间计算下一个周期
|
||||
// 保持原始的开始时间(小时和分钟),只增加日期
|
||||
const originalStartHour = start.hour();
|
||||
const originalStartMinute = start.minute();
|
||||
|
||||
// 计算下一个开始时间:明天的同一时刻
|
||||
let nextStart = now.startOf('day').hour(originalStartHour).minute(originalStartMinute);
|
||||
|
||||
// 如果计算出的时间在当前时间之前,则使用后天的同一时刻
|
||||
if (nextStart.isBefore(now)) {
|
||||
nextStart = nextStart.add(1, 'day');
|
||||
}
|
||||
|
||||
const nextEnd = nextStart.add(currentPlan.fastingHours, 'hour');
|
||||
|
||||
if (__DEV__) {
|
||||
console.log('自动续订断食周期:', {
|
||||
oldStart: start.format('YYYY-MM-DD HH:mm'),
|
||||
oldEnd: end.format('YYYY-MM-DD HH:mm'),
|
||||
nextStart: nextStart.format('YYYY-MM-DD HH:mm'),
|
||||
nextEnd: nextEnd.format('YYYY-MM-DD HH:mm'),
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(rescheduleActivePlan({
|
||||
start: nextStart.toDate().toISOString(),
|
||||
start: nextStart.toISOString(),
|
||||
origin: 'auto',
|
||||
}));
|
||||
}, [dispatch, activeSchedule, currentPlan, phase]);
|
||||
|
||||
Reference in New Issue
Block a user