Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the inLocalTimeZone parameter to the prepareResponse function. #338

Merged
merged 11 commits into from
Nov 14, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,22 @@ public void getHeartRateSamples(double startDate,
}
}

@ReactMethod
public void getAggregatedHeartRateSamples(double startDate,
double endDate,
int bucketInterval,
String bucketUnit,
Promise promise) {

try {
HealthHistory healthHistory = mGoogleFitManager.getHealthHistory();
healthHistory.setDataType(DataType.TYPE_HEART_RATE_BPM);
promise.resolve(healthHistory.getAggregatedHeartRateHistory((long)startDate, (long)endDate, bucketInterval, bucketUnit));
} catch (IllegalViewOperationException e) {
promise.reject(e);
}
}

@ReactMethod
public void getRestingHeartRateSamples(double startDate,
double endDate,
Expand Down
45 changes: 45 additions & 0 deletions android/src/main/java/com/reactnative/googlefit/HealthHistory.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,47 @@ else if (dataReadResult.getDataSets().size() > 0) {
return map;
}

/**
* GLE added to allow us to aggregate heart rate data.
* It does the same as health history, but adds the aggregation.
* Note there are also some changes to the processDataSet method to allow for the aggregation.
*/
public ReadableArray getAggregatedHeartRateHistory(long startTime, long endTime, int bucketInterval, String bucketUnit) {
DataReadRequest.Builder readRequestBuilder = new DataReadRequest.Builder()
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS);

if (this.dataType == DataType.TYPE_HEART_RATE_BPM) {
readRequestBuilder
.aggregate(this.dataType, DataType.AGGREGATE_HEART_RATE_SUMMARY)
.bucketByTime(bucketInterval, HelperUtil.processBucketUnit(bucketUnit));
} else {
readRequestBuilder.read(this.dataType);
}

DataReadRequest readRequest = readRequestBuilder.build();

DataReadResult dataReadResult = Fitness.HistoryApi.readData(googleFitManager.getGoogleApiClient(), readRequest).await(1, TimeUnit.MINUTES);

WritableArray map = Arguments.createArray();

//Used for aggregated data
if (dataReadResult.getBuckets().size() > 0) {
for (Bucket bucket : dataReadResult.getBuckets()) {
List<DataSet> dataSets = bucket.getDataSets();
for (DataSet dataSet : dataSets) {
processDataSet(dataSet, map);
}
}
}
//Used for non-aggregated data
else if (dataReadResult.getDataSets().size() > 0) {
for (DataSet dataSet : dataReadResult.getDataSets()) {
processDataSet(dataSet, map);
}
}
return map;
}

