Files
digital-pilates/services/backgroundTaskManager.ts
richarjiang 83805a4b07 feat: Refactor MoodCalendarScreen to use dayjs for date handling and improve calendar data generation
feat: Update FitnessRingsCard to navigate to fitness rings detail page on press

feat: Modify NutritionRadarCard to enhance UI and add haptic feedback on actions

feat: Add FITNESS_RINGS_DETAIL route for navigation

fix: Adjust minimum fetch interval in BackgroundTaskManager for background tasks

feat: Implement haptic feedback utility functions for better user experience

feat: Extend health permissions to include Apple Exercise Time and Apple Stand Time

feat: Add functions to fetch hourly activity, exercise, and stand data for improved health tracking

feat: Enhance user preferences to manage fitness exercise minutes and active hours info dismissal
2025-09-05 15:32:34 +08:00

440 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<void> {
if (this.isInitialized) {
console.log('后台任务管理器已初始化');
return;
}
try {
// 配置后台获取
const status = await BackgroundFetch.configure({
minimumFetchInterval: 15, // 最小间隔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<void> {
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<void> {
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<void> {
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<boolean> {
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<void> {
try {
await BackgroundFetch.start();
console.log('后台任务已启动');
} catch (error) {
console.error('启动后台任务失败:', error);
}
}
/**
* 停止后台任务
*/
async stop(): Promise<void> {
try {
await BackgroundFetch.stop();
console.log('后台任务已停止');
} catch (error) {
console.error('停止后台任务失败:', error);
}
}
/**
* 获取后台任务状态
*/
async getStatus(): Promise<number> {
try {
return await BackgroundFetch.status();
} catch (error) {
console.error('获取后台任务状态失败:', error);
return BackgroundFetch.STATUS_DENIED;
}
}
/**
* 检查后台任务状态
*/
async checkStatus(): Promise<string> {
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<void> {
console.log('开始测试后台任务...');
try {
// 手动触发后台任务执行
await this.executeBackgroundTasks();
console.log('后台任务测试完成');
} catch (error) {
console.error('后台任务测试失败:', error);
}
}
/**
* 注册喝水提醒后台任务
*/
async registerWaterReminderTask(): Promise<void> {
console.log('注册喝水提醒后台任务...');
try {
// 检查是否已经初始化
if (!this.isInitialized) {
await this.initialize();
}
// 启动后台任务
await this.start();
console.log('喝水提醒后台任务注册成功');
} catch (error) {
console.error('注册喝水提醒后台任务失败:', error);
throw error;
}
}
/**
* 取消喝水提醒后台任务
*/
async unregisterWaterReminderTask(): Promise<void> {
console.log('取消喝水提醒后台任务...');
try {
await this.stop();
console.log('喝水提醒后台任务已取消');
} catch (error) {
console.error('取消喝水提醒后台任务失败:', error);
throw error;
}
}
/**
* 获取最后一次后台检查时间
*/
async getLastBackgroundCheckTime(): Promise<number | null> {
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<void> {
console.log('注册站立提醒后台任务...');
try {
// 检查是否已经初始化
if (!this.isInitialized) {
await this.initialize();
}
// 启动后台任务
await this.start();
console.log('站立提醒后台任务注册成功');
} catch (error) {
console.error('注册站立提醒后台任务失败:', error);
throw error;
}
}
/**
* 取消站立提醒后台任务
*/
async unregisterStandReminderTask(): Promise<void> {
console.log('取消站立提醒后台任务...');
try {
// 取消所有相关通知
await StandReminderHelpers.cancelStandReminders();
console.log('站立提醒后台任务已取消');
} catch (error) {
console.error('取消站立提醒后台任务失败:', error);
throw error;
}
}
/**
* 获取最后一次站立检查时间
*/
async getLastStandCheckTime(): Promise<number | null> {
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<void> {
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,
};