- Removed NitroModules and ReactNativeHealthkit from Podfile.lock and package files. - Updated Info.plist to increment app version from 2 to 3. - Refactored background task manager to define background tasks within the class. - Added new utility file for sleep data management, including fetching sleep samples, calculating sleep statistics, and generating sleep quality scores.
316 lines
8.7 KiB
TypeScript
316 lines
8.7 KiB
TypeScript
import { store } from '@/store';
|
|
import { StandReminderHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers';
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import * as BackgroundTask from 'expo-background-task';
|
|
import * as TaskManager from 'expo-task-manager';
|
|
import { TaskManagerTaskBody } from 'expo-task-manager';
|
|
|
|
const BACKGROUND_TASK_IDENTIFIER = 'background-task';
|
|
|
|
|
|
|
|
// 检查通知权限
|
|
async function 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 function 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);
|
|
}
|
|
}
|
|
|
|
// 执行站立提醒后台任务
|
|
async function 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);
|
|
}
|
|
}
|
|
|
|
// 发送测试通知以验证后台任务执行
|
|
async function sendTestNotification(): Promise<void> {
|
|
try {
|
|
console.log('发送后台任务测试通知...');
|
|
|
|
const Notifications = await import('expo-notifications');
|
|
|
|
// 发送简单的测试通知
|
|
await Notifications.scheduleNotificationAsync({
|
|
content: {
|
|
title: '后台任务测试',
|
|
body: `后台任务正在执行中... 时间: ${new Date().toLocaleTimeString()}`,
|
|
data: {
|
|
type: 'background_task_test',
|
|
timestamp: Date.now()
|
|
}
|
|
},
|
|
trigger: null, // 立即发送
|
|
});
|
|
|
|
console.log('后台任务测试通知发送成功');
|
|
|
|
// 记录测试通知发送时间
|
|
await AsyncStorage.setItem('@last_background_test_notification', Date.now().toString());
|
|
|
|
} catch (error) {
|
|
console.error('发送测试通知失败:', error);
|
|
}
|
|
}
|
|
|
|
// 后台任务执行函数
|
|
async function executeBackgroundTasks(): Promise<void> {
|
|
console.log('开始执行后台任务...');
|
|
|
|
try {
|
|
// 检查应用权限和用户设置
|
|
const hasPermission = await checkNotificationPermissions();
|
|
if (!hasPermission) {
|
|
console.log('没有通知权限,跳过后台任务');
|
|
return;
|
|
}
|
|
|
|
await sendTestNotification()
|
|
|
|
// 执行喝水提醒检查任务
|
|
await executeWaterReminderTask();
|
|
|
|
// 执行站立提醒检查任务
|
|
await executeStandReminderTask();
|
|
|
|
console.log('后台任务执行完成');
|
|
} catch (error) {
|
|
console.error('执行后台任务失败:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 后台任务管理器
|
|
* 负责配置和管理应用的后台任务执行
|
|
*/
|
|
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 {
|
|
|
|
// 定义后台任务
|
|
TaskManager.defineTask(BACKGROUND_TASK_IDENTIFIER, async (body: TaskManagerTaskBody) => {
|
|
try {
|
|
console.log('[BackgroundTask] 后台任务执行');
|
|
await executeBackgroundTasks();
|
|
return BackgroundTask.BackgroundTaskResult.Success;
|
|
} catch (error) {
|
|
console.error('[BackgroundTask] 任务执行失败:', error);
|
|
return BackgroundTask.BackgroundTaskResult.Failed;
|
|
}
|
|
});
|
|
|
|
|
|
// 注册后台任务
|
|
const status = await BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDENTIFIER, {
|
|
minimumInterval: 15,
|
|
});
|
|
|
|
console.log('[BackgroundTask] 配置状态:', status);
|
|
|
|
this.isInitialized = true;
|
|
console.log('后台任务管理器初始化完成');
|
|
|
|
} catch (error) {
|
|
console.error('初始化后台任务管理器失败:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 停止后台任务
|
|
*/
|
|
async stop(): Promise<void> {
|
|
try {
|
|
await BackgroundTask.unregisterTaskAsync(BACKGROUND_TASK_IDENTIFIER);
|
|
console.log('后台任务已停止');
|
|
} catch (error) {
|
|
console.error('停止后台任务失败:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取后台任务状态
|
|
*/
|
|
async getStatus(): Promise<BackgroundTask.BackgroundTaskStatus> {
|
|
try {
|
|
const status = await BackgroundTask.getStatusAsync();
|
|
return status || BackgroundTask.BackgroundTaskStatus.Restricted;
|
|
} catch (error) {
|
|
console.error('获取后台任务状态失败:', error);
|
|
return BackgroundTask.BackgroundTaskStatus.Restricted;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查后台任务状态
|
|
*/
|
|
async checkStatus(): Promise<string> {
|
|
const status = await this.getStatus();
|
|
|
|
switch (status) {
|
|
case BackgroundTask.BackgroundTaskStatus.Available:
|
|
return '可用';
|
|
case BackgroundTask.BackgroundTaskStatus.Restricted:
|
|
return '受限制';
|
|
default:
|
|
return '未知';
|
|
}
|
|
}
|
|
|
|
async triggerTaskForTesting(): Promise<void> {
|
|
await BackgroundTask.triggerTaskWorkerForTestingAsync();
|
|
}
|
|
|
|
|
|
/**
|
|
* 测试后台任务
|
|
*/
|
|
async testBackgroundTask(): Promise<void> {
|
|
console.log('开始测试后台任务...');
|
|
|
|
try {
|
|
// 手动触发后台任务执行
|
|
await executeBackgroundTasks();
|
|
console.log('后台任务测试完成');
|
|
} catch (error) {
|
|
console.error('后台任务测试失败:', 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 后台任务管理器单例实例
|
|
*/
|
|
export const backgroundTaskManager = BackgroundTaskManager.getInstance();
|
|
|
|
/**
|
|
* 后台任务事件类型
|
|
*/
|
|
export interface BackgroundTaskEvent {
|
|
taskId: string;
|
|
timestamp: number;
|
|
success: boolean;
|
|
error?: string;
|
|
}
|