Skip to content

Commit

Permalink
chore: polish
Browse files Browse the repository at this point in the history
  • Loading branch information
carozo committed Jan 6, 2025
1 parent 0b30018 commit 95ffbca
Show file tree
Hide file tree
Showing 5 changed files with 341 additions and 343 deletions.
190 changes: 190 additions & 0 deletions src/helpers/results.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { Platform } from 'react-native';
import {
type ReadRecordsResult,
type RecordResult,
} from 'react-native-health-connect';

import type { HealthValue } from 'react-native-health';
import { HealthLinkDataType, type ReadOptions } from '../types/dataTypes';
import {
androidHeightUnitMap,
androidWeightUnitMap,
BloodGlucoseUnit,
} from '../types/units';
import type { HealthLinkDataValue } from '../types/results';

const AppleHealthKit = require('react-native-health');

export const dataValueDeserializer = <T extends HealthLinkDataType>(
dataType: T,
options: ReadOptions,
dataValue: HealthValue | RecordResult<any>
): HealthLinkDataValue<T> | null => {
if (Platform.OS === 'ios') {
let iosDataValue = dataValue as HealthValue;
let result = {
value: iosDataValue.value,
id: iosDataValue.id,
time: iosDataValue.startDate,
metadata: {
...iosDataValue.metadata,
source: (iosDataValue as any).sourceId,
},
};

switch (dataType) {
case HealthLinkDataType.BloodPressure:
return {
...result,
value: {
systolic: (iosDataValue as any).bloodPressureSystolicValue,
diastolic: (iosDataValue as any).bloodPressureDiastolicValue,
},
} as HealthLinkDataValue<T>;
case HealthLinkDataType.OxygenSaturation:
return {
...result,
value: iosDataValue.value * 100,
} as HealthLinkDataValue<T>;
default:
return result as HealthLinkDataValue<T>;
}
} else if (Platform.OS === 'android') {
let androidDataValue = dataValue as RecordResult<any>;
let result = {
id: androidDataValue.metadata?.id,
metadata: {
...androidDataValue.metadata,
source: androidDataValue.metadata?.dataOrigin as string,
},
time: (androidDataValue as any).time,
};

switch (dataType) {
case HealthLinkDataType.BloodGlucose: {
const bloodGlucoseDataValue =
androidDataValue as RecordResult<'BloodGlucose'>;
return {
...result,
metadata: {
...result.metadata,
relationToMeal: bloodGlucoseDataValue.relationToMeal,
mealType: bloodGlucoseDataValue.mealType,
specimenSource: bloodGlucoseDataValue.specimenSource,
},
value:
options?.unit === BloodGlucoseUnit.MgPerdL
? bloodGlucoseDataValue.level?.inMilligramsPerDeciliter
: bloodGlucoseDataValue.level?.inMillimolesPerLiter,
} as unknown as HealthLinkDataValue<T>;
}
case HealthLinkDataType.BloodPressure: {
const bloodPressureDataValue =
androidDataValue as RecordResult<'BloodPressure'>;
return {
...result,
value: {
systolic: bloodPressureDataValue.systolic.inMillimetersOfMercury,
diastolic: bloodPressureDataValue.diastolic.inMillimetersOfMercury,
},
} as HealthLinkDataValue<T>;
}
case HealthLinkDataType.Height: {
const heightDataValue = androidDataValue as RecordResult<'Height'>;
return {
...result,
value: androidHeightUnitMap(heightDataValue, options.unit),
} as HealthLinkDataValue<T>;
}
case HealthLinkDataType.Weight: {
const weightDataValue = androidDataValue as RecordResult<'Weight'>;
return {
...result,
value: androidWeightUnitMap(weightDataValue, options.unit),
} as HealthLinkDataValue<T>;
}
case HealthLinkDataType.HeartRate: {
const heartRateDataValue =
androidDataValue as RecordResult<'HeartRate'>;
return {
...result,
value: heartRateDataValue.samples[0]?.beatsPerMinute,
time: heartRateDataValue.samples[0]?.time,
} as HealthLinkDataValue<T>;
}
case HealthLinkDataType.RestingHeartRate: {
const restingHeartRateDataValue =
androidDataValue as RecordResult<'RestingHeartRate'>;
return {
...result,
value: restingHeartRateDataValue.beatsPerMinute,
} as HealthLinkDataValue<T>;
}
case HealthLinkDataType.OxygenSaturation: {
const oxygenSaturationDataValue =
androidDataValue as RecordResult<'OxygenSaturation'>;
return {
...result,
value: oxygenSaturationDataValue.percentage,
} as HealthLinkDataValue<T>;
}
case HealthLinkDataType.Steps: {
const stepsDataValue = androidDataValue as RecordResult<'Steps'>;
return {
...result,
value: stepsDataValue.count,
} as HealthLinkDataValue<T>;
}
default:
return result as HealthLinkDataValue<T>;
}
}
return null;
};

