184 lines
4.7 KiB
TypeScript
184 lines
4.7 KiB
TypeScript
/**
|
||
* 睡眠通知服务
|
||
*
|
||
* 负责在睡眠分析完成后发送通知,提供睡眠质量评估和建议
|
||
*/
|
||
|
||
import i18n from '@/i18n';
|
||
import { logger } from '@/utils/logger';
|
||
import dayjs from 'dayjs';
|
||
import * as Notifications from 'expo-notifications';
|
||
import { SleepAnalysisData } from './sleepMonitor';
|
||
|
||
const t = (key: string, options?: Record<string, unknown>) => i18n.t(`sleepNotification.${key}`, options);
|
||
|
||
/**
|
||
* 分析睡眠数据并发送通知
|
||
*/
|
||
export async function analyzeSleepAndSendNotification(
|
||
analysis: SleepAnalysisData
|
||
): Promise<void> {
|
||
try {
|
||
logger.info('开始分析睡眠并发送通知:', {
|
||
score: analysis.sleepScore,
|
||
quality: analysis.quality,
|
||
duration: analysis.totalSleepHours,
|
||
});
|
||
|
||
// 构建通知内容
|
||
const notification = buildSleepNotification(analysis);
|
||
|
||
// 发送通知
|
||
await Notifications.scheduleNotificationAsync({
|
||
content: notification,
|
||
trigger: null, // 立即发送
|
||
});
|
||
|
||
logger.info('睡眠分析通知已发送');
|
||
} catch (error) {
|
||
logger.error('发送睡眠分析通知失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 构建睡眠通知内容
|
||
*/
|
||
function buildSleepNotification(analysis: SleepAnalysisData): Notifications.NotificationContentInput {
|
||
const { sleepScore, quality, totalSleepHours, sleepEfficiency } = analysis;
|
||
|
||
// 根据质量等级选择emoji和标题
|
||
const qualityConfig = getQualityConfig(quality);
|
||
|
||
// 构建通知标题
|
||
const title = `${qualityConfig.emoji} ${qualityConfig.title}`;
|
||
|
||
// 构建通知正文
|
||
const sleepDuration = formatSleepDuration(totalSleepHours);
|
||
const body = t('body', {
|
||
duration: sleepDuration,
|
||
efficiency: sleepEfficiency.toFixed(0),
|
||
score: sleepScore
|
||
});
|
||
|
||
// 获取建议
|
||
const suggestion = getSleepSuggestion(analysis);
|
||
|
||
// 计算睡眠日期
|
||
// 睡眠详情页面使用的日期逻辑是:传入的日期会查询从前一天18:00到当天12:00的数据
|
||
// 所以我们应该传递醒来的日期(sessionEnd),这样用户点击通知后能看到正确的睡眠数据
|
||
const sleepDate = analysis.sessionEnd
|
||
? dayjs(analysis.sessionEnd).format('YYYY-MM-DD')
|
||
: dayjs().format('YYYY-MM-DD');
|
||
|
||
return {
|
||
title,
|
||
body: `${body}\n${suggestion}`,
|
||
data: {
|
||
type: 'sleep_analysis',
|
||
score: sleepScore,
|
||
quality,
|
||
date: sleepDate, // 添加日期参数,用于点击通知后跳转
|
||
analysis: JSON.stringify(analysis),
|
||
url: '/sleep-detail', // 点击通知跳转到睡眠详情页
|
||
},
|
||
sound: 'default',
|
||
badge: 1,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取质量配置
|
||
*/
|
||
function getQualityConfig(quality: string): {
|
||
emoji: string;
|
||
title: string;
|
||
} {
|
||
const configs: Record<string, { emoji: string; title: string }> = {
|
||
excellent: {
|
||
emoji: '🥳',
|
||
title: t('quality.excellent'),
|
||
},
|
||
good: {
|
||
emoji: '☀️',
|
||
title: t('quality.good'),
|
||
},
|
||
fair: {
|
||
emoji: '🌤️',
|
||
title: t('quality.fair'),
|
||
},
|
||
poor: {
|
||
emoji: '🌛',
|
||
title: t('quality.poor'),
|
||
},
|
||
very_poor: {
|
||
emoji: '🫂',
|
||
title: t('quality.veryPoor'),
|
||
},
|
||
};
|
||
|
||
return configs[quality] || {
|
||
emoji: '🛏️',
|
||
title: t('quality.default'),
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 格式化睡眠时长
|
||
*/
|
||
function formatSleepDuration(hours: number): string {
|
||
const h = Math.floor(hours);
|
||
const m = Math.round((hours - h) * 60);
|
||
|
||
if (m === 0) {
|
||
return t('duration.hoursOnly', { hours: h });
|
||
}
|
||
return t('duration.hoursAndMinutes', { hours: h, minutes: m });
|
||
}
|
||
|
||
/**
|
||
* 获取睡眠建议
|
||
*/
|
||
function getSleepSuggestion(analysis: SleepAnalysisData): string {
|
||
const { quality, totalSleepHours, deepSleepPercentage, remSleepPercentage, sleepEfficiency } = analysis;
|
||
|
||
// 优秀或良好的睡眠 - 给予鼓励
|
||
if (quality === 'excellent' || quality === 'good') {
|
||
const tips = [
|
||
t('tips.excellent.keepItUp'),
|
||
t('tips.excellent.greatJob'),
|
||
t('tips.excellent.energized'),
|
||
t('tips.excellent.proud'),
|
||
];
|
||
return `✨ ${tips[Math.floor(Math.random() * tips.length)]}`;
|
||
}
|
||
|
||
// 根据具体问题给出温暖的建议
|
||
const suggestions: string[] = [];
|
||
|
||
if (totalSleepHours < 7) {
|
||
suggestions.push(t('tips.suggestions.shortSleep'));
|
||
} else if (totalSleepHours > 9) {
|
||
suggestions.push(t('tips.suggestions.longSleep'));
|
||
}
|
||
|
||
if (deepSleepPercentage < 13) {
|
||
suggestions.push(t('tips.suggestions.lowDeepSleep'));
|
||
}
|
||
|
||
if (remSleepPercentage < 20) {
|
||
suggestions.push(t('tips.suggestions.lowRemSleep'));
|
||
}
|
||
|
||
if (sleepEfficiency < 85) {
|
||
suggestions.push(t('tips.suggestions.lowEfficiency'));
|
||
}
|
||
|
||
// 如果有具体建议,返回第一条;否则返回通用建议
|
||
if (suggestions.length > 0) {
|
||
return `💡 ${suggestions[0]}`;
|
||
}
|
||
|
||
return `💡 ${t('tips.general')}`;
|
||
}
|