diff --git a/README.md b/README.md index a2cad69..5095057 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,9 @@ Fetch steps on a given period of time. It requires an `Object` with `startDate` - **Fitness.getDistance(dates: { startDate: string, endDate: string })** Fetch distance in meters on a given period of time. It requires an `Object` with `startDate` and `endDate` attributes as string. If startDate is not provided an error will be thrown. +- **Fitness.getCalories(dates: { startDate: string, endDate: string })** +Fetch calories burnt in kilocalories on a given period of time. It requires an `Object` with `startDate` and `endDate` attributes as string. If startDate is not provided an error will be thrown. + - **Fitness.subscribeToActivity()** Available only on android. Subscribe to all Google Fit activities. It returns a promise with `true` for a successful subscription and `false` otherwise. Call this function to get all google fit activites and eliminate the need to have Google Fit installed on the device. diff --git a/android/src/main/java/com/ovalmoney/fitness/RNFitnessModule.java b/android/src/main/java/com/ovalmoney/fitness/RNFitnessModule.java index 7c30866..a0608ba 100644 --- a/android/src/main/java/com/ovalmoney/fitness/RNFitnessModule.java +++ b/android/src/main/java/com/ovalmoney/fitness/RNFitnessModule.java @@ -85,4 +85,13 @@ public void getDistance(double startDate, double endDate, Promise promise){ promise.reject(e); } } + + @ReactMethod + public void getCalories(double startDate, double endDate, Promise promise){ + try { + manager.getCalories(getCurrentActivity(), startDate, endDate, promise); + }catch(Error e){ + promise.reject(e); + } + } } diff --git a/android/src/main/java/com/ovalmoney/fitness/manager/Manager.java b/android/src/main/java/com/ovalmoney/fitness/manager/Manager.java index aca9b4d..b5a6c38 100644 --- a/android/src/main/java/com/ovalmoney/fitness/manager/Manager.java +++ b/android/src/main/java/com/ovalmoney/fitness/manager/Manager.java @@ -63,6 +63,7 @@ public boolean isAuthorized(final Activity activity){ .addDataType(DataType.TYPE_DISTANCE_DELTA, FitnessOptions.ACCESS_READ) .addDataType(DataType.TYPE_ACTIVITY_SAMPLES, FitnessOptions.ACCESS_WRITE) .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE, FitnessOptions.ACCESS_WRITE) + .addDataType(DataType.TYPE_CALORIES_EXPENDED, FitnessOptions.ACCESS_WRITE) .build(); return GoogleSignIn.hasPermissions(GoogleSignIn.getLastSignedInAccount(activity), fitnessOptions); } @@ -76,6 +77,7 @@ public void requestPermissions(@NonNull Activity currentActivity, Promise promis .addDataType(DataType.TYPE_DISTANCE_DELTA, FitnessOptions.ACCESS_READ) .addDataType(DataType.TYPE_ACTIVITY_SAMPLES, FitnessOptions.ACCESS_WRITE) .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE, FitnessOptions.ACCESS_WRITE) + .addDataType(DataType.TYPE_CALORIES_EXPENDED, FitnessOptions.ACCESS_WRITE) .build(); GoogleSignIn.requestPermissions( currentActivity, @@ -218,6 +220,43 @@ public void onComplete(@NonNull Task task) { }); } + public void getCalories(Context context, double startDate, double endDate, final Promise promise) { + DataReadRequest readRequest = new DataReadRequest.Builder() + .aggregate(DataType.TYPE_CALORIES_EXPENDED, DataType.AGGREGATE_CALORIES_EXPENDED) + .bucketByTime(1, TimeUnit.DAYS) + .setTimeRange((long) startDate, (long) endDate, TimeUnit.MILLISECONDS) + .build(); + + Fitness.getHistoryClient(context, GoogleSignIn.getLastSignedInAccount(context)) + .readData(readRequest) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(DataReadResponse dataReadResponse) { + if (dataReadResponse.getBuckets().size() > 0) { + WritableArray calories = Arguments.createArray(); + for (Bucket bucket : dataReadResponse.getBuckets()) { + List dataSets = bucket.getDataSets(); + for (DataSet dataSet : dataSets) { + processDistance(dataSet, distances); + } + } + promise.resolve(distances); + } + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + promise.reject(e); + } + }) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + } + }); + } + private void processStep(DataSet dataSet, WritableArray map) { WritableMap stepMap = Arguments.createMap(); @@ -245,4 +284,18 @@ private void processDistance(DataSet dataSet, WritableArray map) { } } } + + private void processCalories(DataSet dataSet, WritableArray map) { + + WritableMap caloryMap = Arguments.createMap(); + + for (DataPoint dp : dataSet.getDataPoints()) { + for(Field field : dp.getDataType().getFields()) { + caloryMap.putString("startDate", dateFormat.format(dp.getStartTime(TimeUnit.MILLISECONDS))); + caloryMap.putString("endDate", dateFormat.format(dp.getEndTime(TimeUnit.MILLISECONDS))); + caloryMap.putDouble("quantity", dp.getValue(field).asFloat()); + map.pushMap(caloryMap); + } + } + } } diff --git a/ios/RCTFitness/RCTFitness.m b/ios/RCTFitness/RCTFitness.m index f2f75a5..4fd98cf 100644 --- a/ios/RCTFitness/RCTFitness.m +++ b/ios/RCTFitness/RCTFitness.m @@ -51,6 +51,7 @@ - (NSDictionary *)constantsToExport{ NSMutableSet *perms = [NSMutableSet setWithCapacity:1]; [perms addObject:[HKObjectType quantityTypeForIdentifier: HKQuantityTypeIdentifierDistanceWalkingRunning]]; [perms addObject:[HKObjectType quantityTypeForIdentifier: HKQuantityTypeIdentifierStepCount]]; + [perms addObject:[HKObjectType quantityTypeForIdentifier: HKQuantityTypeIdentifierActiveEnergyBurned]]; [self.healthStore requestAuthorizationToShareTypes:nil readTypes:perms completion:^(BOOL success, NSError *error) { if (!success) { NSError * error = [RCTFitness createErrorWithCode:ErrorHKNotAvailable andDescription:RCT_ERROR_HK_NOT_AVAILABLE]; @@ -75,6 +76,7 @@ - (NSDictionary *)constantsToExport{ NSMutableSet *perms = [NSMutableSet setWithCapacity:1]; [perms addObject:[HKObjectType quantityTypeForIdentifier: HKQuantityTypeIdentifierDistanceWalkingRunning]]; [perms addObject:[HKObjectType quantityTypeForIdentifier: HKQuantityTypeIdentifierStepCount]]; + [perms addObject:[HKObjectType quantityTypeForIdentifier: HKQuantityTypeIdentifierActiveEnergyBurned]]; [self.healthStore getRequestStatusForAuthorizationToShareTypes:[NSSet set] readTypes:perms completion:^(HKAuthorizationRequestStatus status, NSError *error) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -222,6 +224,67 @@ - (NSDictionary *)constantsToExport{ [self.healthStore executeQuery:query]; } +RCT_REMAP_METHOD(getCalories, + withStartDate: (double) startDate + andEndDate: (double) endDate + withCaloriesResolver:(RCTPromiseResolveBlock)resolve + andCaloriesRejecter:(RCTPromiseRejectBlock)reject){ + + if(!startDate){ + NSError * error = [RCTFitness createErrorWithCode:ErrorDateNotCorrect andDescription:RCT_ERROR_DATE_NOT_CORRECT]; + [RCTFitness handleRejectBlock:reject error:error]; + return; + } + HKQuantityType *type = + [HKObjectType quantityTypeForIdentifier: HKQuantityTypeIdentifierActiveEnergyBurned]; + NSCalendar *calendar = [NSCalendar currentCalendar]; + NSDateComponents *interval = [[NSDateComponents alloc] init]; + interval.day = 1; + + NSDateComponents *anchorComponents = [calendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear + fromDate:[NSDate date]]; + anchorComponents.hour = 0; + NSDate *anchorDate = [calendar dateFromComponents:anchorComponents]; + HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:type + quantitySamplePredicate:nil + options:HKStatisticsOptionCumulativeSum + anchorDate:anchorDate + intervalComponents:interval]; + query.initialResultsHandler = + ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) { + + if (error) { + NSError * error = [RCTFitness createErrorWithCode:ErrorNoEvents andDescription:RCT_ERROR_NO_EVENTS]; + [RCTFitness handleRejectBlock:reject error:error]; + return; + } + + NSDate * sd = [RCTFitness dateFromTimeStamp: startDate / 1000]; + NSDate * ed = [RCTFitness dateFromTimeStamp: endDate / 1000]; + + NSMutableArray *data = [NSMutableArray arrayWithCapacity:1]; + [results + enumerateStatisticsFromDate: sd + toDate:ed + withBlock:^(HKStatistics *result, BOOL *stop) { + HKQuantity *quantity = result.sumQuantity; + if (quantity) { + NSDictionary *elem = @{ + @"quantity" : @([quantity doubleValueForUnit:[HKUnit kilocalorieUnit]]), + @"startDate" : [RCTFitness ISO8601StringFromDate: result.startDate], + @"endDate" : [RCTFitness ISO8601StringFromDate: result.endDate], + }; + [data addObject:elem]; + } + }]; + dispatch_async(dispatch_get_main_queue(), ^{ + resolve(data); + }); + }; + + [self.healthStore executeQuery:query]; +} + @end diff --git a/js/index.js b/js/index.js index cc75b74..aeada98 100644 --- a/js/index.js +++ b/js/index.js @@ -18,6 +18,15 @@ const getSteps = ({ startDate, endDate }) => const getDistance = ({ startDate, endDate }) => NativeModules.Fitness.getDistance(parseDate(startDate), parseDate(endDate)); +/** + * Get native getCalories with parsed Dates + * @param startDate + * @param endDate + * @returns {*} + */ +const getCalories = ({ startDate, endDate }) => +NativeModules.Fitness.getCalories(parseDate(startDate), parseDate(endDate)); + /** * Check if valid date and parse it * @param date: Date to parse @@ -38,4 +47,5 @@ export default { ...NativeModules.Fitness, getSteps, getDistance, + getCalories, };