export const readDataResultDeserializer = <T extends HealthLinkDataType>(
dataType: T,
options: ReadOptions,
data: ReadRecordsResult<T> | HealthValue[]
): HealthLinkDataValue<T>[] => {
let dataValueArray: RecordResult<T>[] | HealthValue[] =
Platform.OS === 'ios'
? (data as HealthValue[])
: (data as ReadRecordsResult<T>).records;
return dataValueArray.reduce((acc, d) => {
const deserializedValue = dataValueDeserializer(dataType, options, d);
if (deserializedValue !== null) {
acc.push(deserializedValue);
}
return acc;
}, [] as HealthLinkDataValue<T>[]);
};

export const readIosCallback = (
dataType: HealthLinkDataType,
options: ReadOptions
) => {
return new Promise((resolve, reject) => {
dataValueToIosReadFunction(dataType)(options, (err: any, results: any) => {
if (err) {
console.error(err);
reject(err);
}
resolve(results);
});
});
};

export const dataValueToIosReadFunction = (dataType: HealthLinkDataType) => {
const dataTypeMap: { [key in HealthLinkDataType]?: any } = {
BloodGlucose: AppleHealthKit.getBloodGlucoseSamples,
Height: AppleHealthKit.getHeightSamples,
Weight: AppleHealthKit.getWeightSamples,
HeartRate: AppleHealthKit.getHeartRateSamples,
RestingHeartRate: AppleHealthKit.getRestingHeartRateSamples,
OxygenSaturation: AppleHealthKit.getOxygenSaturationSamples,
BloodPressure: AppleHealthKit.getBloodPressureSamples,
Steps: AppleHealthKit.getDailyStepCountSamples,
};
return dataTypeMap[dataType] || (() => {});
};
142 changes: 142 additions & 0 deletions src/helpers/save.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { type HealthValueOptions } from 'react-native-health';
import { HealthLinkDataType } from '../types/dataTypes';
import { Platform } from 'react-native';
import type { HealthConnectRecord } from 'react-native-health-connect';
import {
BloodGlucoseUnit,
HeighUnit,
unitToIosUnitMap,
WeightUnit,
} from '../types/units';
import type { WriteDataType, WriteOptions } from '../types/save';

const AppleHealthKit = require('react-native-health');

