基于提供的 Git diff,我将生成以下 conventional commit message: ## 变更分析: 1. **核心功能**: - 新增睡眠监控服务(`services/sleepMonitor.ts`) - 新增睡眠通知服务(`services/sleepNotificationService.ts`) - iOS 原生端增加睡眠观察者方法 2. **应用启动优化**: - 重构 `app/_layout.tsx` 中的初始化流程,按优先级分阶段加载服务 3. **药品功能改进**: - 优化语音识别交互(实时预览、可取消) - Widget 增加 URL scheme 支持 4. **路由配置**: - 新增药品管理路由常量 ## 提交信息类型: - **主类型**:`feat` (新增睡眠监控功能) - **作用域**:`health` (健康相关功能) --- 请确认方案后,我将生成最终的 commit message。 --- **最终 Commit Message:** feat(health): 添加睡眠监控和通知服务,优化应用启动流程 - 新增睡眠监控服务,支持实时监听 HealthKit 睡眠数据更新 - 实现睡眠质量分析算法,计算睡眠评分和各阶段占比 - 新增睡眠通知服务,分析完成后自动推送质量评估和建议 - iOS 原生端实现睡眠数据观察者,支持后台数据传递 - 重构应用启动初始化流程,按优先级分阶段加载服务(关键/次要/后台/空闲) - 优化药品录入页面语音识别交互,支持实时预览和取消操作 - 药品 Widget 增加 deeplink 支持,点击跳转到应用 - 新增药品管理路由常量配置
513 lines
15 KiB
TypeScript
513 lines
15 KiB
TypeScript
/**
|
||
* 睡眠监控服务
|
||
*
|
||
* 负责监听睡眠数据更新事件,分析睡眠质量并提供反馈
|
||
*/
|
||
|
||
import { logger } from '@/utils/logger';
|
||
import { NativeEventEmitter, NativeModules } from 'react-native';
|
||
import { analyzeSleepAndSendNotification } from './sleepNotificationService';
|
||
|
||
const { HealthKitManager } = NativeModules;
|
||
|
||
|
||
// 睡眠阶段类型
|
||
export type SleepStage = 'inBed' | 'asleep' | 'awake' | 'core' | 'deep' | 'rem' | 'unknown';
|
||
|
||
// 睡眠样本数据
|
||
export interface SleepSample {
|
||
id: string;
|
||
startDate: string;
|
||
endDate: string;
|
||
value: number;
|
||
categoryType: SleepStage;
|
||
duration: number;
|
||
source: {
|
||
name: string;
|
||
bundleIdentifier: string;
|
||
};
|
||
metadata: Record<string, any>;
|
||
}
|
||
|
||
// 睡眠分析结果
|
||
export interface SleepAnalysisData {
|
||
sessionStart: string;
|
||
sessionEnd: string;
|
||
totalSleepDuration: number; // 秒
|
||
totalSleepHours: number; // 小时
|
||
deepSleepDuration: number; // 秒
|
||
deepSleepHours: number; // 小时
|
||
deepSleepPercentage: number; // 百分比
|
||
remSleepDuration: number; // 秒
|
||
remSleepHours: number; // 小时
|
||
remSleepPercentage: number; // 百分比
|
||
coreSleepDuration: number; // 秒
|
||
coreSleepHours: number; // 小时
|
||
coreSleepPercentage: number; // 百分比
|
||
awakeDuration: number; // 秒
|
||
awakeMinutes: number; // 分钟
|
||
sleepEfficiency: number; // 百分比
|
||
sleepScore: number; // 0-100分
|
||
quality: 'excellent' | 'good' | 'fair' | 'poor' | 'very_poor';
|
||
}
|
||
|
||
// 睡眠事件数据
|
||
export interface SleepEventData {
|
||
timestamp: number;
|
||
type: 'sleep_data_updated';
|
||
}
|
||
|
||
/**
|
||
* 睡眠监控服务类
|
||
*/
|
||
class SleepMonitorService {
|
||
private eventEmitter: NativeEventEmitter | null = null;
|
||
private eventSubscription: any = null;
|
||
private isInitialized = false;
|
||
private lastProcessedTime = 0;
|
||
private debounceDelay = 3000; // 3秒防抖延迟
|
||
|
||
/**
|
||
* 初始化睡眠监控服务
|
||
*/
|
||
async initialize(): Promise<boolean> {
|
||
if (this.isInitialized) {
|
||
console.log('[SleepMonitor] Already initialized');
|
||
return true;
|
||
}
|
||
|
||
try {
|
||
// 创建事件发射器
|
||
this.eventEmitter = new NativeEventEmitter(HealthKitManager);
|
||
|
||
// 订阅睡眠更新事件
|
||
this.eventSubscription = this.eventEmitter.addListener(
|
||
'sleepUpdate',
|
||
this.handleSleepUpdate.bind(this)
|
||
);
|
||
|
||
// 启动睡眠观察者
|
||
await HealthKitManager.startSleepObserver();
|
||
|
||
this.isInitialized = true;
|
||
console.log('[SleepMonitor] Initialized successfully');
|
||
return true;
|
||
} catch (error) {
|
||
console.error('[SleepMonitor] Initialization failed:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 停止睡眠监控服务
|
||
*/
|
||
async stop(): Promise<void> {
|
||
if (!this.isInitialized) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 取消事件订阅
|
||
if (this.eventSubscription) {
|
||
this.eventSubscription.remove();
|
||
this.eventSubscription = null;
|
||
}
|
||
|
||
// 停止睡眠观察者
|
||
await HealthKitManager.stopSleepObserver();
|
||
|
||
this.isInitialized = false;
|
||
console.log('[SleepMonitor] Stopped successfully');
|
||
} catch (error) {
|
||
console.error('[SleepMonitor] Stop failed:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理睡眠更新事件
|
||
*/
|
||
private async handleSleepUpdate(event: SleepEventData): Promise<void> {
|
||
console.log('[SleepMonitor] Sleep data updated:', event);
|
||
|
||
// 防抖处理:避免短时间内重复处理
|
||
const now = Date.now();
|
||
if (now - this.lastProcessedTime < this.debounceDelay) {
|
||
console.log('[SleepMonitor] Debouncing, skipping this update');
|
||
return;
|
||
}
|
||
this.lastProcessedTime = now;
|
||
|
||
try {
|
||
// 分析最近的睡眠数据
|
||
const analysis = await this.analyzeSleepData();
|
||
|
||
if (analysis) {
|
||
logger.info('[SleepMonitor] Sleep analysis completed:', {
|
||
score: analysis.sleepScore,
|
||
quality: analysis.quality,
|
||
duration: `${analysis.totalSleepHours.toFixed(1)}h`,
|
||
});
|
||
|
||
// 发送睡眠分析通知
|
||
await this.notifySleepAnalysis(analysis);
|
||
}
|
||
} catch (error) {
|
||
console.error('[SleepMonitor] Failed to analyze sleep data:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 分析睡眠数据
|
||
*/
|
||
async analyzeSleepData(): Promise<SleepAnalysisData | null> {
|
||
try {
|
||
// 获取最近24小时的睡眠数据
|
||
const endDate = new Date();
|
||
const startDate = new Date(endDate.getTime() - 24 * 60 * 60 * 1000);
|
||
|
||
const result = await HealthKitManager.getSleepData({
|
||
startDate: startDate.toISOString(),
|
||
endDate: endDate.toISOString(),
|
||
});
|
||
|
||
if (!result.data || result.data.length === 0) {
|
||
console.log('[SleepMonitor] No sleep data found');
|
||
return null;
|
||
}
|
||
|
||
const sleepSamples: SleepSample[] = result.data;
|
||
|
||
// 找到最近一次完整的睡眠会话
|
||
const session = this.findLatestCompleteSleepSession(sleepSamples);
|
||
if (!session) {
|
||
console.log('[SleepMonitor] No complete sleep session found');
|
||
return null;
|
||
}
|
||
|
||
// 分析睡眠样本
|
||
const analysis = this.analyzeSleepSamples(session.samples, session.startDate, session.endDate);
|
||
|
||
return analysis;
|
||
} catch (error) {
|
||
console.error('[SleepMonitor] Failed to analyze sleep data:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 找到最近一次完整的睡眠会话
|
||
*/
|
||
private findLatestCompleteSleepSession(samples: SleepSample[]): {
|
||
samples: SleepSample[];
|
||
startDate: Date;
|
||
endDate: Date;
|
||
} | null {
|
||
if (samples.length === 0) return null;
|
||
|
||
// 按开始时间排序
|
||
const sortedSamples = [...samples].sort((a, b) =>
|
||
new Date(a.startDate).getTime() - new Date(b.startDate).getTime()
|
||
);
|
||
|
||
// 从最新的样本开始往前查找
|
||
const reversedSamples = [...sortedSamples].reverse();
|
||
const sessionSamples: SleepSample[] = [];
|
||
let sessionEnd: Date | null = null;
|
||
|
||
for (const sample of reversedSamples) {
|
||
// 如果是第一个样本,记录结束时间
|
||
if (!sessionEnd) {
|
||
sessionEnd = new Date(sample.endDate);
|
||
}
|
||
|
||
// 收集连续的睡眠样本(时间间隔不超过30分钟)
|
||
if (sessionSamples.length > 0) {
|
||
const lastSample = sessionSamples[sessionSamples.length - 1];
|
||
const gap = new Date(lastSample.startDate).getTime() - new Date(sample.endDate).getTime();
|
||
if (gap > 30 * 60 * 1000) { // 间隔超过30分钟
|
||
break;
|
||
}
|
||
}
|
||
|
||
sessionSamples.push(sample);
|
||
}
|
||
|
||
// 反转回正常顺序
|
||
sessionSamples.reverse();
|
||
|
||
if (sessionSamples.length === 0) return null;
|
||
|
||
const sessionStart = new Date(sessionSamples[0].startDate);
|
||
const sessionEndDate = sessionEnd || new Date(sessionSamples[sessionSamples.length - 1].endDate);
|
||
|
||
// 确保会话至少有2小时的数据
|
||
const duration = sessionEndDate.getTime() - sessionStart.getTime();
|
||
if (duration < 2 * 60 * 60 * 1000) { // 至少2小时
|
||
return null;
|
||
}
|
||
|
||
return {
|
||
samples: sessionSamples,
|
||
startDate: sessionStart,
|
||
endDate: sessionEndDate,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 分析睡眠样本数据
|
||
*/
|
||
private analyzeSleepSamples(
|
||
samples: SleepSample[],
|
||
sessionStart: Date,
|
||
sessionEnd: Date
|
||
): SleepAnalysisData {
|
||
// 计算各阶段睡眠时长
|
||
let totalSleepDuration = 0;
|
||
let deepSleepDuration = 0;
|
||
let remSleepDuration = 0;
|
||
let coreSleepDuration = 0;
|
||
let awakeDuration = 0;
|
||
|
||
for (const sample of samples) {
|
||
const duration = sample.duration;
|
||
|
||
switch (sample.categoryType) {
|
||
case 'deep':
|
||
deepSleepDuration += duration;
|
||
totalSleepDuration += duration;
|
||
break;
|
||
case 'rem':
|
||
remSleepDuration += duration;
|
||
totalSleepDuration += duration;
|
||
break;
|
||
case 'core':
|
||
coreSleepDuration += duration;
|
||
totalSleepDuration += duration;
|
||
break;
|
||
case 'asleep':
|
||
totalSleepDuration += duration;
|
||
break;
|
||
case 'awake':
|
||
awakeDuration += duration;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 计算睡眠效率
|
||
const totalInBedDuration = (sessionEnd.getTime() - sessionStart.getTime()) / 1000;
|
||
const sleepEfficiency = totalInBedDuration > 0
|
||
? (totalSleepDuration / totalInBedDuration) * 100
|
||
: 0;
|
||
|
||
// 计算各阶段睡眠占比
|
||
const deepSleepPercentage = totalSleepDuration > 0
|
||
? (deepSleepDuration / totalSleepDuration) * 100
|
||
: 0;
|
||
const remSleepPercentage = totalSleepDuration > 0
|
||
? (remSleepDuration / totalSleepDuration) * 100
|
||
: 0;
|
||
const coreSleepPercentage = totalSleepDuration > 0
|
||
? (coreSleepDuration / totalSleepDuration) * 100
|
||
: 0;
|
||
|
||
// 计算睡眠评分
|
||
const sleepScore = this.calculateSleepScore(
|
||
totalSleepDuration,
|
||
deepSleepDuration,
|
||
remSleepDuration,
|
||
sleepEfficiency,
|
||
awakeDuration
|
||
);
|
||
|
||
return {
|
||
sessionStart: sessionStart.toISOString(),
|
||
sessionEnd: sessionEnd.toISOString(),
|
||
totalSleepDuration,
|
||
totalSleepHours: totalSleepDuration / 3600,
|
||
deepSleepDuration,
|
||
deepSleepHours: deepSleepDuration / 3600,
|
||
deepSleepPercentage,
|
||
remSleepDuration,
|
||
remSleepHours: remSleepDuration / 3600,
|
||
remSleepPercentage,
|
||
coreSleepDuration,
|
||
coreSleepHours: coreSleepDuration / 3600,
|
||
coreSleepPercentage,
|
||
awakeDuration,
|
||
awakeMinutes: awakeDuration / 60,
|
||
sleepEfficiency,
|
||
sleepScore,
|
||
quality: this.getSleepQualityLabel(sleepScore),
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 计算睡眠评分(0-100分)
|
||
*/
|
||
private calculateSleepScore(
|
||
totalSleepDuration: number,
|
||
deepSleepDuration: number,
|
||
remSleepDuration: number,
|
||
sleepEfficiency: number,
|
||
awakeDuration: number
|
||
): number {
|
||
let score = 0;
|
||
|
||
// 1. 总睡眠时长评分(40分)
|
||
const sleepHours = totalSleepDuration / 3600;
|
||
if (sleepHours >= 7 && sleepHours <= 9) {
|
||
score += 40; // 理想睡眠时长
|
||
} else if (sleepHours >= 6 && sleepHours < 7) {
|
||
score += 35; // 稍短
|
||
} else if (sleepHours >= 9 && sleepHours < 10) {
|
||
score += 35; // 稍长
|
||
} else if (sleepHours >= 5 && sleepHours < 6) {
|
||
score += 25; // 较短
|
||
} else if (sleepHours >= 10 && sleepHours < 11) {
|
||
score += 30; // 较长
|
||
} else if (sleepHours >= 4 && sleepHours < 5) {
|
||
score += 15; // 很短
|
||
} else if (sleepHours >= 11) {
|
||
score += 20; // 过长
|
||
} else {
|
||
score += 10; // 极短
|
||
}
|
||
|
||
// 2. 深度睡眠评分(25分)
|
||
const deepSleepPercentage = totalSleepDuration > 0
|
||
? (deepSleepDuration / totalSleepDuration) * 100
|
||
: 0;
|
||
|
||
if (deepSleepPercentage >= 13 && deepSleepPercentage <= 23) {
|
||
score += 25; // 理想的深度睡眠占比(13-23%)
|
||
} else if (deepSleepPercentage >= 10 && deepSleepPercentage < 13) {
|
||
score += 20;
|
||
} else if (deepSleepPercentage >= 23 && deepSleepPercentage < 30) {
|
||
score += 20;
|
||
} else if (deepSleepPercentage >= 7 && deepSleepPercentage < 10) {
|
||
score += 15;
|
||
} else if (deepSleepPercentage >= 30) {
|
||
score += 15;
|
||
} else {
|
||
score += 10;
|
||
}
|
||
|
||
// 3. REM睡眠评分(20分)
|
||
const remSleepPercentage = totalSleepDuration > 0
|
||
? (remSleepDuration / totalSleepDuration) * 100
|
||
: 0;
|
||
|
||
if (remSleepPercentage >= 20 && remSleepPercentage <= 25) {
|
||
score += 20; // 理想的REM睡眠占比(20-25%)
|
||
} else if (remSleepPercentage >= 15 && remSleepPercentage < 20) {
|
||
score += 18;
|
||
} else if (remSleepPercentage >= 25 && remSleepPercentage < 30) {
|
||
score += 18;
|
||
} else if (remSleepPercentage >= 10 && remSleepPercentage < 15) {
|
||
score += 12;
|
||
} else if (remSleepPercentage >= 30) {
|
||
score += 15;
|
||
} else {
|
||
score += 8;
|
||
}
|
||
|
||
// 4. 睡眠效率评分(15分)
|
||
if (sleepEfficiency >= 85) {
|
||
score += 15; // 优秀的睡眠效率
|
||
} else if (sleepEfficiency >= 75) {
|
||
score += 12;
|
||
} else if (sleepEfficiency >= 65) {
|
||
score += 8;
|
||
} else {
|
||
score += 5;
|
||
}
|
||
|
||
return Math.min(Math.round(score), 100);
|
||
}
|
||
|
||
/**
|
||
* 根据评分获取睡眠质量标签
|
||
*/
|
||
private getSleepQualityLabel(score: number): 'excellent' | 'good' | 'fair' | 'poor' | 'very_poor' {
|
||
if (score >= 90) return 'excellent';
|
||
if (score >= 75) return 'good';
|
||
if (score >= 60) return 'fair';
|
||
if (score >= 40) return 'poor';
|
||
return 'very_poor';
|
||
}
|
||
|
||
/**
|
||
* 通知睡眠分析结果
|
||
*/
|
||
private async notifySleepAnalysis(analysis: SleepAnalysisData): Promise<void> {
|
||
try {
|
||
// 发送睡眠分析通知
|
||
await analyzeSleepAndSendNotification(analysis);
|
||
|
||
logger.info('[SleepMonitor] Sleep analysis notification sent:', {
|
||
score: analysis.sleepScore,
|
||
quality: analysis.quality,
|
||
duration: `${analysis.totalSleepHours.toFixed(1)}h`,
|
||
efficiency: `${analysis.sleepEfficiency.toFixed(0)}%`,
|
||
});
|
||
|
||
// 可以在这里更新 Redux store
|
||
// store.dispatch(updateSleepAnalysis(analysis));
|
||
} catch (error) {
|
||
logger.error('[SleepMonitor] Failed to send sleep notification:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取睡眠质量描述
|
||
*/
|
||
getQualityDescription(quality: string): string {
|
||
const descriptions = {
|
||
excellent: '优秀 - 您的睡眠质量非常好!',
|
||
good: '良好 - 您的睡眠质量不错',
|
||
fair: '一般 - 您的睡眠质量还可以改善',
|
||
poor: '较差 - 建议改善睡眠习惯',
|
||
very_poor: '很差 - 强烈建议关注睡眠健康',
|
||
};
|
||
return descriptions[quality as keyof typeof descriptions] || '未知';
|
||
}
|
||
|
||
/**
|
||
* 获取睡眠建议
|
||
*/
|
||
getSleepRecommendations(analysis: SleepAnalysisData): string[] {
|
||
const recommendations: string[] = [];
|
||
|
||
// 基于总睡眠时长的建议
|
||
if (analysis.totalSleepHours < 7) {
|
||
recommendations.push('建议增加睡眠时间,成年人每晚需要7-9小时睡眠');
|
||
} else if (analysis.totalSleepHours > 9) {
|
||
recommendations.push('睡眠时间偏长,可能影响睡眠质量');
|
||
}
|
||
|
||
// 基于深度睡眠的建议
|
||
if (analysis.deepSleepPercentage < 13) {
|
||
recommendations.push('深度睡眠不足,建议睡前避免使用电子设备');
|
||
}
|
||
|
||
// 基于REM睡眠的建议
|
||
if (analysis.remSleepPercentage < 20) {
|
||
recommendations.push('REM睡眠不足,建议保持规律的作息时间');
|
||
}
|
||
|
||
// 基于睡眠效率的建议
|
||
if (analysis.sleepEfficiency < 85) {
|
||
recommendations.push('睡眠效率较低,建议改善睡眠环境');
|
||
}
|
||
|
||
// 基于清醒时间的建议
|
||
if (analysis.awakeMinutes > 30) {
|
||
recommendations.push('夜间醒来时间较长,建议睡前放松身心');
|
||
}
|
||
|
||
return recommendations;
|
||
}
|
||
}
|
||
|
||
// 导出单例
|
||
export const sleepMonitorService = new SleepMonitorService(); |