From 635d835a5099b5695c152eca6e4616c2257f7d5e Mon Sep 17 00:00:00 2001 From: richarjiang Date: Mon, 3 Nov 2025 14:13:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(fasting):=20=E5=AE=8C=E5=96=84=E6=96=AD?= =?UTF-8?q?=E9=A3=9F=E9=80=9A=E7=9F=A5=E7=B3=BB=E7=BB=9F=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在应用启动时添加断食通知初始化逻辑,改进错误消息提示,并新增后台任务支持断食通知同步。同时优化挑战加入后的数据刷新流程和会员卡片显示样式。 主要更改: - 添加断食通知启动检测和初始化 - 改进断食通知错误消息,提供更详细的用户指导 - 新增断食通知后台任务处理 - 优化挑战加入后自动刷新详情和排名数据 - 调整会员价格字体大小以提升视觉效果 --- app/_layout.tsx | 11 ++++++ app/challenges/[id]/index.tsx | 4 +- components/model/MembershipModal.tsx | 2 +- hooks/useFastingNotifications.ts | 6 +-- services/backgroundTaskManager.ts | 55 +++++++++++++++++++++++++++- 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/app/_layout.tsx b/app/_layout.tsx index 9135a21..0a2b0e6 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -154,6 +154,17 @@ function Bootstrapper({ children }: { children: React.ReactNode }) { await WaterNotificationHelpers.scheduleRegularWaterReminders(profile.name || '用户'); logger.info('默认喝水提醒已注册'); + // 安排断食通知(如果存在活跃的断食计划) + try { + const fastingSchedule = store.getState().fasting.activeSchedule; + if (fastingSchedule) { + const fastingPlan = store.getState().fasting.activeSchedule ? null : null; + // 断食通知将通过 useFastingNotifications hook 在页面加载时自动安排 + logger.info('检测到活跃的断食计划,将通过页面 hook 自动安排通知'); + } + } catch (error) { + logger.warn('安排断食通知失败:', error); + } // 初始化快捷动作 await setupQuickActions(); diff --git a/app/challenges/[id]/index.tsx b/app/challenges/[id]/index.tsx index 6740f0d..6fbe217 100644 --- a/app/challenges/[id]/index.tsx +++ b/app/challenges/[id]/index.tsx @@ -206,7 +206,9 @@ export default function ChallengeDetailScreen() { } try { - await dispatch(joinChallenge(id)); + await dispatch(joinChallenge(id)).unwrap(); + await dispatch(fetchChallengeDetail(id)).unwrap(); + await dispatch(fetchChallengeRankings({ id })); setShowCelebration(true) } catch (error) { Toast.error('加入挑战失败') diff --git a/components/model/MembershipModal.tsx b/components/model/MembershipModal.tsx index e70f617..5b98d5e 100644 --- a/components/model/MembershipModal.tsx +++ b/components/model/MembershipModal.tsx @@ -1232,7 +1232,7 @@ const styles = StyleSheet.create({ color: '#241F1F', }, planCardPrice: { - fontSize: 22, + fontSize: 16, fontWeight: '700', marginTop: 12, }, diff --git a/hooks/useFastingNotifications.ts b/hooks/useFastingNotifications.ts index 92bda4e..9dc3c05 100644 --- a/hooks/useFastingNotifications.ts +++ b/hooks/useFastingNotifications.ts @@ -52,7 +52,7 @@ export const useFastingNotifications = ( ...prev, isReady: false, isLoading: false, - error: '通知权限未授予', + error: '通知权限未开启。请前往"个人"页面开启消息推送,或检查系统通知权限设置。', })); return; } @@ -109,7 +109,7 @@ export const useFastingNotifications = ( console.error('验证断食通知失败', error); setState(prev => ({ ...prev, - error: error instanceof Error ? error.message : '验证失败', + error: '同步断食通知失败:' + (error instanceof Error ? error.message : '未知错误') + '。请点击"重试"按钮,或检查推送权限设置。', })); // 验证失败时不立即强制同步,避免重复调用 @@ -151,7 +151,7 @@ export const useFastingNotifications = ( console.error('强制同步断食通知失败', error); setState(prev => ({ ...prev, - error: error instanceof Error ? error.message : '同步失败', + error: '强制同步断食通知失败:' + (error instanceof Error ? error.message : '未知错误') + '。请点击"重试"按钮,或检查推送权限设置。', })); } finally { isSyncingRef.current = false; diff --git a/services/backgroundTaskManager.ts b/services/backgroundTaskManager.ts index adb38fc..c05f90c 100644 --- a/services/backgroundTaskManager.ts +++ b/services/backgroundTaskManager.ts @@ -5,6 +5,8 @@ import AsyncStorage from '@/utils/kvStore'; import { log } from '@/utils/logger'; import { ChallengeNotificationHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers'; import { getWaterGoalFromStorage } from '@/utils/userPreferences'; +import { resyncFastingNotifications } from '@/services/fastingNotifications'; +import { selectActiveFastingSchedule, selectActiveFastingPlan } from '@/store/fastingSlice'; import dayjs from 'dayjs'; import * as BackgroundTask from 'expo-background-task'; import * as TaskManager from 'expo-task-manager'; @@ -191,6 +193,54 @@ async function executeChallengeReminderTask(): Promise { } } +// 执行断食通知后台任务 +async function executeFastingNotificationTask(): Promise { + try { + console.log('执行断食通知后台任务...'); + + let state; + try { + state = store.getState(); + } catch (error) { + console.log('无法获取 Redux state,跳过断食通知任务:', error); + return; + } + + const activeSchedule = selectActiveFastingSchedule(state); + const activePlan = selectActiveFastingPlan(state); + + if (!activeSchedule || !activePlan) { + console.log('没有激活的断食计划,跳过断食通知任务'); + return; + } + + // 检查断食计划是否已结束 + const end = dayjs(activeSchedule.endISO); + if (end.isBefore(dayjs())) { + console.log('断食计划已结束,跳过断食通知任务'); + return; + } + + console.log('正在同步断食通知...', { + planId: activePlan.id, + start: activeSchedule.startISO, + end: activeSchedule.endISO, + }); + + // 重新同步断食通知 + await resyncFastingNotifications({ + schedule: activeSchedule, + plan: activePlan, + enabled: true, + }); + + console.log('断食通知后台同步完成'); + + } catch (error) { + console.error('执行断食通知后台任务失败:', error); + } +} + // 发送测试通知以验证后台任务执行 async function sendTestNotification(): Promise { try { @@ -286,6 +336,9 @@ async function executeBackgroundTasks(): Promise { // 执行挑战鼓励提醒任务 await executeChallengeReminderTask(); + // 执行断食通知任务 + await executeFastingNotificationTask(); + console.log('后台任务执行完成'); } catch (error) { console.error('执行后台任务失败:', error); @@ -318,7 +371,7 @@ export class BackgroundTaskManager { try { // 定义后台任务 - TaskManager.defineTask(BACKGROUND_TASK_IDENTIFIER, async (body: TaskManagerTaskBody) => { + TaskManager.defineTask(BACKGROUND_TASK_IDENTIFIER, async (_body: TaskManagerTaskBody) => { try { log.info(`[BackgroundTask] 后台任务执行, 任务 ID: ${BACKGROUND_TASK_IDENTIFIER}`); await executeBackgroundTasks();