public ReadableArray getRestingHeartRateHistory(long startTime, long endTime, int bucketInterval, String bucketUnit) {
DataReadRequest.Builder readRequestBuilder = new DataReadRequest.Builder()
.aggregate(new DataSource.Builder()
Expand Down Expand Up @@ -283,6 +324,10 @@ private void processDataSet(DataSet dataSet, WritableArray map) {
if (this.dataType == HealthDataTypes.TYPE_BLOOD_PRESSURE) {
stepMap.putDouble("diastolic", dp.getValue(HealthFields.FIELD_BLOOD_PRESSURE_DIASTOLIC).asFloat());
stepMap.putDouble("systolic", dp.getValue(HealthFields.FIELD_BLOOD_PRESSURE_SYSTOLIC).asFloat());
} else if (this.dataType == DataType.TYPE_HEART_RATE_BPM && field.toString().startsWith("average")) {
stepMap.putDouble("average", dp.getValue(Field.FIELD_AVERAGE).asFloat());
stepMap.putDouble("min", dp.getValue(Field.FIELD_MIN).asFloat());
stepMap.putDouble("max", dp.getValue(Field.FIELD_MAX).asFloat());
} else {
stepMap.putDouble("value", dp.getValue(field).asFloat());
}
Expand Down
18 changes: 17 additions & 1 deletion index.android.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ declare module 'react-native-google-fit' {
options: StartAndEndDate & Partial<BucketOptions>
) => Promise<HeartRateResponse[]>;

getAggregatedHeartRateSamples: (
options: StartAndEndDate & Partial<BucketOptions>,
inLocalTimeZone: boolean
) => Promise<AggregatedHeartRateResponse[]>;

/**
* Query for getting resting heart rate samples. the options object is used to setup a query to retrieve relevant samples.
* @param {Object} options getRestingHeartRateSamples accepts an options object startDate: ISO8601Timestamp and endDate: ISO8601Timestamp.
Expand Down Expand Up @@ -181,9 +186,11 @@ declare module 'react-native-google-fit' {
/**
* Get the sleep sessions over a specified date range.
* @param {Object} options getSleepData accepts an options object containing required startDate: ISO8601Timestamp and endDate: ISO8601Timestamp.
* @param inLocalTimeZone return start and end dates in local time zone rather than converting to UTC.
*/
getSleepSamples: (
options: Partial<StartAndEndDate>
options: Partial<StartAndEndDate>,
inLocalTimeZone: boolean
) => Promise<SleepSampleResponse[]>

saveSleep: (
Expand Down Expand Up @@ -340,6 +347,15 @@ declare module 'react-native-google-fit' {
wasManuallyEntered: boolean
};

export type AggregatedHeartRateResponse = {
startDate: string,
endDate: string,
min: number,
average: number,
max: number,
day: Day,
}

export type BloodPressureResponse = {
startDate: string,
endDate: string,
Expand Down
19 changes: 17 additions & 2 deletions index.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,20 @@ class RNGoogleFit {
return result;
}

getAggregatedHeartRateSamples = async (options, inLocalTimeZone = false) => {
const { startDate, endDate, bucketInterval, bucketUnit } = prepareInput(options);
const result = await googleFit.getAggregatedHeartRateSamples(
startDate,
endDate,
bucketInterval,
bucketUnit
);
if (result.length > 0) {
return prepareResponse(result, 'average', inLocalTimeZone);
}
return result;
}

getRestingHeartRateSamples = async (options) => {
const { startDate, endDate, bucketInterval, bucketUnit } = prepareInput(options);
const result = await googleFit.getRestingHeartRateSamples(
Expand Down Expand Up @@ -689,17 +703,18 @@ class RNGoogleFit {
/**
* Get the sleep sessions over a specified date range.
* @param {Object} options getSleepData accepts an options object containing required startDate: ISO8601Timestamp and endDate: ISO8601Timestamp.
* @param inLocalTimeZone return start and end dates in local time zone rather than converting to UTC
*/

getSleepSamples = async (options) => {
getSleepSamples = async (options, inLocalTimeZone = false) => {
const { startDate, endDate } = prepareInput(options);

const result = await googleFit.getSleepSamples(
startDate,
endDate
);

return prepareResponse(result, "addedBy");
return prepareResponse(result, "addedBy", inLocalTimeZone);
}

saveSleep = async (options) => {
Expand Down
11 changes: 8 additions & 3 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function prepareInput(options) {
return { startDate, endDate, bucketInterval, bucketUnit };
}

export function prepareResponse(response, byKey = 'value') {
export function prepareResponse(response, byKey = 'value', inLocalTimeZone = false) {
return response
.map(el => {
if (!isNil(el[byKey])) {
Expand All @@ -57,8 +57,13 @@ export function prepareResponse(response, byKey = 'value') {
// the Chrome V8 debugger, but not on device. Using momentJS here to get around this issue.
// el.startDate = new Date(el.startDate).toISOString()
// el.endDate = new Date(el.endDate).toISOString()
el.startDate = moment(el.startDate).toISOString()
el.endDate = moment(el.endDate).toISOString()
if (inLocalTimeZone) {
el.startDate = moment.parseZone(el.startDate).toISOString(true)
el.endDate = moment.parseZone(el.endDate).toISOString(true)
} else {
el.startDate = moment(el.startDate).toISOString()
el.endDate = moment(el.endDate).toISOString()
}
return el
}
})
Expand Down