diff --git a/app.json b/app.json index aa51e43..231e1f9 100644 --- a/app.json +++ b/app.json @@ -79,6 +79,20 @@ "drink_water": "./assets/images/icons/IconGlass.png" } } + ], + [ + "expo-background-task", + { + "minimumInterval": 15 + } + ], + [ + "expo-task-manager", + { + "taskManagers": [ + "background-health-reminders" + ] + } ] ], "experiments": { diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ce48827..b597d4b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -40,6 +40,8 @@ PODS: - ExpoModulesCore - ExpoAsset (11.1.7): - ExpoModulesCore + - ExpoBackgroundTask (0.2.8): + - ExpoModulesCore - ExpoBlur (14.1.5): - ExpoModulesCore - ExpoCamera (16.1.11): @@ -102,6 +104,9 @@ PODS: - ExpoModulesCore - ExpoWebBrowser (14.2.0): - ExpoModulesCore + - EXTaskManager (13.1.6): + - ExpoModulesCore + - UMAppLoader - fast_float (6.1.4) - FBLazyVector (0.79.5) - fmt (11.0.2) @@ -1741,8 +1746,6 @@ 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): @@ -1984,6 +1987,7 @@ PODS: - SDWebImage/Core (~> 5.17) - Sentry/HybridSDK (8.53.2) - SocketRocket (0.7.1) + - UMAppLoader (5.1.3) - Yoga (0.0.0) - ZXingObjC/Core (3.6.9) - ZXingObjC/OneD (3.6.9): @@ -2001,6 +2005,7 @@ DEPENDENCIES: - Expo (from `../node_modules/expo`) - ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`) - ExpoAsset (from `../node_modules/expo-asset/ios`) + - ExpoBackgroundTask (from `../node_modules/expo-background-task/ios`) - ExpoBlur (from `../node_modules/expo-blur/ios`) - ExpoCamera (from `../node_modules/expo-camera/ios`) - ExpoFileSystem (from `../node_modules/expo-file-system/ios`) @@ -2018,6 +2023,7 @@ DEPENDENCIES: - ExpoSymbols (from `../node_modules/expo-symbols/ios`) - ExpoSystemUI (from `../node_modules/expo-system-ui/ios`) - ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`) + - EXTaskManager (from `../node_modules/expo-task-manager/ios`) - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) @@ -2091,7 +2097,6 @@ 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`)" @@ -2104,6 +2109,7 @@ DEPENDENCIES: - RNScreens (from `../node_modules/react-native-screens`) - "RNSentry (from `../node_modules/@sentry/react-native`)" - RNSVG (from `../node_modules/react-native-svg`) + - UMAppLoader (from `../node_modules/unimodules-app-loader/ios`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -2144,6 +2150,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-apple-authentication/ios" ExpoAsset: :path: "../node_modules/expo-asset/ios" + ExpoBackgroundTask: + :path: "../node_modules/expo-background-task/ios" ExpoBlur: :path: "../node_modules/expo-blur/ios" ExpoCamera: @@ -2178,6 +2186,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-system-ui/ios" ExpoWebBrowser: :path: "../node_modules/expo-web-browser/ios" + EXTaskManager: + :path: "../node_modules/expo-task-manager/ios" fast_float: :podspec: "../node_modules/react-native/third-party-podspecs/fast_float.podspec" FBLazyVector: @@ -2320,8 +2330,6 @@ 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: @@ -2346,6 +2354,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@sentry/react-native" RNSVG: :path: "../node_modules/react-native-svg" + UMAppLoader: + :path: "../node_modules/unimodules-app-loader/ios" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" @@ -2359,6 +2369,7 @@ SPEC CHECKSUMS: Expo: c9e30ab79606b3800733594a961528bc4abb0ffe ExpoAppleAuthentication: 4d2e0c88a4463229760f1fbb9a937a810efb6863 ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6 + ExpoBackgroundTask: 6c1990438e45b5c4bbbc7d75aa6b688d53602fe8 ExpoBlur: 3c8885b9bf9eef4309041ec87adec48b5f1986a9 ExpoCamera: e1879906d41184e84b57d7643119f8509414e318 ExpoFileSystem: 7f92f7be2f5c5ed40a7c9efc8fa30821181d9d63 @@ -2376,6 +2387,7 @@ SPEC CHECKSUMS: ExpoSymbols: c5612a90fb9179cdaebcd19bea9d8c69e5d3b859 ExpoSystemUI: 433a971503b99020318518ed30a58204288bab2d ExpoWebBrowser: dc39a88485f007e61a3dff05d6a75f22ab4a2e92 + EXTaskManager: 280143f6d8e596f28739d74bf34910300dcbd4ea fast_float: 23278fd30b349f976d2014f4aec9e2d7bc1c3806 FBLazyVector: d2a9cd223302b6c9aa4aa34c1a775e9db609eb52 fmt: b85d977e8fe789fd71c77123f9f4920d88c4d170 @@ -2456,7 +2468,6 @@ SPEC CHECKSUMS: ReactCommon: 7eb76fcd5133313d8c6a138a5c7dd89f80f189d5 RevenueCat: eb2aa042789d9c99ad5172bd96e28b96286d6ada RNAppleHealthKit: 86ef7ab70f762b802f5c5289372de360cca701f9 - RNBackgroundFetch: e44c9e85d7fb3122c37d8a806278f62c7682d7ea RNCAsyncStorage: b44e8a4e798c3e1f56bffccd0f591f674fb9198f RNCMaskedView: d4644e239e65383f96d2f32c40c297f09705ac96 RNCPicker: da0f1c9411208c1ca52bc98383db54a06e0a3862 @@ -2475,6 +2486,7 @@ SPEC CHECKSUMS: SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 Sentry: 59993bffde4a1ac297ba6d268dc4bbce068d7c1b SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 + UMAppLoader: 55159b69750129faa7a51c493cb8ea55a7b64eb9 Yoga: adb397651e1c00672c12e9495babca70777e411e ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 diff --git a/ios/digitalpilates.xcodeproj/project.pbxproj b/ios/digitalpilates.xcodeproj/project.pbxproj index 3c002d1..f57ca3c 100644 --- a/ios/digitalpilates.xcodeproj/project.pbxproj +++ b/ios/digitalpilates.xcodeproj/project.pbxproj @@ -268,12 +268,12 @@ "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXNotifications/ExpoNotifications_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXTaskManager/ExpoTaskManager_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", "${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", @@ -293,12 +293,12 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoTaskManager_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", "${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/ios/digitalpilates/AppDelegate.swift b/ios/digitalpilates/AppDelegate.swift index e3514aa..a7887e1 100644 --- a/ios/digitalpilates/AppDelegate.swift +++ b/ios/digitalpilates/AppDelegate.swift @@ -1,7 +1,6 @@ import Expo import React import ReactAppDependencyProvider -import TSBackgroundFetch @UIApplicationMain public class AppDelegate: ExpoAppDelegate { @@ -22,8 +21,6 @@ public class AppDelegate: ExpoAppDelegate { reactNativeFactory = factory bindReactNativeFactory(factory) - TSBackgroundFetch.sharedInstance().didFinishLaunching(); - #if os(iOS) || os(tvOS) window = UIWindow(frame: UIScreen.main.bounds) factory.startReactNative( diff --git a/package-lock.json b/package-lock.json index 264a922..a665ce6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "dayjs": "^1.11.13", "expo": "53.0.22", "expo-apple-authentication": "~7.2.4", + "expo-background-task": "~0.2.8", "expo-blur": "~14.1.5", "expo-camera": "^16.1.11", "expo-constants": "~17.1.7", @@ -39,13 +40,13 @@ "expo-status-bar": "~2.2.3", "expo-symbols": "~0.4.5", "expo-system-ui": "~5.0.11", + "expo-task-manager": "~13.1.6", "expo-web-browser": "~14.2.0", "lodash": "^4.17.21", "lottie-react-native": "^7.3.4", "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", @@ -7125,6 +7126,18 @@ "react-native": "*" } }, + "node_modules/expo-background-task": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/expo-background-task/-/expo-background-task-0.2.8.tgz", + "integrity": "sha512-dePyskpmyDZeOtbr9vWFh+Nrse0TvF6YitJqnKcd+3P7pDMiDr1V2aT6zHdNOc5iV9vPaDJoH/zdmlarp1uHMQ==", + "license": "MIT", + "dependencies": { + "expo-task-manager": "~13.1.6" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-blur": { "version": "14.1.5", "resolved": "https://registry.npmjs.org/expo-blur/-/expo-blur-14.1.5.tgz", @@ -7455,6 +7468,19 @@ "integrity": "sha512-0v2/ruY7eeKun4BeKu+GcfO+SHBdl0LJn4ZFzTzjHdWES0Cn+ONqKljYaIv8p9MV2Hx/kcdEvbY4lWI34jC/mQ==", "license": "MIT" }, + "node_modules/expo-task-manager": { + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/expo-task-manager/-/expo-task-manager-13.1.6.tgz", + "integrity": "sha512-sYNAftpIeZ+j6ur17Jo0OpSTk9ks/MDvTbrNCimXMyjIt69XXYL/kAPYf76bWuxOuN8bcJ8Ef8YvihkwFG9hDA==", + "license": "MIT", + "dependencies": { + "unimodules-app-loader": "~5.1.3" + }, + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, "node_modules/expo-web-browser": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-14.2.0.tgz", @@ -11435,12 +11461,6 @@ } } }, - "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", @@ -13884,6 +13904,12 @@ "node": ">=4" } }, + "node_modules/unimodules-app-loader": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unimodules-app-loader/-/unimodules-app-loader-5.1.3.tgz", + "integrity": "sha512-nPUkwfkpJWvdOQrVvyQSUol93/UdmsCVd9Hkx9RgAevmKSVYdZI+S87W73NGKl6QbwK9L1BDSY5OrQuo8Oq15g==", + "license": "MIT" + }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", diff --git a/package.json b/package.json index 7519bb7..7eaaa9c 100644 --- a/package.json +++ b/package.json @@ -43,13 +43,13 @@ "expo-status-bar": "~2.2.3", "expo-symbols": "~0.4.5", "expo-system-ui": "~5.0.11", + "expo-task-manager": "~13.1.6", "expo-web-browser": "~14.2.0", "lodash": "^4.17.21", "lottie-react-native": "^7.3.4", "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", @@ -69,7 +69,8 @@ "react-native-web": "~0.20.0", "react-native-webview": "13.13.5", "react-native-wheel-picker-expo": "^0.5.4", - "react-redux": "^9.2.0" + "react-redux": "^9.2.0", + "expo-background-task": "~0.2.8" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/services/backgroundTaskManager.ts b/services/backgroundTaskManager.ts index a7a3eb6..c8eb95d 100644 --- a/services/backgroundTaskManager.ts +++ b/services/backgroundTaskManager.ts @@ -1,7 +1,8 @@ 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'; +import * as BackgroundTask from 'expo-background-task'; +import * as TaskManager from 'expo-task-manager'; /** * 后台任务标识符 @@ -9,11 +10,148 @@ import BackgroundFetch from 'react-native-background-fetch'; export const BACKGROUND_TASK_IDS = { WATER_REMINDER: 'water-reminder-task', STAND_REMINDER: 'stand-reminder-task', + HEALTH_REMINDERS: 'background-health-reminders', } as const; +// 定义后台任务 +TaskManager.defineTask(BACKGROUND_TASK_IDS.HEALTH_REMINDERS, async () => { + try { + console.log('[BackgroundTask] 后台任务执行'); + await executeBackgroundTasks(); + return BackgroundTask.BackgroundTaskResult.Success; + } catch (error) { + console.error('[BackgroundTask] 任务执行失败:', error); + return BackgroundTask.BackgroundTaskResult.Failed; + } +}); + +// 检查通知权限 +async function 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 function 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); + } +} + +// 执行站立提醒后台任务 +async function 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); + } +} + +// 后台任务执行函数 +async function executeBackgroundTasks(): Promise { + console.log('开始执行后台任务...'); + + try { + // 检查应用权限和用户设置 + const hasPermission = await checkNotificationPermissions(); + if (!hasPermission) { + console.log('没有通知权限,跳过后台任务'); + return; + } + + // 执行喝水提醒检查任务 + await executeWaterReminderTask(); + + // 执行站立提醒检查任务 + await executeStandReminderTask(); + + console.log('后台任务执行完成'); + } catch (error) { + console.error('执行后台任务失败:', error); + } +} + /** * 后台任务管理器 - * 负责配置和管理 iOS 应用的后台任务执行 + * 负责配置和管理应用的后台任务执行 */ export class BackgroundTaskManager { private static instance: BackgroundTaskManager; @@ -36,194 +174,32 @@ export class BackgroundTaskManager { } try { - // 配置后台获取 - const status = await BackgroundFetch.configure({ - minimumFetchInterval: 15, // 最小间隔15分钟(单位:秒) - }, async (taskId) => { - console.log('[BackgroundFetch] 后台任务执行:', taskId); - await this.executeBackgroundTasks(); - // 完成任务 - BackgroundFetch.finish(taskId); - }, (error) => { - console.error('[BackgroundFetch] 配置失败:', error); + // 注册后台任务 + const status = await BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDS.HEALTH_REMINDERS, { + minimumInterval: 15, // 15分钟 }); - console.log('[BackgroundFetch] 配置状态:', status); + console.log('[BackgroundTask] 配置状态:', status); this.isInitialized = true; console.log('后台任务管理器初始化完成'); - // 初始化完成后自动启动后台任务 - await this.start(); - 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; - } - - // 获取用户名 - const state = store.getState(); - const userName = state.user.profile?.name || '朋友'; - - // 发送测试通知 - const Notifications = await import('expo-notifications'); - - await Notifications.scheduleNotificationAsync({ - content: { - title: '测试通知', - body: `你好 ${userName}!这是一条测试消息,用于验证通知功能是否正常工作。`, - data: { type: 'test_notification', timestamp: Date.now() }, - sound: true, - priority: Notifications.AndroidNotificationPriority.HIGH, - }, - trigger: null, // 立即发送 - }); - - // 执行喝水提醒检查任务 - 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 { - BackgroundFetch.scheduleTask({ - taskId: 'com.anonymous.digitalpilates.backgroundfetch', - delay: 15 * 60 * 1000 + await BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDS.HEALTH_REMINDERS, { + minimumInterval: 15, }); - - await BackgroundFetch.start(); console.log('后台任务已启动'); } catch (error) { console.error('启动后台任务失败:', error); @@ -235,7 +211,7 @@ export class BackgroundTaskManager { */ async stop(): Promise { try { - await BackgroundFetch.stop(); + await BackgroundTask.unregisterTaskAsync(BACKGROUND_TASK_IDS.HEALTH_REMINDERS); console.log('后台任务已停止'); } catch (error) { console.error('停止后台任务失败:', error); @@ -245,12 +221,13 @@ export class BackgroundTaskManager { /** * 获取后台任务状态 */ - async getStatus(): Promise { + async getStatus(): Promise { try { - return await BackgroundFetch.status(); + const status = await BackgroundTask.getStatusAsync(); + return status || BackgroundTask.BackgroundTaskStatus.Restricted; } catch (error) { console.error('获取后台任务状态失败:', error); - return BackgroundFetch.STATUS_DENIED; + return BackgroundTask.BackgroundTaskStatus.Restricted; } } @@ -261,11 +238,9 @@ export class BackgroundTaskManager { const status = await this.getStatus(); switch (status) { - case BackgroundFetch.STATUS_AVAILABLE: + case BackgroundTask.BackgroundTaskStatus.Available: return '可用'; - case BackgroundFetch.STATUS_DENIED: - return '被拒绝'; - case BackgroundFetch.STATUS_RESTRICTED: + case BackgroundTask.BackgroundTaskStatus.Restricted: return '受限制'; default: return '未知'; @@ -280,7 +255,7 @@ export class BackgroundTaskManager { try { // 手动触发后台任务执行 - await this.executeBackgroundTasks(); + await executeBackgroundTasks(); console.log('后台任务测试完成'); } catch (error) { console.error('后台任务测试失败:', error); @@ -396,7 +371,7 @@ export class BackgroundTaskManager { try { // 手动触发站立提醒任务执行 - await this.executeStandReminderTask(); + await executeStandReminderTask(); console.log('站立提醒后台任务测试完成'); } catch (error) { console.error('站立提醒后台任务测试失败:', error);