feat: 完善饮水 widget
This commit is contained in:
@@ -101,17 +101,17 @@ async function fetchSleepSamples(date: Date): Promise<SleepSample[]> {
|
||||
|
||||
// 添加详细日志,了解实际获取到的数据类型
|
||||
console.log('获取到睡眠样本:', results.length);
|
||||
console.log('睡眠样本详情:', results.map(r => ({
|
||||
value: r.value,
|
||||
start: r.startDate?.substring(11, 16),
|
||||
console.log('睡眠样本详情:', results.map(r => ({
|
||||
value: r.value,
|
||||
start: r.startDate?.substring(11, 16),
|
||||
end: r.endDate?.substring(11, 16),
|
||||
duration: `${Math.round((new Date(r.endDate).getTime() - new Date(r.startDate).getTime()) / 60000)}min`
|
||||
})));
|
||||
|
||||
|
||||
// 检查可用的睡眠阶段类型
|
||||
const uniqueValues = [...new Set(results.map(r => r.value))];
|
||||
console.log('可用的睡眠阶段类型:', uniqueValues);
|
||||
|
||||
|
||||
resolve(results as unknown as SleepSample[]);
|
||||
});
|
||||
});
|
||||
@@ -165,7 +165,6 @@ function calculateSleepStageStats(samples: SleepSample[]): SleepStageStats[] {
|
||||
|
||||
// 计算实际睡眠时间(包括所有睡眠阶段,排除在床时间)
|
||||
const actualSleepTime = Array.from(stageMap.entries())
|
||||
.filter(([stage]) => stage !== SleepStage.InBed)
|
||||
.reduce((total, [, duration]) => total + duration, 0);
|
||||
|
||||
// 生成统计数据,包含所有睡眠阶段(包括清醒时间)
|
||||
@@ -322,34 +321,34 @@ export async function fetchSleepDetailForDate(date: Date): Promise<SleepDetailDa
|
||||
|
||||
// 找到入睡时间和起床时间
|
||||
// 过滤出实际睡眠阶段(排除在床时间和清醒时间)
|
||||
const actualSleepSamples = sleepSamples.filter(sample =>
|
||||
const actualSleepSamples = sleepSamples.filter(sample =>
|
||||
sample.value !== SleepStage.InBed && sample.value !== SleepStage.Awake
|
||||
);
|
||||
|
||||
|
||||
// 入睡时间:第一个实际睡眠阶段的开始时间
|
||||
// 起床时间:最后一个实际睡眠阶段的结束时间
|
||||
let bedtime: string;
|
||||
let wakeupTime: string;
|
||||
|
||||
|
||||
if (actualSleepSamples.length > 0) {
|
||||
// 按开始时间排序
|
||||
const sortedSleepSamples = actualSleepSamples.sort((a, b) =>
|
||||
const sortedSleepSamples = actualSleepSamples.sort((a, b) =>
|
||||
new Date(a.startDate).getTime() - new Date(b.startDate).getTime()
|
||||
);
|
||||
|
||||
|
||||
bedtime = sortedSleepSamples[0].startDate;
|
||||
wakeupTime = sortedSleepSamples[sortedSleepSamples.length - 1].endDate;
|
||||
|
||||
|
||||
console.log('计算入睡和起床时间:');
|
||||
console.log('- 入睡时间:', dayjs(bedtime).format('YYYY-MM-DD HH:mm:ss'));
|
||||
console.log('- 起床时间:', dayjs(wakeupTime).format('YYYY-MM-DD HH:mm:ss'));
|
||||
} else {
|
||||
// 如果没有实际睡眠数据,回退到使用所有样本数据
|
||||
console.warn('没有找到实际睡眠阶段数据,使用所有样本数据');
|
||||
const sortedAllSamples = sleepSamples.sort((a, b) =>
|
||||
const sortedAllSamples = sleepSamples.sort((a, b) =>
|
||||
new Date(a.startDate).getTime() - new Date(b.startDate).getTime()
|
||||
);
|
||||
|
||||
|
||||
bedtime = sortedAllSamples[0].startDate;
|
||||
wakeupTime = sortedAllSamples[sortedAllSamples.length - 1].endDate;
|
||||
}
|
||||
@@ -357,17 +356,17 @@ export async function fetchSleepDetailForDate(date: Date): Promise<SleepDetailDa
|
||||
// 计算在床时间 - 使用 INBED 样本数据
|
||||
let timeInBed: number;
|
||||
const inBedSamples = sleepSamples.filter(sample => sample.value === SleepStage.InBed);
|
||||
|
||||
|
||||
if (inBedSamples.length > 0) {
|
||||
// 使用 INBED 样本计算在床时间
|
||||
const sortedInBedSamples = inBedSamples.sort((a, b) =>
|
||||
const sortedInBedSamples = inBedSamples.sort((a, b) =>
|
||||
new Date(a.startDate).getTime() - new Date(b.startDate).getTime()
|
||||
);
|
||||
|
||||
|
||||
const inBedStart = sortedInBedSamples[0].startDate;
|
||||
const inBedEnd = sortedInBedSamples[sortedInBedSamples.length - 1].endDate;
|
||||
timeInBed = dayjs(inBedEnd).diff(dayjs(inBedStart), 'minute');
|
||||
|
||||
|
||||
console.log('在床时间计算:');
|
||||
console.log('- 上床时间:', dayjs(inBedStart).format('YYYY-MM-DD HH:mm:ss'));
|
||||
console.log('- 离床时间:', dayjs(inBedEnd).format('YYYY-MM-DD HH:mm:ss'));
|
||||
@@ -381,9 +380,8 @@ export async function fetchSleepDetailForDate(date: Date): Promise<SleepDetailDa
|
||||
// 计算睡眠阶段统计
|
||||
const sleepStages = calculateSleepStageStats(sleepSamples);
|
||||
|
||||
// 计算总睡眠时间(排除清醒时间)
|
||||
// 计算总睡眠时间
|
||||
const totalSleepTime = sleepStages
|
||||
.filter(stage => stage.stage !== SleepStage.Awake)
|
||||
.reduce((total, stage) => total + stage.duration, 0);
|
||||
|
||||
// 计算睡眠效率
|
||||
@@ -450,14 +448,14 @@ export function formatTime(dateString: string): string {
|
||||
// 将睡眠样本数据转换为15分钟间隔的睡眠阶段数据
|
||||
export function convertSleepSamplesToIntervals(sleepSamples: SleepSample[], bedtime: string, wakeupTime: string): { time: string; stage: SleepStage }[] {
|
||||
const data: { time: string; stage: SleepStage }[] = [];
|
||||
|
||||
|
||||
if (sleepSamples.length === 0) {
|
||||
console.log('没有睡眠样本数据可用于图表显示');
|
||||
return [];
|
||||
}
|
||||
|
||||
// 过滤掉InBed阶段,只保留实际睡眠阶段
|
||||
const sleepOnlySamples = sleepSamples.filter(sample =>
|
||||
const sleepOnlySamples = sleepSamples.filter(sample =>
|
||||
sample.value !== SleepStage.InBed
|
||||
);
|
||||
|
||||
@@ -476,14 +474,14 @@ export function convertSleepSamplesToIntervals(sleepSamples: SleepSample[], bedt
|
||||
// 创建一个映射,用于快速查找每个时间点的睡眠阶段
|
||||
while (currentTime.isBefore(endTime)) {
|
||||
const currentTimestamp = currentTime.toDate().getTime();
|
||||
|
||||
|
||||
// 找到当前时间点对应的睡眠阶段
|
||||
let currentStage = SleepStage.Awake; // 默认为清醒
|
||||
|
||||
|
||||
for (const sample of sleepOnlySamples) {
|
||||
const sampleStart = new Date(sample.startDate).getTime();
|
||||
const sampleEnd = new Date(sample.endDate).getTime();
|
||||
|
||||
|
||||
// 如果当前时间在这个样本的时间范围内
|
||||
if (currentTimestamp >= sampleStart && currentTimestamp < sampleEnd) {
|
||||
currentStage = sample.value;
|
||||
@@ -493,7 +491,7 @@ export function convertSleepSamplesToIntervals(sleepSamples: SleepSample[], bedt
|
||||
|
||||
const timeStr = currentTime.format('HH:mm');
|
||||
data.push({ time: timeStr, stage: currentStage });
|
||||
|
||||
|
||||
// 移动到下一个15分钟间隔
|
||||
currentTime = currentTime.add(15, 'minute');
|
||||
}
|
||||
|
||||
101
services/waterRecordBridge.ts
Normal file
101
services/waterRecordBridge.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { NativeModules, NativeEventEmitter, EmitterSubscription } from 'react-native';
|
||||
import { createWaterRecordAction } from '@/store/waterSlice';
|
||||
import { store } from '@/store';
|
||||
import { WaterRecordSource } from './waterRecords';
|
||||
|
||||
// Native Module 接口定义
|
||||
interface WaterRecordManagerInterface {
|
||||
addWaterRecord(amount: number): Promise<{
|
||||
success: boolean;
|
||||
amount: number;
|
||||
newTotal: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
// 获取原生模块
|
||||
const WaterRecordManager: WaterRecordManagerInterface = NativeModules.WaterRecordManager;
|
||||
|
||||
// 创建事件发射器
|
||||
const waterRecordEventEmitter = NativeModules.WaterRecordManager
|
||||
? new NativeEventEmitter(NativeModules.WaterRecordManager)
|
||||
: null;
|
||||
|
||||
// 事件监听器引用
|
||||
let eventSubscription: EmitterSubscription | null = null;
|
||||
|
||||
// 事件数据接口
|
||||
interface WaterRecordEventData {
|
||||
amount: number;
|
||||
recordedAt: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
// 初始化事件监听器
|
||||
export function initializeWaterRecordBridge(): void {
|
||||
if (!waterRecordEventEmitter) {
|
||||
console.warn('WaterRecordManager 原生模块不可用,跳过事件监听器初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventSubscription) {
|
||||
// 如果已经初始化,先清理
|
||||
eventSubscription.remove();
|
||||
}
|
||||
|
||||
// 监听来自原生模块的事件
|
||||
eventSubscription = waterRecordEventEmitter.addListener(
|
||||
'WaterRecordAdded',
|
||||
(eventData: WaterRecordEventData) => {
|
||||
console.log('收到来自 Swift 的喝水记录事件:', eventData);
|
||||
|
||||
// 通过 Redux 创建喝水记录
|
||||
store.dispatch(createWaterRecordAction({
|
||||
amount: eventData.amount,
|
||||
recordedAt: eventData.recordedAt,
|
||||
source: WaterRecordSource.Auto, // 标记为自动添加
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
console.log('WaterRecordBridge 初始化完成');
|
||||
}
|
||||
|
||||
// 清理事件监听器
|
||||
export function cleanupWaterRecordBridge(): void {
|
||||
if (eventSubscription) {
|
||||
eventSubscription.remove();
|
||||
eventSubscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 供原生代码调用的添加喝水记录方法
|
||||
export async function addWaterRecordFromNative(amount: number): Promise<{
|
||||
success: boolean;
|
||||
amount: number;
|
||||
newTotal: number;
|
||||
}> {
|
||||
try {
|
||||
if (!WaterRecordManager) {
|
||||
throw new Error('WaterRecordManager 原生模块不可用');
|
||||
}
|
||||
|
||||
const result = await WaterRecordManager.addWaterRecord(amount);
|
||||
console.log('通过 Bridge 添加喝水记录成功:', result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('通过 Bridge 添加喝水记录失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查原生模块是否可用
|
||||
export function isWaterRecordBridgeAvailable(): boolean {
|
||||
return WaterRecordManager && typeof WaterRecordManager.addWaterRecord === 'function';
|
||||
}
|
||||
|
||||
export default {
|
||||
initializeWaterRecordBridge,
|
||||
cleanupWaterRecordBridge,
|
||||
addWaterRecordFromNative,
|
||||
isWaterRecordBridgeAvailable,
|
||||
};
|
||||
Reference in New Issue
Block a user