150 lines
4.3 KiB
TypeScript
150 lines
4.3 KiB
TypeScript
/**
|
|
* HealthKit Native Module Interface
|
|
* React Native TypeScript bindings for iOS HealthKit access
|
|
*/
|
|
|
|
import { requireNativeModule } from 'expo-modules-core';
|
|
|
|
export interface HealthKitPermissions {
|
|
[key: string]: 'notDetermined' | 'denied' | 'authorized' | 'unknown';
|
|
}
|
|
|
|
export interface HealthKitAuthorizationResult {
|
|
success: boolean;
|
|
permissions: HealthKitPermissions;
|
|
}
|
|
|
|
export interface SleepDataSource {
|
|
name: string;
|
|
bundleIdentifier: string;
|
|
}
|
|
|
|
export interface SleepDataSample {
|
|
id: string;
|
|
startDate: string; // ISO8601 format
|
|
endDate: string; // ISO8601 format
|
|
value: number;
|
|
categoryType: 'inBed' | 'asleep' | 'awake' | 'core' | 'deep' | 'rem' | 'unknown';
|
|
duration: number; // Duration in seconds
|
|
source: SleepDataSource;
|
|
metadata: Record<string, any>;
|
|
}
|
|
|
|
export interface SleepDataOptions {
|
|
startDate?: string; // ISO8601 format, defaults to 7 days ago
|
|
endDate?: string; // ISO8601 format, defaults to now
|
|
limit?: number; // Maximum number of samples, defaults to 100
|
|
}
|
|
|
|
export interface SleepDataResult {
|
|
data: SleepDataSample[];
|
|
count: number;
|
|
startDate: string;
|
|
endDate: string;
|
|
}
|
|
|
|
export interface HealthKitManagerInterface {
|
|
/**
|
|
* Request authorization to access HealthKit data
|
|
* This will prompt the user for permission to read/write health data
|
|
*/
|
|
requestAuthorization(): Promise<HealthKitAuthorizationResult>;
|
|
|
|
/**
|
|
* Get current authorization status for HealthKit data types
|
|
* This checks the current permission status without prompting the user
|
|
*/
|
|
getAuthorizationStatus(): Promise<HealthKitAuthorizationResult>;
|
|
|
|
/**
|
|
* Get sleep analysis data from HealthKit
|
|
* @param options Query options including date range and limit
|
|
*/
|
|
getSleepData(options?: SleepDataOptions): Promise<SleepDataResult>;
|
|
}
|
|
|
|
// Native module interface
|
|
const HealthKitManager: HealthKitManagerInterface = requireNativeModule('HealthKitManager');
|
|
|
|
export default HealthKitManager;
|
|
|
|
// Utility functions for working with sleep data
|
|
export class HealthKitUtils {
|
|
/**
|
|
* Convert seconds to hours and minutes
|
|
*/
|
|
static formatDuration(seconds: number): string {
|
|
const hours = Math.floor(seconds / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
if (hours > 0) {
|
|
return `${hours}h ${minutes}m`;
|
|
}
|
|
return `${minutes}m`;
|
|
}
|
|
|
|
/**
|
|
* Get total sleep duration from sleep samples for a specific date
|
|
*/
|
|
static getTotalSleepDuration(samples: SleepDataSample[], date: Date): number {
|
|
const targetDate = date.toISOString().split('T')[0];
|
|
|
|
return samples
|
|
.filter(sample => {
|
|
const sampleDate = new Date(sample.startDate).toISOString().split('T')[0];
|
|
return sampleDate === targetDate &&
|
|
['asleep', 'core', 'deep', 'rem'].includes(sample.categoryType);
|
|
})
|
|
.reduce((total, sample) => total + sample.duration, 0);
|
|
}
|
|
|
|
/**
|
|
* Group sleep samples by date
|
|
*/
|
|
static groupSamplesByDate(samples: SleepDataSample[]): Record<string, SleepDataSample[]> {
|
|
return samples.reduce((grouped, sample) => {
|
|
const date = new Date(sample.startDate).toISOString().split('T')[0];
|
|
if (!grouped[date]) {
|
|
grouped[date] = [];
|
|
}
|
|
grouped[date].push(sample);
|
|
return grouped;
|
|
}, {} as Record<string, SleepDataSample[]>);
|
|
}
|
|
|
|
/**
|
|
* Get sleep quality metrics from samples
|
|
*/
|
|
static getSleepQualityMetrics(samples: SleepDataSample[]) {
|
|
const sleepSamples = samples.filter(s =>
|
|
['asleep', 'core', 'deep', 'rem'].includes(s.categoryType)
|
|
);
|
|
|
|
if (sleepSamples.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const totalDuration = sleepSamples.reduce((sum, s) => sum + s.duration, 0);
|
|
const deepSleepDuration = sleepSamples
|
|
.filter(s => s.categoryType === 'deep')
|
|
.reduce((sum, s) => sum + s.duration, 0);
|
|
const remSleepDuration = sleepSamples
|
|
.filter(s => s.categoryType === 'rem')
|
|
.reduce((sum, s) => sum + s.duration, 0);
|
|
|
|
return {
|
|
totalDuration,
|
|
deepSleepDuration,
|
|
remSleepDuration,
|
|
deepSleepPercentage: totalDuration > 0 ? (deepSleepDuration / totalDuration) * 100 : 0,
|
|
remSleepPercentage: totalDuration > 0 ? (remSleepDuration / totalDuration) * 100 : 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if HealthKit is available (iOS only)
|
|
*/
|
|
static isAvailable(): boolean {
|
|
return HealthKitManager != null;
|
|
}
|
|
} |