Files
digital-pilates/services/workoutNotificationService.ts
richarjiang f80a1bae78 feat(background-task): 实现iOS原生后台任务V2系统并重构锻炼通知消息模板
- 新增iOS原生BackgroundTaskBridge桥接模块,支持后台任务注册、调度和完成
- 重构BackgroundTaskManager为V2版本,集成原生iOS后台任务能力
- 在AppDelegate中注册后台任务处理器,确保应用启动时正确初始化
- 重构锻炼通知消息生成逻辑,使用配置化模板提升可维护性
- 扩展健康数据类型映射,支持更多运动项目的中文显示
- 替换原有backgroundTaskManager引用为backgroundTaskManagerV2
2025-11-04 09:41:10 +08:00

271 lines
8.4 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 { getWorkoutTypeDisplayName, WorkoutData } from '@/utils/health';
import { getNotificationEnabled } from '@/utils/userPreferences';
import {
getWorkoutNotificationEnabled,
isNotificationTimeAllowed,
isWorkoutTypeEnabled
} from '@/utils/workoutPreferences';
import { notificationService, NotificationTypes } from './notifications';
import { getWorkoutDetailMetrics } from './workoutDetail';
interface WorkoutEncouragementMessage {
title: string;
body: string;
data: Record<string, any>;
}
export async function analyzeWorkoutAndSendNotification(workout: WorkoutData): Promise<void> {
try {
// 检查用户是否启用了通用通知
const notificationsEnabled = await getNotificationEnabled();
if (!notificationsEnabled) {
console.log('用户已禁用通知,跳过锻炼结束通知');
return;
}
// 检查用户是否启用了锻炼通知
const workoutNotificationsEnabled = await getWorkoutNotificationEnabled();
if (!workoutNotificationsEnabled) {
console.log('用户已禁用锻炼通知,跳过锻炼结束通知');
return;
}
// 检查时间限制(避免深夜打扰)
const timeAllowed = await isNotificationTimeAllowed();
if (!timeAllowed) {
console.log('当前时间不适合发送通知,跳过锻炼结束通知');
return;
}
// 检查特定锻炼类型是否启用了通知
const workoutTypeEnabled = await isWorkoutTypeEnabled(workout.workoutActivityTypeString || '');
if (!workoutTypeEnabled) {
console.log('该锻炼类型已禁用通知,跳过锻炼结束通知:', workout.workoutActivityTypeString);
return;
}
// 获取详细的锻炼指标
const workoutMetrics = await getWorkoutDetailMetrics(workout);
// 生成个性化鼓励消息
const message = generateEncouragementMessage(workout, workoutMetrics);
// 发送通知
await notificationService.sendImmediateNotification({
title: message.title,
body: message.body,
data: {
type: NotificationTypes.WORKOUT_COMPLETION,
workoutId: workout.id,
workoutType: workout.workoutActivityTypeString,
duration: workout.duration,
calories: workout.totalEnergyBurned,
...message.data
},
sound: true,
priority: 'high'
});
console.log('锻炼结束通知已发送:', message.title);
} catch (error) {
console.error('发送锻炼结束通知失败:', error);
}
}
interface WorkoutMessageConfig {
emoji: string;
titleTemplate: string;
bodyTemplate: (params: {
workoutType: string;
durationMinutes: number;
calories: number;
distanceKm?: string;
averageHeartRate?: number;
mets?: number;
}) => string;
encouragement: string;
dataExtractor?: (workout: WorkoutData, metrics: any) => Record<string, any>;
}
const WORKOUT_MESSAGES: Record<string, WorkoutMessageConfig> = {
running: {
emoji: '🏃‍♂️',
titleTemplate: '跑步完成!',
bodyTemplate: ({ durationMinutes, calories, averageHeartRate }) => {
let body = `您完成了${durationMinutes}分钟的跑步`;
if (calories > 0) {
body += `,消耗${calories}千卡`;
}
if (averageHeartRate) {
body += `(平均心率${averageHeartRate}次/分)`;
}
return body + '';
},
encouragement: '坚持就是胜利!💪',
dataExtractor: (workout, metrics) => ({
heartRateContext: metrics.averageHeartRate ? 'provided' : 'none'
})
},
cycling: {
emoji: '🚴‍♂️',
titleTemplate: '骑行完成!',
bodyTemplate: ({ durationMinutes, calories, distanceKm }) => {
let body = `${durationMinutes}分钟骑行`;
if (distanceKm) {
body += `,行程${distanceKm}公里`;
}
if (calories > 0) {
body += `,消耗${calories}千卡`;
}
return body + '';
},
encouragement: '追寻风的自由!🌟'
},
swimming: {
emoji: '🏊‍♂️',
titleTemplate: '游泳完成!',
bodyTemplate: ({ durationMinutes }) => {
return `水中${durationMinutes}分钟的锻炼完成!`;
},
encouragement: '全身肌肉都得到了锻炼!💦'
},
yoga: {
emoji: '🧘‍♀️',
titleTemplate: '瑜伽完成!',
bodyTemplate: ({ durationMinutes }) => {
return `${durationMinutes}分钟的瑜伽练习`;
},
encouragement: '身心合一,平静致远!🌸'
},
functionalstrengthtraining: {
emoji: '💪',
titleTemplate: '力量训练完成!',
bodyTemplate: ({ durationMinutes, calories, mets }) => {
let body = `${durationMinutes}分钟力量训练`;
if (calories > 0) {
body += `,消耗${calories}千卡`;
}
if (mets && mets > 6) {
body += '(高强度)';
}
return body + '';
},
encouragement: '肌肉正在变得更强壮!🔥',
dataExtractor: (workout, metrics) => ({
strengthLevel: metrics?.mets && metrics.mets > 6 ? 'high' : 'moderate'
})
},
traditionalstrengthtraining: {
emoji: '💪',
titleTemplate: '力量训练完成!',
bodyTemplate: ({ durationMinutes, calories, mets }) => {
let body = `${durationMinutes}分钟力量训练`;
if (calories > 0) {
body += `,消耗${calories}千卡`;
}
if (mets && mets > 6) {
body += '(高强度)';
}
return body + '';
},
encouragement: '肌肉正在变得更强壮!🔥',
dataExtractor: (workout, metrics) => ({
strengthLevel: metrics?.mets && metrics.mets > 6 ? 'high' : 'moderate'
})
},
highintensityintervaltraining: {
emoji: '🔥',
titleTemplate: 'HIIT训练完成',
bodyTemplate: ({ durationMinutes, calories }) => {
let body = `${durationMinutes}分钟高强度间歇训练`;
if (calories > 0) {
body += `,消耗${calories}千卡`;
}
return body + '';
},
encouragement: '心肺功能显著提升!⚡',
dataExtractor: (workout, metrics) => ({
hiitCompleted: true
})
}
};
function getWorkoutMessage(workoutTypeString?: string): WorkoutMessageConfig | null {
if (!workoutTypeString) return null;
const normalizedType = workoutTypeString.toLowerCase();
if (normalizedType.includes('strength')) {
return WORKOUT_MESSAGES.traditionalstrengthtraining;
}
return WORKOUT_MESSAGES[normalizedType] || null;
}
function generateEncouragementMessage(
workout: WorkoutData,
metrics: any
): WorkoutEncouragementMessage {
if (!workout) {
return {
title: '锻炼完成!',
body: '恭喜您完成锻炼!',
data: {}
};
}
const workoutType = getWorkoutTypeDisplayName(workout.workoutActivityTypeString) || '锻炼';
const durationMinutes = workout.duration ? Math.round(workout.duration / 60) : 0;
const calories = workout.totalEnergyBurned ? Math.round(workout.totalEnergyBurned) : 0;
const distanceKm = workout.totalDistance && workout.totalDistance > 0
? (workout.totalDistance / 1000).toFixed(2)
: undefined;
const messageConfig = getWorkoutMessage(workout.workoutActivityTypeString);
let title = '🎯 锻炼完成!';
let body = '';
const data: Record<string, any> = {};
if (messageConfig) {
title = `${messageConfig.emoji} ${messageConfig.titleTemplate}`;
body = messageConfig.bodyTemplate({
workoutType,
durationMinutes,
calories,
distanceKm,
averageHeartRate: metrics?.averageHeartRate,
mets: metrics?.mets
});
body += messageConfig.encouragement;
if (messageConfig.dataExtractor) {
Object.assign(data, messageConfig.dataExtractor(workout, metrics));
}
} else {
body = `${workoutType} ${durationMinutes}分钟完成!`;
if (calories > 0) {
body += `消耗${calories}千卡热量。`;
}
body += '坚持运动,收获健康!🌟';
}
if (metrics?.heartRateZones && Array.isArray(metrics.heartRateZones) && metrics.heartRateZones.length > 0) {
try {
const dominantZone = metrics.heartRateZones.reduce((prev: any, current: any) =>
current?.durationMinutes > prev?.durationMinutes ? current : prev
);
if (dominantZone?.durationMinutes > 5) {
data.heartRateZone = dominantZone.key;
data.heartRateZoneLabel = dominantZone.label;
}
} catch (error) {
console.warn('心率区间分析失败:', error);
}
}
if (metrics?.mets) {
data.intensity = metrics.mets < 3 ? 'low' : metrics.mets < 6 ? 'moderate' : 'high';
}
return { title, body, data };
}