feat(workout): 新增锻炼结束监听和个性化通知功能

实现了iOS HealthKit锻炼数据实时监听,当用户完成锻炼时自动发送个性化鼓励通知。包括锻炼类型筛选、时间范围控制、用户偏好设置等完整功能,并提供了测试工具和详细文档。
This commit is contained in:
richarjiang
2025-10-13 10:05:02 +08:00
parent 12883c5410
commit 971aebd560
18 changed files with 2210 additions and 1264 deletions

173
services/workoutMonitor.ts Normal file
View File

@@ -0,0 +1,173 @@
import { fetchRecentWorkouts, WorkoutData } from '@/utils/health';
import AsyncStorage from '@/utils/kvStore';
import { NativeEventEmitter, NativeModules } from 'react-native';
import { analyzeWorkoutAndSendNotification } from './workoutNotificationService';
const { HealthKitManager } = NativeModules;
const workoutEmitter = new NativeEventEmitter(HealthKitManager);
class WorkoutMonitorService {
private static instance: WorkoutMonitorService;
private isInitialized = false;
private lastProcessedWorkoutId: string | null = null;
private processingTimeout: any = null;
private eventListenerSubscription: any = null;
static getInstance(): WorkoutMonitorService {
if (!WorkoutMonitorService.instance) {
WorkoutMonitorService.instance = new WorkoutMonitorService();
}
return WorkoutMonitorService.instance;
}
async initialize(): Promise<void> {
if (this.isInitialized) {
console.log('锻炼监听服务已初始化');
return;
}
try {
// 获取上次处理的锻炼ID
await this.loadLastProcessedWorkoutId();
// 启动 iOS 原生锻炼监听器
await HealthKitManager.startWorkoutObserver();
// 监听锻炼更新事件
this.eventListenerSubscription = workoutEmitter.addListener(
'workoutUpdate',
this.handleWorkoutUpdate.bind(this)
);
this.isInitialized = true;
console.log('锻炼监听服务初始化成功');
} catch (error) {
console.error('锻炼监听服务初始化失败:', error);
throw error;
}
}
async stop(): Promise<void> {
try {
// 停止原生监听器
await HealthKitManager.stopWorkoutObserver();
// 移除事件监听器
if (this.eventListenerSubscription) {
this.eventListenerSubscription.remove();
this.eventListenerSubscription = null;
}
// 清理定时器
if (this.processingTimeout) {
clearTimeout(this.processingTimeout);
this.processingTimeout = null;
}
this.isInitialized = false;
console.log('锻炼监听服务已停止');
} catch (error) {
console.error('停止锻炼监听服务失败:', error);
}
}
private async loadLastProcessedWorkoutId(): Promise<void> {
try {
const storedId = await AsyncStorage.getItem('@last_processed_workout_id');
this.lastProcessedWorkoutId = storedId;
console.log('上次处理的锻炼ID:', this.lastProcessedWorkoutId);
} catch (error) {
console.error('加载上次处理的锻炼ID失败:', error);
}
}
private async saveLastProcessedWorkoutId(workoutId: string): Promise<void> {
try {
await AsyncStorage.setItem('@last_processed_workout_id', workoutId);
this.lastProcessedWorkoutId = workoutId;
} catch (error) {
console.error('保存上次处理的锻炼ID失败:', error);
}
}
private async handleWorkoutUpdate(event: any): Promise<void> {
console.log('收到锻炼更新事件:', event);
// 防抖处理,避免短时间内重复处理
if (this.processingTimeout) {
clearTimeout(this.processingTimeout);
}
this.processingTimeout = setTimeout(async () => {
try {
await this.checkForNewWorkouts();
} catch (error) {
console.error('检查新锻炼失败:', error);
}
}, 5000); // 5秒延迟确保 HealthKit 数据已完全更新
}
private async checkForNewWorkouts(): Promise<void> {
try {
console.log('检查新的锻炼记录...');
// 获取最近1小时的锻炼记录
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
const recentWorkouts = await fetchRecentWorkouts({
startDate: oneHourAgo.toISOString(),
endDate: new Date().toISOString(),
limit: 10
});
console.log(`找到 ${recentWorkouts.length} 条最近的锻炼记录`);
// 检查是否有新的锻炼记录
for (const workout of recentWorkouts) {
if (workout.id !== this.lastProcessedWorkoutId) {
console.log('检测到新锻炼:', {
id: workout.id,
type: workout.workoutActivityTypeString,
duration: workout.duration,
startDate: workout.startDate
});
await this.processNewWorkout(workout);
await this.saveLastProcessedWorkoutId(workout.id);
} else {
console.log('锻炼已处理过,跳过:', workout.id);
}
}
} catch (error) {
console.error('检查新锻炼失败:', error);
}
}
private async processNewWorkout(workout: WorkoutData): Promise<void> {
try {
console.log('开始处理新锻炼:', workout.id);
// 分析锻炼并发送通知
await analyzeWorkoutAndSendNotification(workout);
console.log('新锻炼处理完成:', workout.id);
} catch (error) {
console.error('处理新锻炼失败:', error);
}
}
// 手动触发检查(用于测试)
async manualCheck(): Promise<void> {
console.log('手动触发锻炼检查...');
await this.checkForNewWorkouts();
}
// 获取服务状态
getStatus(): { initialized: boolean; lastProcessedWorkoutId: string | null } {
return {
initialized: this.isInitialized,
lastProcessedWorkoutId: this.lastProcessedWorkoutId
};
}
}
export const workoutMonitorService = WorkoutMonitorService.getInstance();