From 460a7e4289cb4550db9ef5ac6f209e4602243ef5 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Fri, 5 Sep 2025 10:29:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E7=AE=A1=E7=90=86=E5=99=A8=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=96=9D=E6=B0=B4=E5=92=8C=E7=AB=99=E7=AB=8B=E6=8F=90?= =?UTF-8?q?=E9=86=92=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/statistics.tsx | 3 + app/_layout.tsx | 11 +- ios/Podfile.lock | 6 + ios/digitalpilates.xcodeproj/project.pbxproj | 2 + package-lock.json | 7 + package.json | 3 +- services/backgroundTaskManager.ts | 421 +++++++++++++++++++ utils/health.ts | 46 +- utils/notificationHelpers.ts | 97 +++++ 9 files changed, 583 insertions(+), 13 deletions(-) create mode 100644 services/backgroundTaskManager.ts diff --git a/app/(tabs)/statistics.tsx b/app/(tabs)/statistics.tsx index cc10ffd..18b0a42 100644 --- a/app/(tabs)/statistics.tsx +++ b/app/(tabs)/statistics.tsx @@ -527,6 +527,8 @@ export default function ExploreScreen() { } }, [todayWaterStats, userProfile]); + + // 日期点击时,加载对应日期数据 const onSelectDate = React.useCallback((index: number, date: Date) => { setSelectedIndex(index); @@ -1082,4 +1084,5 @@ const styles = StyleSheet.create({ padding: 4, }, + }); diff --git a/app/_layout.tsx b/app/_layout.tsx index 4e63f06..866a27a 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -12,6 +12,7 @@ import { notificationService } from '@/services/notifications'; import { store } from '@/store'; import { rehydrateUser, setPrivacyAgreed } from '@/store/userSlice'; import { MoodNotificationHelpers, NutritionNotificationHelpers } from '@/utils/notificationHelpers'; +import { backgroundTaskManager } from '@/services/backgroundTaskManager'; import React from 'react'; import RNExitApp from 'react-native-exit-app'; @@ -35,8 +36,12 @@ function Bootstrapper({ children }: { children: React.ReactNode }) { // 初始化通知服务 await notificationService.initialize(); console.log('通知服务初始化成功'); + + // 初始化后台任务管理器 + await backgroundTaskManager.initialize(); + console.log('后台任务管理器初始化成功'); } catch (error) { - console.error('通知服务初始化失败:', error); + console.error('通知服务或后台任务管理器初始化失败:', error); } }; @@ -71,6 +76,10 @@ function Bootstrapper({ children }: { children: React.ReactNode }) { // 注册心情提醒(21:00) await MoodNotificationHelpers.scheduleDailyMoodReminder(profile.name); console.log('心情提醒已注册'); + + // 注册喝水提醒后台任务 + await backgroundTaskManager.registerWaterReminderTask(); + console.log('喝水提醒后台任务已注册'); } catch (error) { console.error('注册提醒失败:', error); } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 29045df..ad902b5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1739,6 +1739,8 @@ PODS: - RevenueCat (5.34.0) - RNAppleHealthKit (1.7.0): - React + - RNBackgroundFetch (4.2.8): + - React-Core - RNCAsyncStorage (2.2.0): - React-Core - RNCMaskedView (0.3.2): @@ -2086,6 +2088,7 @@ DEPENDENCIES: - ReactCodegen (from `build/generated/ios`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - RNAppleHealthKit (from `../node_modules/react-native-health`) + - RNBackgroundFetch (from `../node_modules/react-native-background-fetch`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)" - "RNCPicker (from `../node_modules/@react-native-picker/picker`)" @@ -2312,6 +2315,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" RNAppleHealthKit: :path: "../node_modules/react-native-health" + RNBackgroundFetch: + :path: "../node_modules/react-native-background-fetch" RNCAsyncStorage: :path: "../node_modules/@react-native-async-storage/async-storage" RNCMaskedView: @@ -2445,6 +2450,7 @@ SPEC CHECKSUMS: ReactCommon: 7eb76fcd5133313d8c6a138a5c7dd89f80f189d5 RevenueCat: eb2aa042789d9c99ad5172bd96e28b96286d6ada RNAppleHealthKit: 86ef7ab70f762b802f5c5289372de360cca701f9 + RNBackgroundFetch: e44c9e85d7fb3122c37d8a806278f62c7682d7ea RNCAsyncStorage: b44e8a4e798c3e1f56bffccd0f591f674fb9198f RNCMaskedView: d4644e239e65383f96d2f32c40c297f09705ac96 RNCPicker: da0f1c9411208c1ca52bc98383db54a06e0a3862 diff --git a/ios/digitalpilates.xcodeproj/project.pbxproj b/ios/digitalpilates.xcodeproj/project.pbxproj index ac6fd15..3c002d1 100644 --- a/ios/digitalpilates.xcodeproj/project.pbxproj +++ b/ios/digitalpilates.xcodeproj/project.pbxproj @@ -273,6 +273,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/PurchasesHybridCommon/PurchasesHybridCommon.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/QCloudCOSXML/QCloudCOSXML.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNBackgroundFetch/TSBackgroundFetchPrivacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", @@ -297,6 +298,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PurchasesHybridCommon.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QCloudCOSXML.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TSBackgroundFetchPrivacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", diff --git a/package-lock.json b/package-lock.json index 0ae1156..108143b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "react": "19.0.0", "react-dom": "19.0.0", "react-native": "0.79.5", + "react-native-background-fetch": "^4.2.8", "react-native-cos-sdk": "^1.2.1", "react-native-device-info": "^14.0.4", "react-native-exit-app": "^2.0.0", @@ -11397,6 +11398,12 @@ } } }, + "node_modules/react-native-background-fetch": { + "version": "4.2.8", + "resolved": "https://mirrors.tencent.com/npm/react-native-background-fetch/-/react-native-background-fetch-4.2.8.tgz", + "integrity": "sha512-vKPumvhBuxr3oI1L7cNunYIsKV8jD4Xz2A9JT/FW5yvn7GAAct184FAZ9dFef75auBxixinaCjRBlip53xGWmQ==", + "license": "MIT" + }, "node_modules/react-native-cos-sdk": { "version": "1.2.1", "resolved": "https://mirrors.tencent.com/npm/react-native-cos-sdk/-/react-native-cos-sdk-1.2.1.tgz", diff --git a/package.json b/package.json index ec1ba51..45acef6 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "react": "19.0.0", "react-dom": "19.0.0", "react-native": "0.79.5", + "react-native-background-fetch": "^4.2.8", "react-native-cos-sdk": "^1.2.1", "react-native-device-info": "^14.0.4", "react-native-exit-app": "^2.0.0", @@ -77,4 +78,4 @@ "typescript": "~5.8.3" }, "private": true -} \ No newline at end of file +} diff --git a/services/backgroundTaskManager.ts b/services/backgroundTaskManager.ts new file mode 100644 index 0000000..9c2a479 --- /dev/null +++ b/services/backgroundTaskManager.ts @@ -0,0 +1,421 @@ +import { store } from '@/store'; +import { StandReminderHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import BackgroundFetch from 'react-native-background-fetch'; + +/** + * 后台任务标识符 + */ +export const BACKGROUND_TASK_IDS = { + WATER_REMINDER: 'water-reminder-task', + STAND_REMINDER: 'stand-reminder-task', +} as const; + +/** + * 后台任务管理器 + * 负责配置和管理 iOS 应用的后台任务执行 + */ +export class BackgroundTaskManager { + private static instance: BackgroundTaskManager; + private isInitialized = false; + + static getInstance(): BackgroundTaskManager { + if (!BackgroundTaskManager.instance) { + BackgroundTaskManager.instance = new BackgroundTaskManager(); + } + return BackgroundTaskManager.instance; + } + + /** + * 初始化后台任务管理器 + */ + async initialize(): Promise { + if (this.isInitialized) { + console.log('后台任务管理器已初始化'); + return; + } + + try { + // 配置后台获取 + const status = await BackgroundFetch.configure({ + minimumFetchInterval: 15000, // 最小间隔15分钟(iOS 实际控制间隔) + }, async (taskId) => { + console.log('[BackgroundFetch] 后台任务执行:', taskId); + await this.executeBackgroundTasks(); + // 完成任务 + BackgroundFetch.finish(taskId); + }, (error) => { + console.error('[BackgroundFetch] 配置失败:', error); + }); + + console.log('[BackgroundFetch] 配置状态:', status); + + this.isInitialized = true; + console.log('后台任务管理器初始化完成'); + + } catch (error) { + console.error('初始化后台任务管理器失败:', error); + throw error; + } + } + + /** + * 执行后台任务 + */ + private async executeBackgroundTasks(): Promise { + console.log('开始执行后台任务...'); + + try { + // 检查应用权限和用户设置 + const hasPermission = await this.checkNotificationPermissions(); + if (!hasPermission) { + console.log('没有通知权限,跳过后台任务'); + return; + } + + // 执行喝水提醒检查任务 + await this.executeWaterReminderTask(); + + // 执行站立提醒检查任务 + await this.executeStandReminderTask(); + + console.log('后台任务执行完成'); + } catch (error) { + console.error('执行后台任务失败:', error); + } + } + + /** + * 执行喝水提醒后台任务 + */ + private async executeWaterReminderTask(): Promise { + try { + console.log('执行喝水提醒后台任务...'); + + // 获取当前状态 + const state = store.getState(); + const waterStats = state.water.todayStats; + const userProfile = state.user.profile; + + // 检查是否有喝水目标设置 + if (!waterStats || !waterStats.dailyGoal || waterStats.dailyGoal <= 0) { + console.log('没有设置喝水目标,跳过喝水提醒'); + return; + } + + // 检查时间限制(避免深夜打扰) + const currentHour = new Date().getHours(); + if (currentHour < 9 || currentHour >= 21) { + console.log(`当前时间${currentHour}点,不在提醒时间范围内,跳过喝水提醒`); + return; + } + + // 获取用户名 + const userName = userProfile?.name || '朋友'; + + // 构造今日统计数据 + const todayWaterStats = { + totalAmount: waterStats.totalAmount || 0, + dailyGoal: waterStats.dailyGoal, + completionRate: waterStats.completionRate || 0 + }; + + // 调用喝水通知检查函数 + const notificationSent = await WaterNotificationHelpers.checkWaterGoalAndNotify( + userName, + todayWaterStats, + currentHour + ); + + if (notificationSent) { + console.log('后台喝水提醒通知已发送'); + // 记录后台任务执行时间 + await AsyncStorage.setItem('@last_background_water_check', Date.now().toString()); + } else { + console.log('无需发送后台喝水提醒通知'); + } + + } catch (error) { + console.error('执行喝水提醒后台任务失败:', error); + } + } + + /** + * 执行站立提醒后台任务 + */ + private async executeStandReminderTask(): Promise { + try { + console.log('执行站立提醒后台任务...'); + + // 获取当前状态 + const state = store.getState(); + const userProfile = state.user.profile; + + // 检查时间限制(工作时间内提醒,避免深夜或清晨打扰) + const currentHour = new Date().getHours(); + if (currentHour < 9 || currentHour >= 21) { + console.log(`当前时间${currentHour}点,不在站立提醒时间范围内,跳过站立提醒`); + return; + } + + // 获取用户名 + const userName = userProfile?.name || '朋友'; + + // 调用站立提醒检查函数 + const notificationSent = await StandReminderHelpers.checkStandStatusAndNotify(userName); + + if (notificationSent) { + console.log('后台站立提醒通知已发送'); + // 记录后台任务执行时间 + await AsyncStorage.setItem('@last_background_stand_check', Date.now().toString()); + } else { + console.log('无需发送后台站立提醒通知'); + } + + } catch (error) { + console.error('执行站立提醒后台任务失败:', error); + } + } + + /** + * 检查通知权限 + */ + private async checkNotificationPermissions(): Promise { + try { + const Notifications = await import('expo-notifications'); + const { status } = await Notifications.getPermissionsAsync(); + return status === 'granted'; + } catch (error) { + console.error('检查通知权限失败:', error); + return false; + } + } + + /** + * 启动后台任务 + */ + async start(): Promise { + try { + await BackgroundFetch.start(); + console.log('后台任务已启动'); + } catch (error) { + console.error('启动后台任务失败:', error); + } + } + + /** + * 停止后台任务 + */ + async stop(): Promise { + try { + await BackgroundFetch.stop(); + console.log('后台任务已停止'); + } catch (error) { + console.error('停止后台任务失败:', error); + } + } + + /** + * 获取后台任务状态 + */ + async getStatus(): Promise { + try { + return await BackgroundFetch.status(); + } catch (error) { + console.error('获取后台任务状态失败:', error); + return BackgroundFetch.STATUS_DENIED; + } + } + + /** + * 检查后台任务状态 + */ + async checkStatus(): Promise { + const status = await this.getStatus(); + + switch (status) { + case BackgroundFetch.STATUS_AVAILABLE: + return '可用'; + case BackgroundFetch.STATUS_DENIED: + return '被拒绝'; + case BackgroundFetch.STATUS_RESTRICTED: + return '受限制'; + default: + return '未知'; + } + } + + /** + * 测试后台任务 + */ + async testBackgroundTask(): Promise { + console.log('开始测试后台任务...'); + + try { + // 手动触发后台任务执行 + await this.executeBackgroundTasks(); + console.log('后台任务测试完成'); + } catch (error) { + console.error('后台任务测试失败:', error); + } + } + + /** + * 注册喝水提醒后台任务 + */ + async registerWaterReminderTask(): Promise { + console.log('注册喝水提醒后台任务...'); + + try { + // 检查是否已经初始化 + if (!this.isInitialized) { + await this.initialize(); + } + + // 启动后台任务 + await this.start(); + + console.log('喝水提醒后台任务注册成功'); + } catch (error) { + console.error('注册喝水提醒后台任务失败:', error); + throw error; + } + } + + /** + * 取消喝水提醒后台任务 + */ + async unregisterWaterReminderTask(): Promise { + console.log('取消喝水提醒后台任务...'); + + try { + await this.stop(); + console.log('喝水提醒后台任务已取消'); + } catch (error) { + console.error('取消喝水提醒后台任务失败:', error); + throw error; + } + } + + /** + * 获取最后一次后台检查时间 + */ + async getLastBackgroundCheckTime(): Promise { + try { + const lastCheck = await AsyncStorage.getItem('@last_background_water_check'); + return lastCheck ? parseInt(lastCheck) : null; + } catch (error) { + console.error('获取最后后台检查时间失败:', error); + return null; + } + } + + /** + * 注册站立提醒后台任务 + */ + async registerStandReminderTask(): Promise { + console.log('注册站立提醒后台任务...'); + + try { + // 检查是否已经初始化 + if (!this.isInitialized) { + await this.initialize(); + } + + // 启动后台任务 + await this.start(); + + console.log('站立提醒后台任务注册成功'); + } catch (error) { + console.error('注册站立提醒后台任务失败:', error); + throw error; + } + } + + /** + * 取消站立提醒后台任务 + */ + async unregisterStandReminderTask(): Promise { + console.log('取消站立提醒后台任务...'); + + try { + // 取消所有相关通知 + await StandReminderHelpers.cancelStandReminders(); + console.log('站立提醒后台任务已取消'); + } catch (error) { + console.error('取消站立提醒后台任务失败:', error); + throw error; + } + } + + /** + * 获取最后一次站立检查时间 + */ + async getLastStandCheckTime(): Promise { + try { + const lastCheck = await AsyncStorage.getItem('@last_background_stand_check'); + return lastCheck ? parseInt(lastCheck) : null; + } catch (error) { + console.error('获取最后站立检查时间失败:', error); + return null; + } + } + + /** + * 测试站立提醒任务 + */ + async testStandReminderTask(): Promise { + console.log('开始测试站立提醒后台任务...'); + + try { + // 手动触发站立提醒任务执行 + await this.executeStandReminderTask(); + console.log('站立提醒后台任务测试完成'); + } catch (error) { + console.error('站立提醒后台任务测试失败:', error); + } + } +} + +/** + * 后台任务管理器单例实例 + */ +export const backgroundTaskManager = BackgroundTaskManager.getInstance(); + +/** + * 后台任务事件类型 + */ +export interface BackgroundTaskEvent { + taskId: string; + timestamp: number; + success: boolean; + error?: string; +} + +/** + * 后台任务配置选项 + */ +export interface BackgroundTaskConfig { + minimumFetchInterval?: number; + stopOnTerminate?: boolean; + startOnBoot?: boolean; + enableHeadless?: boolean; + requiredNetworkType?: 'NONE' | 'ANY' | 'CELLULAR' | 'WIFI'; + requiresCharging?: boolean; + requiresDeviceIdle?: boolean; + requiresBatteryNotLow?: boolean; +} + +/** + * 默认后台任务配置 + */ +export const DEFAULT_BACKGROUND_TASK_CONFIG: BackgroundTaskConfig = { + minimumFetchInterval: 15000, // 15分钟 + stopOnTerminate: false, + startOnBoot: true, + enableHeadless: true, + requiredNetworkType: 'ANY', + requiresCharging: false, + requiresDeviceIdle: false, + requiresBatteryNotLow: false, +}; \ No newline at end of file diff --git a/utils/health.ts b/utils/health.ts index 461557a..eb3ce82 100644 --- a/utils/health.ts +++ b/utils/health.ts @@ -460,13 +460,13 @@ export async function fetchTodayHRV(): Promise { // 获取最近几小时内的实时HRV数据 export async function fetchRecentHRV(hoursBack: number = 2): Promise { console.log(`开始获取最近${hoursBack}小时内的HRV数据...`); - + const now = new Date(); const options = { startDate: dayjs(now).subtract(hoursBack, 'hour').toDate().toISOString(), endDate: now.toISOString() }; - + return fetchHeartRateVariability(options); } @@ -543,18 +543,18 @@ export async function saveWaterIntakeToHealthKit(amount: number, recordedAt?: st endDate: recordedAt ? new Date(recordedAt).toISOString() : new Date().toISOString(), }; - AppleHealthKit.saveWater(waterOptions, (error: Object, result: boolean) => { + AppleHealthKit.saveWater(waterOptions, (error: Object, result) => { if (error) { console.error('添加饮水记录到 HealthKit 失败:', error); resolve(false); return; } - - console.log('成功添加饮水记录到 HealthKit:', { - originalAmount: amount, - convertedAmount: amount / 1000, - recordedAt, - result + + console.log('成功添加饮水记录到 HealthKit:', { + originalAmount: amount, + convertedAmount: amount / 1000, + recordedAt, + result }); resolve(true); }); @@ -570,7 +570,7 @@ export async function getWaterIntakeFromHealthKit(options: HealthDataOptions): P resolve([]); return; } - + console.log('从 HealthKit 获取饮水记录:', results); resolve(results || []); }); @@ -584,7 +584,31 @@ export async function deleteWaterIntakeFromHealthKit(recordId: string, recordedA // 这是一个占位函数,实际实现可能需要更复杂的逻辑 console.log('注意: HealthKit 通常不支持直接删除单条饮水记录'); console.log('记录信息:', { recordId, recordedAt }); - + // 返回 true 表示"成功"(但实际上可能没有真正删除) return Promise.resolve(true); } + +// 获取当前小时的站立状态 +export async function getCurrentHourStandStatus(): Promise<{ hasStood: boolean; standHours: number; standHoursGoal: number }> { + try { + const currentHour = new Date().getHours(); + console.log(`检查当前小时 ${currentHour} 的站立状态...`); + + // 获取今日健康数据 + const todayHealthData = await fetchTodayHealthData(); + + return { + hasStood: todayHealthData.standHours > currentHour - 1, // 如果站立小时数大于当前小时-1,说明当前小时已站立 + standHours: todayHealthData.standHours, + standHoursGoal: todayHealthData.standHoursGoal + }; + } catch (error) { + console.error('获取当前小时站立状态失败:', error); + return { + hasStood: true, // 默认认为已站立,避免过度提醒 + standHours: 0, + standHoursGoal: 12 + }; + } +} diff --git a/utils/notificationHelpers.ts b/utils/notificationHelpers.ts index c37cabb..9f84268 100644 --- a/utils/notificationHelpers.ts +++ b/utils/notificationHelpers.ts @@ -1,5 +1,6 @@ import * as Notifications from 'expo-notifications'; import { NotificationData, notificationService } from '../services/notifications'; +import { getNotificationEnabled } from './userPreferences'; /** * 构建 coach 页面的深度链接 @@ -917,6 +918,102 @@ export class GeneralNotificationHelpers { } } +/** + * 站立提醒通知助手 + */ +export class StandReminderHelpers { + /** + * 检查站立状态并发送提醒通知 + */ + static async checkStandStatusAndNotify(userName: string): Promise { + try { + console.log('检查站立状态并发送提醒通知...'); + + // 动态导入健康工具,避免循环依赖 + const { getCurrentHourStandStatus } = await import('@/utils/health'); + + // 获取当前小时站立状态 + const standStatus = await getCurrentHourStandStatus(); + + console.log('当前站立状态:', standStatus); + + // 如果已经站立过,不需要提醒 + if (standStatus.hasStood) { + console.log('用户当前小时已经站立,无需提醒'); + return false; + } + + // 检查时间范围(工作时间内提醒,避免深夜或清晨打扰) + const currentHour = new Date().getHours(); + if (currentHour < 9 || currentHour >= 21) { + console.log(`当前时间${currentHour}点,不在站立提醒时间范围内`); + return false; + } + + // 检查是否启用了通知 + if (!(await getNotificationEnabled())) { + console.log('用户未启用通知功能,跳过站立提醒'); + return false; + } + + // 生成提醒消息 + const reminderMessage = this.generateStandReminderMessage(userName, standStatus.standHours, standStatus.standHoursGoal); + + // 发送站立提醒通知 + await notificationService.sendImmediateNotification({ + title: '站立提醒', + body: reminderMessage, + data: { + type: 'stand_reminder', + currentStandHours: standStatus.standHours, + standHoursGoal: standStatus.standHoursGoal, + timestamp: Date.now() + }, + sound: true, + priority: 'normal', + }); + + console.log('站立提醒通知发送成功'); + return true; + + } catch (error) { + console.error('检查站立状态并发送提醒失败:', error); + return false; + } + } + + /** + * 生成站立提醒消息 + */ + private static generateStandReminderMessage(userName: string, currentStandHours: number, goalHours: number): string { + const currentHour = new Date().getHours(); + const progress = Math.round((currentStandHours / goalHours) * 100); + + const messages = [ + `${userName},该站起来活动一下了!当前已完成${progress}%的站立目标`, + `${userName},久坐伤身,起来走走吧~已站立${currentStandHours}/${goalHours}小时`, + `${userName},站立一会儿对健康有益,目前进度${currentStandHours}/${goalHours}小时`, + `${userName},记得起身活动哦!今日站立进度${progress}%` + ]; + + // 根据时间选择不同的消息 + const messageIndex = currentHour % messages.length; + return messages[messageIndex]; + } + + /** + * 取消所有站立提醒通知 + */ + static async cancelStandReminders(): Promise { + try { + await GeneralNotificationHelpers.cancelNotificationsByType('stand_reminder'); + console.log('已取消所有站立提醒通知'); + } catch (error) { + console.error('取消站立提醒通知失败:', error); + } + } +} + /** * 通知模板 */