export const serializeWriteOptions = <T extends WriteDataType>(
dataType: T,
options: WriteOptions<T>
): HealthValueOptions | HealthConnectRecord | null => {
if (Platform.OS === 'ios') {
if (!options.unit) return null;
const value = options.value ?? 0;
const iosOptions: HealthValueOptions = {
unit: unitToIosUnitMap[options.unit],
value:
options.unit === WeightUnit.Kg
? value * 1000
: options.unit === HeighUnit.Cm
? value / 100
: value,
};
return iosOptions;
}

if (Platform.OS === 'android') {
const androidOptions = {
startTime: options.startDate ?? new Date().toISOString(),
endTime: options.endDate ?? new Date().toISOString(),
};

switch (dataType) {
case HealthLinkDataType.BloodGlucose: {
const {
relationToMeal = 0,
mealType = 0,
specimenSource = 0,
} = options.metadata || {};
const unit =
options.unit === BloodGlucoseUnit.MgPerdL
? 'milligramsPerDeciliter'
: 'millimolesPerLiter';
return {
...androidOptions,
recordType: 'BloodGlucose',
relationToMeal,
mealType,
specimenSource,
time: options.time ?? androidOptions.startTime,
level: { unit, value: options.value as number },
};
}
case HealthLinkDataType.Steps:
return {
...androidOptions,
recordType: 'Steps',
count: options.value ?? 0,
};
case HealthLinkDataType.Weight: {
const weightUnit =
options.unit === WeightUnit.Kg
? 'kilograms'
: options.unit === WeightUnit.Gram
? 'grams'
: 'pounds';
return {
...androidOptions,
recordType: 'Weight',
time: options.time ?? androidOptions.startTime,
weight: { unit: weightUnit, value: options.value as number },
};
}
case HealthLinkDataType.Height: {
const heightUnit =
options.unit === HeighUnit.Cm || options.unit === HeighUnit.Meter
? 'meters'
: options.unit === HeighUnit.Foot
? 'feet'
: 'inches';
const heightValue =
options.unit === HeighUnit.Cm
? (options.value as number) / 100
: (options.value as number);
return {
...androidOptions,
recordType: 'Height',
time: options.time ?? androidOptions.startTime,
height: { unit: heightUnit, value: heightValue },
};
}
case HealthLinkDataType.HeartRate:
return {
...androidOptions,
recordType: 'HeartRate',
samples: [
{
time: options.time ?? androidOptions.startTime,
beatsPerMinute: options.value as number,
},
],
};
default:
return null;
}
}

return null;
};

export const writeIosCallback = <T extends WriteDataType>(
dataType: WriteDataType,
options: WriteOptions<T>
) => {
return new Promise((resolve, reject) => {
dataValueToIosWriteFunction(dataType)(options, (err: any, results: any) => {
if (err) {
console.error(err);
reject(err);
}
resolve(results);
});
});
};

export const dataValueToIosWriteFunction = (dataType: WriteDataType) => {
const dataTypeMap: { [key in WriteDataType]?: any } = {
BloodGlucose: AppleHealthKit.saveBloodGlucoseSample,
Height: AppleHealthKit.saveHeight,
Weight: AppleHealthKit.saveWeight,
HeartRate: AppleHealthKit.saveHeartRateSample,
Steps: AppleHealthKit.saveSteps,
};
return dataTypeMap[dataType] || (() => {});
};
27 changes: 8 additions & 19 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,10 @@ import {
type ReadOptions,
} from './types/dataTypes';
import type { HealthValue } from 'react-native-health';
import {
readDataResultDeserializer,
readIosCallback,
type HealthLinkDataValue,
} from './types/results';
import {
serializeWriteOptions,
writeIosCallback,
type WriteDataType,
type WriteOptions,
} from './types/save';
import { type HealthLinkDataValue } from './types/results';
import { type WriteDataType, type WriteOptions } from './types/save';
import { serializeWriteOptions, writeIosCallback } from './helpers/save';
import { readDataResultDeserializer, readIosCallback } from './helpers/results';

const AppleHealthKit = require('react-native-health');

Expand Down Expand Up @@ -63,17 +56,13 @@ export const write = async <T extends WriteDataType>(
dataType: T,
data: WriteOptions<T>
): Promise<void> => {
const serializedData = serializeWriteOptions(dataType, data);
if (serializedData === null) {
return;
}
if (Platform.OS === 'ios') {
const serializedData = serializeWriteOptions(dataType, data);
if (serializedData === null) {
return;
}
await writeIosCallback(dataType, serializedData as WriteOptions<T>);
} else if (Platform.OS === 'android') {
const serializedData = serializeWriteOptions(dataType, data);
if (serializedData === null) {
return;
}
await insertRecords([serializedData as HealthConnectRecord]).catch((e) => {
console.error(e);
});
Expand Down
Loading

0 comments on commit 95ffbca

Please sign in to comment.