diff --git a/Constants/Permissions.js b/Constants/Permissions.js index a6e2d25b..d54f0b85 100644 --- a/Constants/Permissions.js +++ b/Constants/Permissions.js @@ -55,6 +55,7 @@ export const Permissions = { Zinc: "Zinc", Water: "Water", DistanceCycling: "DistanceCycling", + DistanceSwimming: "DistanceSwimming", DistanceWalkingRunning: "DistanceWalkingRunning", FlightsClimbed: "FlightsClimbed", HeartRate: "HeartRate", @@ -66,5 +67,6 @@ export const Permissions = { SleepAnalysis: "SleepAnalysis", StepCount: "StepCount", Steps: "Steps", - Weight: "Weight" + Weight: "Weight", + Workout: "Workout" } diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Activity.h b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Activity.h index 235e9c6b..5bdecf42 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Activity.h +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Activity.h @@ -3,8 +3,10 @@ // RCTAppleHealthKit // // Created by Alexander Vallorosi on 4/27/17. -// Copyright © 2017 Alexander Vallorosi. All rights reserved. +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. // + #import "RCTAppleHealthKit.h" @interface RCTAppleHealthKit (Methods_Activity) diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Activity.m b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Activity.m index 03b2724c..378413ea 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Activity.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Activity.m @@ -3,8 +3,8 @@ // RCTAppleHealthKit // // Created by Alexander Vallorosi on 4/27/17. -// Copyright © 2017 Alexander Vallorosi. All rights reserved. -// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. #import "RCTAppleHealthKit+Methods_Activity.h" #import "RCTAppleHealthKit+Queries.h" @@ -35,8 +35,7 @@ - (void)activity_getActiveEnergyBurned:(NSDictionary *)input callback:(RCTRespon callback(@[[NSNull null], results]); return; } else { - NSLog(@"error getting active energy burned samples: %@", error); - callback(@[RCTMakeError(@"error getting active energy burned samples", nil, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } }]; @@ -65,8 +64,7 @@ - (void)activity_getBasalEnergyBurned:(NSDictionary *)input callback:(RCTRespons callback(@[[NSNull null], results]); return; } else { - NSLog(@"error getting basal energy burned samples: %@", error); - callback(@[RCTMakeError(@"error getting basal energy burned samples", nil, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } }]; diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.h b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.h index 08679742..51b67716 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.h +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.h @@ -23,6 +23,11 @@ - (void)body_saveHeight:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; - (void)body_getLatestBodyFatPercentage:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; +- (void)body_getBodyFatPercentageSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; +- (void)body_saveBodyFatPercentage:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; + - (void)body_getLatestLeanBodyMass:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; +- (void)body_getLeanBodyMassSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; +- (void)body_saveLeanBodyMass:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; @end diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.m b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.m index ca11ae22..e3ba841e 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Body.m @@ -18,22 +18,17 @@ - (void)body_getLatestWeight:(NSDictionary *)input callback:(RCTResponseSenderBl { HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass]; - HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input]; - if(unit == nil){ - unit = [HKUnit poundUnit]; - } - + HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[HKUnit poundUnit]]; + [self fetchMostRecentQuantitySampleOfType:weightType predicate:nil completion:^(HKQuantity *mostRecentQuantity, NSDate *startDate, NSDate *endDate, NSError *error) { if (!mostRecentQuantity) { - NSLog(@"error getting latest weight: %@", error); - callback(@[RCTMakeError(@"error getting latest weight", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); } else { // Determine the weight in the required unit. double usersWeight = [mostRecentQuantity doubleValueForUnit:unit]; - NSDictionary *response = @{ @"value" : @(usersWeight), @"startDate" : [RCTAppleHealthKit buildISO8601StringFromDate:startDate], @@ -71,8 +66,7 @@ - (void)body_getWeightSamples:(NSDictionary *)input callback:(RCTResponseSenderB callback(@[[NSNull null], results]); return; } else { - NSLog(@"error getting weight samples: %@", error); - callback(@[RCTMakeError(@"error getting weight samples", nil, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } }]; @@ -91,8 +85,7 @@ - (void)body_saveWeight:(NSDictionary *)input callback:(RCTResponseSenderBlock)c [self.healthStore saveObject:weightSample withCompletion:^(BOOL success, NSError *error) { if (!success) { - NSLog(@"error saving the weight sample: %@", error); - callback(@[RCTMakeError(@"error saving the weight sample", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } callback(@[[NSNull null], @(weight)]); @@ -108,8 +101,7 @@ - (void)body_getLatestBodyMassIndex:(NSDictionary *)input callback:(RCTResponseS predicate:nil completion:^(HKQuantity *mostRecentQuantity, NSDate *startDate, NSDate *endDate, NSError *error) { if (!mostRecentQuantity) { - NSLog(@"error getting latest BMI: %@", error); - callback(@[RCTMakeError(@"error getting latest BMI", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); } else { // Determine the bmi in the required unit. @@ -140,8 +132,7 @@ - (void)body_saveBodyMassIndex:(NSDictionary *)input callback:(RCTResponseSender [self.healthStore saveObject:bmiSample withCompletion:^(BOOL success, NSError *error) { if (!success) { - NSLog(@"error saving BMI sample: %@.", error); - callback(@[RCTMakeError(@"error saving BMI sample", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } callback(@[[NSNull null], @(bmi)]); @@ -152,11 +143,7 @@ - (void)body_saveBodyMassIndex:(NSDictionary *)input callback:(RCTResponseSender - (void)body_getLatestHeight:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback { HKQuantityType *heightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight]; - - HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input]; - if(unit == nil){ - unit = [HKUnit inchUnit]; - } + HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[HKUnit inchUnit]];; [self fetchMostRecentQuantitySampleOfType:heightType predicate:nil @@ -206,8 +193,7 @@ - (void)body_getHeightSamples:(NSDictionary *)input callback:(RCTResponseSenderB callback(@[[NSNull null], results]); return; } else { - NSLog(@"error getting height samples: %@", error); - callback(@[RCTMakeError(@"error getting height samples", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } }]; @@ -218,11 +204,7 @@ - (void)body_saveHeight:(NSDictionary *)input callback:(RCTResponseSenderBlock)c { double height = [RCTAppleHealthKit doubleValueFromOptions:input]; NSDate *sampleDate = [RCTAppleHealthKit dateFromOptionsDefaultNow:input]; - - HKUnit *heightUnit = [RCTAppleHealthKit hkUnitFromOptions:input]; - if(heightUnit == nil){ - heightUnit = [HKUnit inchUnit]; - } + HKUnit *heightUnit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[HKUnit inchUnit]]; HKQuantity *heightQuantity = [HKQuantity quantityWithUnit:heightUnit doubleValue:height]; HKQuantityType *heightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight]; @@ -230,8 +212,7 @@ - (void)body_saveHeight:(NSDictionary *)input callback:(RCTResponseSenderBlock)c [self.healthStore saveObject:heightSample withCompletion:^(BOOL success, NSError *error) { if (!success) { - NSLog(@"error saving height sample: %@", error); - callback(@[RCTMakeError(@"error saving height sample", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } callback(@[[NSNull null], @(height)]); @@ -247,8 +228,7 @@ - (void)body_getLatestBodyFatPercentage:(NSDictionary *)input callback:(RCTRespo predicate:nil completion:^(HKQuantity *mostRecentQuantity, NSDate *startDate, NSDate *endDate, NSError *error) { if (!mostRecentQuantity) { - NSLog(@"error getting latest body fat percentage: %@", error); - callback(@[RCTMakeError(@"error getting latest body fat percentage", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); } else { // Determine the weight in the required unit. @@ -269,6 +249,62 @@ - (void)body_getLatestBodyFatPercentage:(NSDictionary *)input callback:(RCTRespo } +- (void)body_getBodyFatPercentageSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback +{ + HKQuantityType *bodyFatPercentType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyFatPercentage]; + + HKUnit *unit = [HKUnit percentUnit]; + NSUInteger limit = [RCTAppleHealthKit uintFromOptions:input key:@"limit" withDefault:HKObjectQueryNoLimit]; + BOOL ascending = [RCTAppleHealthKit boolFromOptions:input key:@"ascending" withDefault:false]; + NSDate *startDate = [RCTAppleHealthKit dateFromOptions:input key:@"startDate" withDefault:nil]; + NSDate *endDate = [RCTAppleHealthKit dateFromOptions:input key:@"endDate" withDefault:[NSDate date]]; + if(startDate == nil){ + callback(@[RCTMakeError(@"startDate is required in options", nil, nil)]); + return; + } + NSPredicate * predicate = [RCTAppleHealthKit predicateForSamplesBetweenDates:startDate endDate:endDate]; + + [self fetchQuantitySamplesOfType:bodyFatPercentType + unit:unit + predicate:predicate + ascending:ascending + limit:limit + completion:^(NSArray *results, NSError *error) { + if(results){ + callback(@[[NSNull null], results]); + return; + } else { + NSLog(@"error getting body fat percentage samples: %@", error); + callback(@[RCTMakeError(@"error getting body fat percentage samples", nil, nil)]); + return; + } + }]; +} + + +- (void)body_saveBodyFatPercentage:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback +{ + double percentage = [RCTAppleHealthKit doubleValueFromOptions:input]; + NSDate *sampleDate = [RCTAppleHealthKit dateFromOptionsDefaultNow:input]; + HKUnit *unit = [HKUnit percentUnit]; + + percentage = percentage / 100; + + HKQuantity *bodyFatPercentQuantity = [HKQuantity quantityWithUnit:unit doubleValue:percentage]; + HKQuantityType *bodyFatPercentType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyFatPercentage]; + HKQuantitySample *bodyFatPercentSample = [HKQuantitySample quantitySampleWithType:bodyFatPercentType quantity:bodyFatPercentQuantity startDate:sampleDate endDate:sampleDate]; + + [self.healthStore saveObject:bodyFatPercentSample withCompletion:^(BOOL success, NSError *error) { + if (!success) { + NSLog(@"error saving body fat percent sample: %@", error); + callback(@[RCTMakeError(@"error saving body fat percent sample", error, nil)]); + return; + } + callback(@[[NSNull null], @(percentage)]); + }]; +} + + - (void)body_getLatestLeanBodyMass:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback { HKQuantityType *leanBodyMassType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierLeanBodyMass]; @@ -277,8 +313,7 @@ - (void)body_getLatestLeanBodyMass:(NSDictionary *)input callback:(RCTResponseSe predicate:nil completion:^(HKQuantity *mostRecentQuantity, NSDate *startDate, NSDate *endDate, NSError *error) { if (!mostRecentQuantity) { - NSLog(@"error getting latest lean body mass: %@", error); - callback(@[RCTMakeError(@"error getting latest lean body mass", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); } else { HKUnit *weightUnit = [HKUnit poundUnit]; @@ -295,4 +330,58 @@ - (void)body_getLatestLeanBodyMass:(NSDictionary *)input callback:(RCTResponseSe }]; } + +- (void)body_getLeanBodyMassSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback +{ + HKQuantityType *leanBodyMassType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierLeanBodyMass]; + + HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[HKUnit poundUnit]]; + NSUInteger limit = [RCTAppleHealthKit uintFromOptions:input key:@"limit" withDefault:HKObjectQueryNoLimit]; + BOOL ascending = [RCTAppleHealthKit boolFromOptions:input key:@"ascending" withDefault:false]; + NSDate *startDate = [RCTAppleHealthKit dateFromOptions:input key:@"startDate" withDefault:nil]; + NSDate *endDate = [RCTAppleHealthKit dateFromOptions:input key:@"endDate" withDefault:[NSDate date]]; + if(startDate == nil){ + callback(@[RCTMakeError(@"startDate is required in options", nil, nil)]); + return; + } + NSPredicate * predicate = [RCTAppleHealthKit predicateForSamplesBetweenDates:startDate endDate:endDate]; + + [self fetchQuantitySamplesOfType:leanBodyMassType + unit:unit + predicate:predicate + ascending:ascending + limit:limit + completion:^(NSArray *results, NSError *error) { + if(results){ + callback(@[[NSNull null], results]); + return; + } else { + NSLog(@"error getting lean body mass samples: %@", error); + callback(@[RCTMakeError(@"error getting lean body mass samples", nil, nil)]); + return; + } + }]; +} + + +- (void)body_saveLeanBodyMass:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback +{ + double mass = [RCTAppleHealthKit doubleValueFromOptions:input]; + NSDate *sampleDate = [RCTAppleHealthKit dateFromOptions:input key:@"startDate" withDefault:[NSDate date]]; + HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[HKUnit poundUnit]]; + + HKQuantity *massQuantity = [HKQuantity quantityWithUnit:unit doubleValue:mass]; + HKQuantityType *massType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierLeanBodyMass]; + HKQuantitySample *massSample = [HKQuantitySample quantitySampleWithType:massType quantity:massQuantity startDate:sampleDate endDate:sampleDate]; + + [self.healthStore saveObject:massSample withCompletion:^(BOOL success, NSError *error) { + if (!success) { + NSLog(@"error saving lean body mass sample: %@", error); + callback(@[RCTMakeError(@"error saving lean body mass sample", error, nil)]); + return; + } + callback(@[[NSNull null], @(mass)]); + }]; +} + @end diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Characteristic.m b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Characteristic.m index e030b856..e6266a93 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Characteristic.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Characteristic.m @@ -34,8 +34,7 @@ - (void)characteristic_getBiologicalSex:(NSDictionary *)input callback:(RCTRespo } if(value == nil){ - NSLog(@"error getting biological sex: %@", error); - callback(@[RCTMakeError(@"error getting biological sex", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } @@ -52,8 +51,7 @@ - (void)characteristic_getDateOfBirth:(NSDictionary *)input callback:(RCTRespons NSDate *dob = [self.healthStore dateOfBirthWithError:&error]; if(error != nil){ - NSLog(@"error getting date of birth: %@", error); - callback(@[RCTMakeError(@"error getting date of birth", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } if(dob == nil) { diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Fitness.h b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Fitness.h index d575d9de..6f1b007f 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Fitness.h +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Fitness.h @@ -14,10 +14,13 @@ - (void)fitness_getStepCountOnDay:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; - (void)fitness_getHourlyStepCountOnDay:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; - (void)fitness_getDailyStepSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; +- (void)fitness_getSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; +- (void)fitness_setObserver:(NSDictionary *)input; - (void)fitness_saveSteps:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; - (void)fitness_initializeStepEventObserver:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; - (void)fitness_getDistanceWalkingRunningOnDay:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; - (void)fitness_getDailyDistanceWalkingRunningSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; +- (void)fitness_getDailyDistanceSwimmingSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; - (void)fitness_getDistanceCyclingOnDay:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; - (void)fitness_getDailyDistanceCyclingSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; - (void)fitness_getFlightsClimbedOnDay:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Fitness.m b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Fitness.m index 734ebc1a..0e78680d 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Fitness.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Fitness.m @@ -34,8 +34,7 @@ - (void)fitness_getStepCountOnDay:(NSDictionary *)input callback:(RCTResponseSen day:date completion:^(double value, NSDate *startDate, NSDate *endDate, NSError *error) { if (!value) { - NSLog(@"could not fetch step count for day: %@", error); - callback(@[RCTMakeError(@"could not fetch step count for day", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } @@ -49,6 +48,52 @@ - (void)fitness_getStepCountOnDay:(NSDictionary *)input callback:(RCTResponseSen }]; } +- (void)fitness_getSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback +{ + HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[HKUnit countUnit]]; + NSUInteger limit = [RCTAppleHealthKit uintFromOptions:input key:@"limit" withDefault:HKObjectQueryNoLimit]; + BOOL ascending = [RCTAppleHealthKit boolFromOptions:input key:@"ascending" withDefault:false]; + NSString *type = [RCTAppleHealthKit stringFromOptions:input key:@"type" withDefault:@"Walking"]; + NSDate *startDate = [RCTAppleHealthKit dateFromOptions:input key:@"startDate" withDefault:[NSDate date]]; + NSDate *endDate = [RCTAppleHealthKit dateFromOptions:input key:@"endDate" withDefault:[NSDate date]]; + + NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate]; + + HKSampleType *samplesType = [RCTAppleHealthKit hkQuantityTypeFromString:type]; + if ([type isEqual:@"Running"] || [type isEqual:@"Cycling"]) { + unit = [HKUnit mileUnit]; + } + NSLog(@"error getting samples: %@", [samplesType identifier]); + [self fetchSamplesOfType:samplesType + unit:unit + predicate:predicate + ascending:ascending + limit:limit + completion:^(NSArray *results, NSError *error) { + if(results){ + callback(@[[NSNull null], results]); + return; + } else { + NSLog(@"error getting samples: %@", error); + callback(@[RCTMakeError(@"error getting samples", nil, nil)]); + return; + } + }]; +} + +- (void)fitness_setObserver:(NSDictionary *)input +{ + HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[HKUnit countUnit]]; + NSString *type = [RCTAppleHealthKit stringFromOptions:input key:@"type" withDefault:@"Walking"]; + + HKSampleType *samplesType = [RCTAppleHealthKit hkQuantityTypeFromString:type]; + if ([type isEqual:@"Running"] || [type isEqual:@"Cycling"]) { + unit = [HKUnit mileUnit]; + } + + [self setObserverForType:samplesType unit:unit]; +} + - (void)fitness_getHourlyStepCountOnDay:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback { @@ -85,6 +130,9 @@ - (void)fitness_getDailyStepSamples:(NSDictionary *)input callback:(RCTResponseS BOOL ascending = [RCTAppleHealthKit boolFromOptions:input key:@"ascending" withDefault:false]; NSDate *startDate = [RCTAppleHealthKit dateFromOptions:input key:@"startDate" withDefault:nil]; NSDate *endDate = [RCTAppleHealthKit dateFromOptions:input key:@"endDate" withDefault:[NSDate date]]; + NSUInteger period = [RCTAppleHealthKit uintFromOptions:input key:@"period" withDefault:60]; + BOOL includeManuallyAdded = [RCTAppleHealthKit boolFromOptions:input key:@"includeManuallyAdded" withDefault:false]; + if(startDate == nil){ callback(@[RCTMakeError(@"startDate is required in options", nil, nil)]); return; @@ -94,14 +142,15 @@ - (void)fitness_getDailyStepSamples:(NSDictionary *)input callback:(RCTResponseS [self fetchCumulativeSumStatisticsCollection:stepCountType unit:unit + period:period startDate:startDate endDate:endDate ascending:ascending limit:limit + includeManuallyAdded:includeManuallyAdded completion:^(NSArray *arr, NSError *err){ if (err != nil) { - NSLog(@"error with fetchCumulativeSumStatisticsCollection: %@", err); - callback(@[RCTMakeError(@"error with fetchCumulativeSumStatisticsCollection", err, nil)]); + callback(@[RCTJSErrorFromNSError(err)]); return; } callback(@[[NSNull null], arr]); @@ -127,8 +176,7 @@ - (void)fitness_saveSteps:(NSDictionary *)input callback:(RCTResponseSenderBlock [self.healthStore saveObject:sample withCompletion:^(BOOL success, NSError *error) { if (!success) { - NSLog(@"An error occured saving the step count sample %@. The error was: %@.", sample, error); - callback(@[RCTMakeError(@"An error occured saving the step count sample", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } callback(@[[NSNull null], @(value)]); @@ -150,9 +198,7 @@ - (void)fitness_initializeStepEventObserver:(NSDictionary *)input callback:(RCTR NSError *error) { if (error) { - // Perform Proper Error Handling Here... - NSLog(@"*** An error occured while setting up the stepCount observer. %@ ***", error.localizedDescription); - callback(@[RCTMakeError(@"An error occured while setting up the stepCount observer", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } @@ -177,8 +223,7 @@ - (void)fitness_getDistanceWalkingRunningOnDay:(NSDictionary *)input callback:(R [self fetchSumOfSamplesOnDayForType:quantityType unit:unit day:date completion:^(double distance, NSDate *startDate, NSDate *endDate, NSError *error) { if (!distance) { - NSLog(@"ERROR getting DistanceWalkingRunning: %@", error); - callback(@[RCTMakeError(@"ERROR getting DistanceWalkingRunning", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } @@ -200,6 +245,8 @@ - (void)fitness_getDailyDistanceWalkingRunningSamples:(NSDictionary *)input call BOOL ascending = [RCTAppleHealthKit boolFromOptions:input key:@"ascending" withDefault:false]; NSDate *startDate = [RCTAppleHealthKit dateFromOptions:input key:@"startDate" withDefault:nil]; NSDate *endDate = [RCTAppleHealthKit dateFromOptions:input key:@"endDate" withDefault:[NSDate date]]; + NSUInteger period = [RCTAppleHealthKit uintFromOptions:input key:@"period" withDefault:60]; + BOOL includeManuallyAdded = [RCTAppleHealthKit boolFromOptions:input key:@"includeManuallyAdded" withDefault:false]; if(startDate == nil){ callback(@[RCTMakeError(@"startDate is required in options", nil, nil)]); return; @@ -209,10 +256,12 @@ - (void)fitness_getDailyDistanceWalkingRunningSamples:(NSDictionary *)input call [self fetchCumulativeSumStatisticsCollection:quantityType unit:unit + period:period startDate:startDate endDate:endDate ascending:ascending limit:limit + includeManuallyAdded:includeManuallyAdded completion:^(NSArray *arr, NSError *err){ if (err != nil) { NSLog(@"error with fetchCumulativeSumStatisticsCollection: %@", err); @@ -223,6 +272,39 @@ - (void)fitness_getDailyDistanceWalkingRunningSamples:(NSDictionary *)input call }]; } +- (void)fitness_getDailyDistanceSwimmingSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback +{ + HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[HKUnit meterUnit]]; + NSUInteger limit = [RCTAppleHealthKit uintFromOptions:input key:@"limit" withDefault:HKObjectQueryNoLimit]; + BOOL ascending = [RCTAppleHealthKit boolFromOptions:input key:@"ascending" withDefault:false]; + NSDate *startDate = [RCTAppleHealthKit dateFromOptions:input key:@"startDate" withDefault:nil]; + NSDate *endDate = [RCTAppleHealthKit dateFromOptions:input key:@"endDate" withDefault:[NSDate date]]; + NSUInteger period = [RCTAppleHealthKit uintFromOptions:input key:@"period" withDefault:60]; + BOOL includeManuallyAdded = [RCTAppleHealthKit boolFromOptions:input key:@"includeManuallyAdded" withDefault:false]; + if(startDate == nil){ + callback(@[RCTMakeError(@"startDate is required in options", nil, nil)]); + return; + } + + HKQuantityType *quantityType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceSwimming]; + + [self fetchCumulativeSumStatisticsCollection:quantityType + unit:unit + period:period + startDate:startDate + endDate:endDate + ascending:ascending + limit:limit + includeManuallyAdded:includeManuallyAdded + completion:^(NSArray *arr, NSError *err){ + if (err != nil) { + callback(@[RCTJSErrorFromNSError(err)]); + return; + } + callback(@[[NSNull null], arr]); + }]; +} + - (void)fitness_getDistanceCyclingOnDay:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback { HKUnit *unit = [RCTAppleHealthKit hkUnitFromOptions:input key:@"unit" withDefault:[HKUnit meterUnit]]; @@ -232,8 +314,7 @@ - (void)fitness_getDistanceCyclingOnDay:(NSDictionary *)input callback:(RCTRespo [self fetchSumOfSamplesOnDayForType:quantityType unit:unit day:date completion:^(double distance, NSDate *startDate, NSDate *endDate, NSError *error) { if (!distance) { - NSLog(@"ERROR getting DistanceCycling: %@", error); - callback(@[RCTMakeError(@"ERROR getting DistanceCycling", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } @@ -254,6 +335,8 @@ - (void)fitness_getDailyDistanceCyclingSamples:(NSDictionary *)input callback:(R BOOL ascending = [RCTAppleHealthKit boolFromOptions:input key:@"ascending" withDefault:false]; NSDate *startDate = [RCTAppleHealthKit dateFromOptions:input key:@"startDate" withDefault:nil]; NSDate *endDate = [RCTAppleHealthKit dateFromOptions:input key:@"endDate" withDefault:[NSDate date]]; + NSUInteger period = [RCTAppleHealthKit uintFromOptions:input key:@"period" withDefault:60]; + BOOL includeManuallyAdded = [RCTAppleHealthKit boolFromOptions:input key:@"includeManuallyAdded" withDefault:false]; if(startDate == nil){ callback(@[RCTMakeError(@"startDate is required in options", nil, nil)]); return; @@ -263,14 +346,15 @@ - (void)fitness_getDailyDistanceCyclingSamples:(NSDictionary *)input callback:(R [self fetchCumulativeSumStatisticsCollection:quantityType unit:unit + period:period startDate:startDate endDate:endDate ascending:ascending limit:limit + includeManuallyAdded:includeManuallyAdded completion:^(NSArray *arr, NSError *err){ if (err != nil) { - NSLog(@"error with fetchCumulativeSumStatisticsCollection: %@", err); - callback(@[RCTMakeError(@"error with fetchCumulativeSumStatisticsCollection", err, nil)]); + callback(@[RCTJSErrorFromNSError(err)]); return; } callback(@[[NSNull null], arr]); @@ -286,8 +370,7 @@ - (void)fitness_getFlightsClimbedOnDay:(NSDictionary *)input callback:(RCTRespon [self fetchSumOfSamplesOnDayForType:quantityType unit:unit day:date completion:^(double count, NSDate *startDate, NSDate *endDate, NSError *error) { if (!count) { - NSLog(@"ERROR getting FlightsClimbed: %@", error); - callback(@[RCTMakeError(@"ERROR getting FlightsClimbed", error, nil), @(count)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } @@ -323,8 +406,7 @@ - (void)fitness_getDailyFlightsClimbedSamples:(NSDictionary *)input callback:(RC limit:limit completion:^(NSArray *arr, NSError *err){ if (err != nil) { - NSLog(@"error with fetchCumulativeSumStatisticsCollection: %@", err); - callback(@[RCTMakeError(@"error with fetchCumulativeSumStatisticsCollection", err, nil)]); + callback(@[RCTJSErrorFromNSError(err)]); return; } callback(@[[NSNull null], arr]); diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Mindfulness.m b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Mindfulness.m index b14ef813..1dc6dca9 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Mindfulness.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Mindfulness.m @@ -29,8 +29,7 @@ - (void)mindfulness_saveMindfulSession:(NSDictionary *)input callback:(RCTRespon [self.healthStore saveObject:sample withCompletion:^(BOOL success, NSError *error) { if (!success) { - NSLog(@"An error occured saving the mindful session sample %@. The error was: %@.", sample, error); - callback(@[RCTMakeError(@"An error occured saving the mindful session sample", error, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } callback(@[[NSNull null], @(value)]); diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Results.m b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Results.m index fd5d0345..a1c1cdea 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Results.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Results.m @@ -32,8 +32,7 @@ - (void)results_getBloodGlucoseSamples:(NSDictionary *)input callback:(RCTRespon callback(@[[NSNull null], results]); return; } else { - NSLog(@"error getting blood glucose samples: %@", error); - callback(@[RCTMakeError(@"error getting blood glucose samples", nil, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } }]; diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Sleep.m b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Sleep.m index faba4ed1..0894624e 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Sleep.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Sleep.m @@ -36,8 +36,7 @@ - (void)sleep_getSleepSamples:(NSDictionary *)input callback:(RCTResponseSenderB callback(@[[NSNull null], results]); return; } else { - NSLog(@"error getting sleep samples: %@", error); - callback(@[RCTMakeError(@"error getting sleep samples", nil, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } }]; diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Vitals.m b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Vitals.m index 74c53cb7..698a1b25 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Vitals.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Methods_Vitals.m @@ -33,8 +33,7 @@ - (void)vitals_getHeartRateSamples:(NSDictionary *)input callback:(RCTResponseSe callback(@[[NSNull null], results]); return; } else { - NSLog(@"error getting heart rate samples: %@", error); - callback(@[RCTMakeError(@"error getting heart rate samples", nil, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } }]; diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Queries.h b/RCTAppleHealthKit/RCTAppleHealthKit+Queries.h index fd02bf5c..2a70f001 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Queries.h +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Queries.h @@ -24,6 +24,18 @@ startDate:(NSDate *)startDate endDate:(NSDate *)endDate completion:(void (^)(NSArray *, NSError *))completionHandler; + + +- (void)fetchSamplesOfType:(HKSampleType *)quantityType + unit:(HKUnit *)unit + predicate:(NSPredicate *)predicate + ascending:(BOOL)asc + limit:(NSUInteger)lim + completion:(void (^)(NSArray *, NSError *))completion; +- (void)setObserverForType:(HKSampleType *)quantityType + unit:(HKUnit *)unit; + + - (void)fetchQuantitySamplesOfType:(HKQuantityType *)quantityType unit:(HKUnit *)unit predicate:(NSPredicate *)predicate @@ -43,7 +55,15 @@ ascending:(BOOL)asc limit:(NSUInteger)lim completion:(void (^)(NSArray *, NSError *))completionHandler; - +- (void)fetchCumulativeSumStatisticsCollection:(HKQuantityType *)quantityType + unit:(HKUnit *)unit + period:(NSUInteger)period + startDate:(NSDate *)startDate + endDate:(NSDate *)endDate + ascending:(BOOL)asc + limit:(NSUInteger)lim + includeManuallyAdded:(BOOL)includeManuallyAdded + completion:(void (^)(NSArray *, NSError *))completionHandler; - (void)fetchSleepCategorySamplesForPredicate:(NSPredicate *)predicate diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Queries.m b/RCTAppleHealthKit/RCTAppleHealthKit+Queries.m index d839bdec..cb282377 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Queries.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Queries.m @@ -10,8 +10,10 @@ #import "RCTAppleHealthKit+Queries.h" #import "RCTAppleHealthKit+Utils.h" -@implementation RCTAppleHealthKit (Queries) +#import <React/RCTBridgeModule.h> +#import <React/RCTEventDispatcher.h> +@implementation RCTAppleHealthKit (Queries) - (void)fetchMostRecentQuantitySampleOfType:(HKQuantityType *)quantityType predicate:(NSPredicate *)predicate @@ -49,7 +51,6 @@ - (void)fetchMostRecentQuantitySampleOfType:(HKQuantityType *)quantityType [self.healthStore executeQuery:query]; } - - (void)fetchQuantitySamplesOfType:(HKQuantityType *)quantityType unit:(HKUnit *)unit predicate:(NSPredicate *)predicate @@ -85,6 +86,8 @@ - (void)fetchQuantitySamplesOfType:(HKQuantityType *)quantityType NSDictionary *elem = @{ @"value" : @(value), + @"sourceName" : [[[sample sourceRevision] source] name], + @"sourceId" : [[[sample sourceRevision] source] bundleIdentifier], @"startDate" : startDateString, @"endDate" : endDateString, }; @@ -106,19 +109,153 @@ - (void)fetchQuantitySamplesOfType:(HKQuantityType *)quantityType [self.healthStore executeQuery:query]; } +- (void)fetchSamplesOfType:(HKSampleType *)type + unit:(HKUnit *)unit + predicate:(NSPredicate *)predicate + ascending:(BOOL)asc + limit:(NSUInteger)lim + completion:(void (^)(NSArray *, NSError *))completion { + NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate + ascending:asc]; + // declare the block + void (^handlerBlock)(HKSampleQuery *query, NSArray *results, NSError *error); + // create and assign the block + handlerBlock = ^(HKSampleQuery *query, NSArray *results, NSError *error) { + if (!results) { + if (completion) { + completion(nil, error); + } + return; + } + if (completion) { + NSMutableArray *data = [NSMutableArray arrayWithCapacity:1]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (type == [HKObjectType workoutType]) { + for (HKWorkout *sample in results) { + double energy = [[sample totalEnergyBurned] doubleValueForUnit:[HKUnit kilocalorieUnit]]; + double distance = [[sample totalDistance] doubleValueForUnit:[HKUnit mileUnit]]; + NSString *type = [RCTAppleHealthKit stringForHKWorkoutActivityType:[sample workoutActivityType]]; + + NSString *startDateString = [RCTAppleHealthKit buildISO8601StringFromDate:sample.startDate]; + NSString *endDateString = [RCTAppleHealthKit buildISO8601StringFromDate:sample.endDate]; + + bool isTracked = true; + if ([[sample metadata][HKMetadataKeyWasUserEntered] intValue] == 1) { + isTracked = false; + } + + NSString* device = @""; + if (@available(iOS 11.0, *)) { + device = [[sample sourceRevision] productType]; + } else { + device = [[sample device] name]; + if (!device) { + device = @"iPhone"; + } + } + + NSDictionary *elem = @{ + @"activityId" : [NSNumber numberWithInt:[sample workoutActivityType]], + @"activityName" : type, + @"calories" : @(energy), + @"tracked" : @(isTracked), + @"sourceName" : [[[sample sourceRevision] source] name], + @"sourceId" : [[[sample sourceRevision] source] bundleIdentifier], + @"device": device, + @"distance" : @(distance), + @"start" : startDateString, + @"end" : endDateString + }; + + [data addObject:elem]; + } + } else { + for (HKQuantitySample *sample in results) { + HKQuantity *quantity = sample.quantity; + double value = [quantity doubleValueForUnit:unit]; + + NSString * valueType = @"quantity"; + if (unit == [HKUnit mileUnit]) { + valueType = @"distance"; + } + + NSString *startDateString = [RCTAppleHealthKit buildISO8601StringFromDate:sample.startDate]; + NSString *endDateString = [RCTAppleHealthKit buildISO8601StringFromDate:sample.endDate]; + + bool isTracked = true; + if ([[sample metadata][HKMetadataKeyWasUserEntered] intValue] == 1) { + isTracked = false; + } + + NSString* device = @""; + if (@available(iOS 11.0, *)) { + device = [[sample sourceRevision] productType]; + } else { + device = [[sample device] name]; + if (!device) { + device = @"iPhone"; + } + } + + NSDictionary *elem = @{ + valueType : @(value), + @"tracked" : @(isTracked), + @"sourceName" : [[[sample sourceRevision] source] name], + @"sourceId" : [[[sample sourceRevision] source] bundleIdentifier], + @"device": device, + @"start" : startDateString, + @"end" : endDateString + }; + + [data addObject:elem]; + } + } + completion(data, error); + }); + } + }; + HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:type + predicate:predicate + limit:lim + sortDescriptors:@[timeSortDescriptor] + resultsHandler:handlerBlock]; + + [self.healthStore executeQuery:query]; +} +- (void)setObserverForType:(HKSampleType *)type + unit:(HKUnit *)unit { + HKObserverQuery *query = [[HKObserverQuery alloc] initWithSampleType:type predicate:nil updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError * _Nullable error){ + if (error) { + NSLog(@"*** An error occured while setting up the stepCount observer. %@ ***", error.localizedDescription); + return; + } + [self.bridge.eventDispatcher sendAppEventWithName:@"observer" body:@""]; + + // Theoretically, HealthKit expect that copletionHandler would be called at the end of query process, + // but it's unclear how to do in in event paradigm +// dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5); +// dispatch_after(delay, dispatch_get_main_queue(), ^(void){ +// completionHandler(); +// }); + }]; + + [self.healthStore executeQuery:query]; + [self.healthStore enableBackgroundDeliveryForType:type frequency:HKUpdateFrequencyImmediate withCompletion:^(BOOL success, NSError * _Nullable error) { + NSLog(@"success %s print some error %@", success ? "true" : "false", [error localizedDescription]); + }]; +} - (void)fetchSleepCategorySamplesForPredicate:(NSPredicate *)predicate limit:(NSUInteger)lim completion:(void (^)(NSArray *, NSError *))completion { - NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:false]; @@ -346,10 +483,10 @@ - (void)fetchCumulativeSumStatisticsCollection:(HKQuantityType *)quantityType fromDate:[NSDate date]]; anchorComponents.hour = 0; NSDate *anchorDate = [calendar dateFromComponents:anchorComponents]; - + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"metadata.%K != YES", HKMetadataKeyWasUserEntered]; // Create the query HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType - quantitySamplePredicate:nil + quantitySamplePredicate:predicate options:HKStatisticsOptionCumulativeSum anchorDate:anchorDate intervalComponents:interval]; @@ -414,9 +551,87 @@ - (void)fetchCumulativeSumStatisticsCollection:(HKQuantityType *)quantityType anchorComponents.hour = 0; NSDate *anchorDate = [calendar dateFromComponents:anchorComponents]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"metadata.%K != YES", HKMetadataKeyWasUserEntered]; + // Create the query + HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType + quantitySamplePredicate:predicate + options:HKStatisticsOptionCumulativeSum + anchorDate:anchorDate + intervalComponents:interval]; + + // Set the results handler + query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) { + if (error) { + // Perform proper error handling here + NSLog(@"*** An error occurred while calculating the statistics: %@ ***", error.localizedDescription); + } + + NSMutableArray *data = [NSMutableArray arrayWithCapacity:1]; + + [results enumerateStatisticsFromDate:startDate + toDate:endDate + withBlock:^(HKStatistics *result, BOOL *stop) { + + HKQuantity *quantity = result.sumQuantity; + if (quantity) { + NSDate *startDate = result.startDate; + NSDate *endDate = result.endDate; + double value = [quantity doubleValueForUnit:unit]; + + NSString *startDateString = [RCTAppleHealthKit buildISO8601StringFromDate:startDate]; + NSString *endDateString = [RCTAppleHealthKit buildISO8601StringFromDate:endDate]; + + NSDictionary *elem = @{ + @"value" : @(value), + @"startDate" : startDateString, + @"endDate" : endDateString, + }; + [data addObject:elem]; + } + }]; + // is ascending by default + if(asc == false) { + [RCTAppleHealthKit reverseNSMutableArray:data]; + } + + if((lim > 0) && ([data count] > lim)) { + NSArray* slicedArray = [data subarrayWithRange:NSMakeRange(0, lim)]; + NSError *err; + completionHandler(slicedArray, err); + } else { + NSError *err; + completionHandler(data, err); + } + }; + + [self.healthStore executeQuery:query]; +} + +- (void)fetchCumulativeSumStatisticsCollection:(HKQuantityType *)quantityType + unit:(HKUnit *)unit + period:(NSUInteger)period + startDate:(NSDate *)startDate + endDate:(NSDate *)endDate + ascending:(BOOL)asc + limit:(NSUInteger)lim + includeManuallyAdded:(BOOL)includeManuallyAdded + completion:(void (^)(NSArray *, NSError *))completionHandler { + + NSCalendar *calendar = [NSCalendar currentCalendar]; + NSDateComponents *interval = [[NSDateComponents alloc] init]; + interval.minute = period; + + NSDateComponents *anchorComponents = [calendar components:NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear + fromDate:startDate]; + //anchorComponents.hour = 0; + NSDate *anchorDate = [calendar dateFromComponents:anchorComponents]; + NSPredicate *predicate = nil; + if (includeManuallyAdded == false) { + predicate = [NSPredicate predicateWithFormat:@"metadata.%K != YES", HKMetadataKeyWasUserEntered]; + } // Create the query HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType - quantitySamplePredicate:nil + quantitySamplePredicate:predicate options:HKStatisticsOptionCumulativeSum anchorDate:anchorDate intervalComponents:interval]; diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.h b/RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.h index 5cb0891b..c6668c69 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.h +++ b/RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.h @@ -11,7 +11,11 @@ @interface RCTAppleHealthKit (TypesAndPermissions) +- (NSDictionary *)readPermsDict; +- (NSDictionary *)writePermsDict; - (NSSet *)getReadPermsFromOptions:(NSArray *)options; - (NSSet *)getWritePermsFromOptions:(NSArray *)options; +- (HKObjectType *)getWritePermFromString:(NSString *)string; +- (NSString *)getAuthorizationStatusString:(HKAuthorizationStatus)status; @end diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.m b/RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.m index 450c0338..a54f03fb 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit+TypesAndPermissions.m @@ -3,7 +3,8 @@ // RCTAppleHealthKit // // Created by Greg Wilson on 2016-06-26. -// Copyright © 2016 Greg Wilson. All rights reserved. +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. // #import "RCTAppleHealthKit+TypesAndPermissions.h" @@ -30,6 +31,7 @@ - (NSDictionary *)readPermsDict { @"StepCount" : [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount], @"DistanceWalkingRunning" : [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning], @"DistanceCycling" : [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceCycling], + @"DistanceSwimming" : [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceSwimming], @"BasalEnergyBurned" : [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBasalEnergyBurned], @"ActiveEnergyBurned" : [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned], @"FlightsClimbed" : [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierFlightsClimbed], @@ -49,6 +51,8 @@ - (NSDictionary *)readPermsDict { @"SleepAnalysis" : [HKObjectType categoryTypeForIdentifier:HKCategoryTypeIdentifierSleepAnalysis], // Mindfulness @"MindfulSession" : [HKObjectType categoryTypeForIdentifier:HKCategoryTypeIdentifierMindfulSession], + //workouts + @"Workout" : [HKObjectType workoutType], }; return readPerms; } @@ -118,7 +122,6 @@ - (NSDictionary *)writePermsDict { return writePerms; } - // Returns HealthKit read permissions from options array - (NSSet *)getReadPermsFromOptions:(NSArray *)options { NSDictionary *readPermDict = [self readPermsDict]; @@ -150,4 +153,18 @@ - (NSSet *)getWritePermsFromOptions:(NSArray *)options { return writePermSet; } +- (HKObjectType *)getWritePermFromString:(NSString *)writePerm { + return [[self writePermsDict] objectForKey:writePerm]; +} +- (NSString *)getAuthorizationStatusString:(HKAuthorizationStatus)status { + switch (status) { + case HKAuthorizationStatusNotDetermined: + return @"NotDetermined"; + case HKAuthorizationStatusSharingDenied: + return @"SharingDenied"; + case HKAuthorizationStatusSharingAuthorized: + return @"SharingAuthorized"; + } +} + @end diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Utils.h b/RCTAppleHealthKit/RCTAppleHealthKit+Utils.h index 4501675e..7d00c782 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Utils.h +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Utils.h @@ -23,7 +23,7 @@ + (NSDate *)startDateFromOptions:(NSDictionary *)options; + (NSDate *)endDateFromOptions:(NSDictionary *)options; + (NSDate *)endDateFromOptionsDefaultNow:(NSDictionary *)options; -+ (HKUnit *)hkUnitFromOptions:(NSDictionary *)options; ++ (HKSampleType *)hkQuantityTypeFromString:(NSString *)type; + (HKUnit *)hkUnitFromOptions:(NSDictionary *)options key:(NSString *)key withDefault:(HKUnit *)defaultValue; + (NSUInteger)uintFromOptions:(NSDictionary *)options key:(NSString *)key withDefault:(NSUInteger)defaultValue; @@ -33,5 +33,6 @@ + (bool)boolFromOptions:(NSDictionary *)options key:(NSString *)key withDefault:(bool)defaultValue; + (NSMutableArray *)reverseNSMutableArray:(NSMutableArray *)array; ++ (NSString*) stringForHKWorkoutActivityType:(int) enumValue; @end diff --git a/RCTAppleHealthKit/RCTAppleHealthKit+Utils.m b/RCTAppleHealthKit/RCTAppleHealthKit+Utils.m index 7e0a834c..f3a2f225 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit+Utils.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit+Utils.m @@ -124,56 +124,20 @@ + (NSDate *)endDateFromOptionsDefaultNow:(NSDictionary *)options { return date; } -// ========== -// DEPRECATED -// ========== -+ (HKUnit *)hkUnitFromOptions:(NSDictionary *)options { - NSString *unitString = [options objectForKey:@"unit"]; - HKUnit *theUnit; - - if([unitString isEqualToString:@"gram"]){ - theUnit = [HKUnit gramUnit]; - } - if([unitString isEqualToString:@"pound"]){ - theUnit = [HKUnit poundUnit]; - } - if([unitString isEqualToString:@"meter"]){ - theUnit = [HKUnit meterUnit]; - } - if([unitString isEqualToString:@"mile"]){ - theUnit = [HKUnit mileUnit]; - } - if([unitString isEqualToString:@"inch"]){ - theUnit = [HKUnit inchUnit]; - } - if([unitString isEqualToString:@"foot"]){ - theUnit = [HKUnit footUnit]; - } - if([unitString isEqualToString:@"second"]){ - theUnit = [HKUnit secondUnit]; - } - if([unitString isEqualToString:@"minute"]){ - theUnit = [HKUnit minuteUnit]; - } - if([unitString isEqualToString:@"hour"]){ - theUnit = [HKUnit hourUnit]; - } - if([unitString isEqualToString:@"day"]){ - theUnit = [HKUnit dayUnit]; - } - if([unitString isEqualToString:@"joule"]){ - theUnit = [HKUnit jouleUnit]; - } - if([unitString isEqualToString:@"calorie"]){ - theUnit = [HKUnit calorieUnit]; - } - if([unitString isEqualToString:@"count"]){ - theUnit = [HKUnit countUnit]; - } - if([unitString isEqualToString:@"percent"]){ - theUnit = [HKUnit percentUnit]; - } - return theUnit; ++ (HKSampleType *)hkQuantityTypeFromString:(NSString *)type { + if ([type isEqual:@"Walking"]) { + return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; + } else if ([type isEqual:@"StairClimbing"]) { + return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierFlightsClimbed]; + } else if ([type isEqual:@"Running"]){ + return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning]; + } else if ([type isEqual:@"Cycling"]){ + return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceCycling]; + } else if ([type isEqual:@"Swimming"]){ + return [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceSwimming]; + } + // default [type isEqual:@"Workout"]) + return [HKObjectType workoutType]; } @@ -184,12 +148,21 @@ + (HKUnit *)hkUnitFromOptions:(NSDictionary *)options key:(NSString *)key withDe if([unitString isEqualToString:@"gram"]){ theUnit = [HKUnit gramUnit]; } + if([unitString isEqualToString:@"kg"]){ + theUnit = [HKUnit gramUnitWithMetricPrefix:HKMetricPrefixKilo]; + } + if([unitString isEqualToString:@"stone"]){ + theUnit = [HKUnit stoneUnit]; + } if([unitString isEqualToString:@"pound"]){ theUnit = [HKUnit poundUnit]; } if([unitString isEqualToString:@"meter"]){ theUnit = [HKUnit meterUnit]; } + if([unitString isEqualToString:@"cm"]){ + theUnit = [HKUnit meterUnitWithMetricPrefix:HKMetricPrefixCenti]; + } if([unitString isEqualToString:@"inch"]){ theUnit = [HKUnit inchUnit]; } @@ -321,4 +294,166 @@ + (NSMutableArray *)reverseNSMutableArray:(NSMutableArray *)array { return array; } ++ (NSString*)stringForHKWorkoutActivityType:(int) enumValue{ + switch( enumValue ){ + case HKWorkoutActivityTypeAmericanFootball: + return @"AmericanFootball"; + case HKWorkoutActivityTypeArchery: + return @"Archery"; + case HKWorkoutActivityTypeAustralianFootball: + return @"AustralianFootball"; + case HKWorkoutActivityTypeBadminton: + return @"Badminton"; + case HKWorkoutActivityTypeBaseball: + return @"Baseball"; + case HKWorkoutActivityTypeBasketball: + return @"Basketball"; + case HKWorkoutActivityTypeBowling: + return @"Bowling"; + case HKWorkoutActivityTypeBoxing: + return @"Boxing"; + case HKWorkoutActivityTypeClimbing: + return @"Climbing"; + case HKWorkoutActivityTypeCricket: + return @"Cricket"; + case HKWorkoutActivityTypeCrossTraining: + return @"CrossTraining"; + case HKWorkoutActivityTypeCurling: + return @"Curling"; + case HKWorkoutActivityTypeCycling: + return @"Cycling"; + case HKWorkoutActivityTypeDance: + return @"Dance"; + case HKWorkoutActivityTypeDanceInspiredTraining: + return @"DanceInspiredTraining"; + case HKWorkoutActivityTypeElliptical: + return @"Elliptical"; + case HKWorkoutActivityTypeEquestrianSports: + return @"EquestrianSports"; + case HKWorkoutActivityTypeFencing: + return @"Fencing"; + case HKWorkoutActivityTypeFishing: + return @"Fishing"; + case HKWorkoutActivityTypeFunctionalStrengthTraining: + return @"FunctionalStrengthTraining"; + case HKWorkoutActivityTypeGolf: + return @"Golf"; + case HKWorkoutActivityTypeGymnastics: + return @"Gymnastics"; + case HKWorkoutActivityTypeHandball: + return @"Handball"; + case HKWorkoutActivityTypeHiking: + return @"Hiking"; + case HKWorkoutActivityTypeHockey: + return @"Hockey"; + case HKWorkoutActivityTypeHunting: + return @"Hunting"; + case HKWorkoutActivityTypeLacrosse: + return @"Lacrosse"; + case HKWorkoutActivityTypeMartialArts: + return @"MartialArts"; + case HKWorkoutActivityTypeMindAndBody: + return @"MindAndBody"; + case HKWorkoutActivityTypeMixedMetabolicCardioTraining: + return @"MixedMetabolicCardioTraining"; + case HKWorkoutActivityTypePaddleSports: + return @"PaddleSports"; + case HKWorkoutActivityTypePlay: + return @"Play"; + case HKWorkoutActivityTypePreparationAndRecovery: + return @"PreparationAndRecovery"; + case HKWorkoutActivityTypeRacquetball: + return @"Racquetball"; + case HKWorkoutActivityTypeRowing: + return @"Rowing"; + case HKWorkoutActivityTypeRugby: + return @"Rugby"; + case HKWorkoutActivityTypeRunning: + return @"Running"; + case HKWorkoutActivityTypeSailing: + return @"Sailing"; + case HKWorkoutActivityTypeSkatingSports: + return @"SkatingSports"; + case HKWorkoutActivityTypeSnowSports: + return @"SnowSports"; + case HKWorkoutActivityTypeSoccer: + return @"Soccer"; + case HKWorkoutActivityTypeSoftball: + return @"Softball"; + case HKWorkoutActivityTypeSquash: + return @"Squash"; + case HKWorkoutActivityTypeStairClimbing: + return @"StairClimbing"; + case HKWorkoutActivityTypeSurfingSports: + return @"SurfingSports"; + case HKWorkoutActivityTypeSwimming: + return @"Swimming"; + case HKWorkoutActivityTypeTableTennis: + return @"TableTennis"; + case HKWorkoutActivityTypeTennis: + return @"Tennis"; + case HKWorkoutActivityTypeTrackAndField: + return @"TrackAndField"; + case HKWorkoutActivityTypeTraditionalStrengthTraining: + return @"TraditionalStrengthTraining"; + case HKWorkoutActivityTypeVolleyball: + return @"Volleyball"; + case HKWorkoutActivityTypeWalking: + return @"Walking"; + case HKWorkoutActivityTypeWaterFitness: + return @"WaterFitness"; + case HKWorkoutActivityTypeWaterPolo: + return @"WaterPolo"; + case HKWorkoutActivityTypeWaterSports: + return @"WaterSports"; + case HKWorkoutActivityTypeWrestling: + return @"Wrestling"; + case HKWorkoutActivityTypeYoga: + return @"Yoga"; + case HKWorkoutActivityTypeOther: + return @"Other"; + case HKWorkoutActivityTypeBarre: + return @"Barre"; + case HKWorkoutActivityTypeCoreTraining: + return @"CoreTraining"; + case HKWorkoutActivityTypeCrossCountrySkiing: + return @"CrossCountrySkiing"; + case HKWorkoutActivityTypeDownhillSkiing: + return @"DownhillSkiing"; + case HKWorkoutActivityTypeFlexibility: + return @"Flexibility"; + case HKWorkoutActivityTypeHighIntensityIntervalTraining: + return @"HighIntensityIntervalTraining"; + case HKWorkoutActivityTypeJumpRope: + return @"JumpRope"; + case HKWorkoutActivityTypeKickboxing: + return @"Kickboxing"; + case HKWorkoutActivityTypePilates: + return @"Pilates"; + case HKWorkoutActivityTypeSnowboarding: + return @"Snowboarding"; + case HKWorkoutActivityTypeStairs: + return @"Stairs"; + case HKWorkoutActivityTypeStepTraining: + return @"StepTraining"; + case HKWorkoutActivityTypeWheelchairWalkPace: + return @"WheelchairWalkPace"; + case HKWorkoutActivityTypeWheelchairRunPace: + return @"WheelchairRunPace"; + case HKWorkoutActivityTypeTaiChi: + return @"TaiChi"; + case HKWorkoutActivityTypeMixedCardio: + return @"MixedCardio"; + case HKWorkoutActivityTypeHandCycling: + return @"HandCycling"; + default:{ + NSException *e = [NSException + exceptionWithName:@"HKWorkoutActivityType InvalidValue" + reason:@"HKWorkoutActivityType can only have a value from the HKWorkoutActivityType enum" + userInfo:nil]; + @throw e; + } + } +} + @end diff --git a/RCTAppleHealthKit/RCTAppleHealthKit.h b/RCTAppleHealthKit/RCTAppleHealthKit.h index 27b8c036..6ccbe26b 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit.h +++ b/RCTAppleHealthKit/RCTAppleHealthKit.h @@ -16,9 +16,11 @@ @interface RCTAppleHealthKit : NSObject <RCTBridgeModule> @property (nonatomic) HKHealthStore *healthStore; +@property BOOL isSync; - (void)isHealthKitAvailable:(RCTResponseSenderBlock)callback; - (void)initializeHealthKit:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; +- (void)checkPermission:(NSString *)input callback:(RCTResponseSenderBlock)callback; - (void)getModuleInfo:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback; @end diff --git a/RCTAppleHealthKit/RCTAppleHealthKit.m b/RCTAppleHealthKit/RCTAppleHealthKit.m index 4e690645..14a1ffa2 100644 --- a/RCTAppleHealthKit/RCTAppleHealthKit.m +++ b/RCTAppleHealthKit/RCTAppleHealthKit.m @@ -3,7 +3,8 @@ // RCTAppleHealthKit // // Created by Greg Wilson on 2016-06-26. -// Copyright © 2016 Greg Wilson. All rights reserved. +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. // #import "RCTAppleHealthKit.h" @@ -23,6 +24,7 @@ #import <React/RCTEventDispatcher.h> @implementation RCTAppleHealthKit + @synthesize bridge = _bridge; RCT_EXPORT_MODULE(); @@ -97,11 +99,31 @@ @implementation RCTAppleHealthKit [self body_getLatestBodyFatPercentage:input callback:callback]; } +RCT_EXPORT_METHOD(getBodyFatPercentageSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback) +{ + [self body_getBodyFatPercentageSamples:input callback:callback]; +} + +RCT_EXPORT_METHOD(saveBodyFatPercentage:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback) +{ + [self body_saveBodyFatPercentage:input callback:callback]; +} + RCT_EXPORT_METHOD(getLatestLeanBodyMass:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback) { [self body_getLatestLeanBodyMass:input callback:callback]; } +RCT_EXPORT_METHOD(getLeanBodyMassSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback) +{ + [self body_getLeanBodyMassSamples:input callback:callback]; +} + +RCT_EXPORT_METHOD(saveLeanBodyMass:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback) +{ + [self body_saveLeanBodyMass:input callback:callback]; +} + RCT_EXPORT_METHOD(getStepCount:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback) { [self fitness_getStepCountOnDay:input callback:callback]; @@ -117,6 +139,16 @@ @implementation RCTAppleHealthKit [self fitness_getDailyStepSamples:input callback:callback]; } +RCT_EXPORT_METHOD(getSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback) +{ + [self fitness_getSamples:input callback:callback]; +} + +RCT_EXPORT_METHOD(setObserver:(NSDictionary *)input) +{ + [self fitness_setObserver:input]; +} + RCT_EXPORT_METHOD(saveSteps:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback) { [self fitness_saveSteps:input callback:callback]; @@ -132,6 +164,11 @@ @implementation RCTAppleHealthKit [self fitness_getDailyDistanceWalkingRunningSamples:input callback:callback]; } +RCT_EXPORT_METHOD(getDailyDistanceSwimmingSamples:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback) +{ + [self fitness_getDailyDistanceSwimmingSamples:input callback:callback]; +} + RCT_EXPORT_METHOD(getDistanceCycling:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback) { [self fitness_getDistanceCyclingOnDay:input callback:callback]; @@ -258,9 +295,7 @@ - (void)initializeHealthKit:(NSDictionary *)input callback:(RCTResponseSenderBlo [self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) { if (!success) { - NSString *errMsg = [NSString stringWithFormat:@"Error with HealthKit authorization: %@", error]; - NSLog(errMsg); - callback(@[RCTMakeError(errMsg, nil, nil)]); + callback(@[RCTJSErrorFromNSError(error)]); return; } else { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -273,6 +308,28 @@ - (void)initializeHealthKit:(NSDictionary *)input callback:(RCTResponseSenderBlo } } +RCT_EXPORT_METHOD(authorizationStatusForType:(NSString *)type + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject +{ + if (self.healthStore == nil) { + self.healthStore = [[HKHealthStore alloc] init]; + } + + if ([HKHealthStore isHealthDataAvailable]) { + HKObjectType *objectType = [self getWritePermFromString:type]; + if (objectType == nil) { + reject(@"unknown write permission", nil, nil); + return; + } + + NSString *status = [self getAuthorizationStatusString:[self.healthStore authorizationStatusForType:objectType]]; + resolve(status); + } else { + reject(@"HealthKit data is not available", nil, nil); + } +}) + - (void)getModuleInfo:(NSDictionary *)input callback:(RCTResponseSenderBlock)callback { NSDictionary *info = @{ diff --git a/README.md b/README.md index 0a385f8a..6ad3ead0 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,12 @@ AppleHealthKit.initHealthKit(options: Object, (err: string, results: Object) => ``` ## Changelog +0.6.5v +- Enable fetching basal energy [#23](https://github.com/terrillo/rn-apple-healthkit/pull/23) +- remove checkPermission functions in order to use from PR [#69](https://github.com/terrillo/rn-apple-healthkit/pull/69) +- Added correct link to permissions. [#73](https://github.com/terrillo/rn-apple-healthkit/pull/73) +- Add unified way to get workouts + convert Activity Types to name + isTracked flag [#25](https://github.com/terrillo/rn-apple-healthkit/pull/25) + 0.6.4v - Basal energy ([#23](https://github.com/terrillo/rn-apple-healthkit/pull/23)) - Fixed issues with saving weight in the past @@ -95,45 +101,53 @@ AppleHealthKit.initHealthKit(options: Object, (err: string, results: Object) => ## Wiki * [Installation](/docs/Install) * [Documentation](#documentation) - * [Permissions](#permissions) + * [Permissions](#supported-apple-permissions) * [Units](#units) * Base Methods * [isAvailable](/docs/isAvailable().md) * [initHealthKit](/docs/initHealthKit().md) + * [authorizationStatusForType](/docs/authorizationStatusForType().md) * Realtime Methods * [initStepCountObserver](/docs/initStepCountObserver().md) + * [setObserver](/docs/setObserver().md) * Read Methods * [getActiveEnergyBurned](/docs/getActiveEnergyBurned().md) * [getBasalEnergyBurned](/docs/getBasalEnergyBurned().md) * [getBiologicalSex](/docs/getBiologicalSex().md) - * [getBloodGlucoseSamples](/docs/getbloodglucosesamples().md) - * [getBloodPressureSamples](/docs/getbloodpressuresamples().md) - * [getBodyTemperatureSamples](/docs/getbodytemperaturesamples().md) - * [getDailyDistanceCyclingSamples]() + * [getBloodGlucoseSamples](/docs/getBloodglucoseSamples().md) + * [getBloodPressureSamples](/docs/getBloodPressureSamples().md) + * [getBodyTemperatureSamples](/docs/getBodyTemperatureSamples().md) + * [getDailyDistanceCyclingSamples](/docs/getDailyDistanceCyclingSamples().md) * [getDailyDistanceWalkingRunningSamples](/docs/getDailyDistanceWalkingRunningSamples().md) * [getDailyFlightsClimbedSamples](/docs/getDailyFlightsClimbedSamples().md) * [getDailyStepCountSamples](/docs/getDailyStepCountSamples().md) * [getDateOfBirth](/docs/getDateOfBirth().md) - * [getDistanceCycling](/docs/getdistancecycling().md) + * [getDistanceCycling](/docs/getDistanceCycling().md) + * [getDistanceSwimming](/docs/getDistanceSwimming().md) * [getDistanceWalkingRunning](/docs/getDistanceWalkingRunning().md) - * [getFlightsClimbed](/docs/getflightsclimbed().md) - * [getHeartRateSamples](/docs/getheartratesamples().md) - * [getHeightSamples](/docs/getheightsamples().md) - * [getLatestBmi](/docs/getlatestbmi().md) - * [getLatestBodyFatPercentage](/docs/getlatestbodyfatpercentage().md) - * [getLatestHeight](/docs/getlatestheight().md) - * [getLatestLeanBodyMass](/docs/getlatestleanbodymass().md) - * [getLatestWeight](/docs/getlatestweight().md) - * [getRespiratoryRateSamples](/docs/getrespiratoryratesamples().md) - * [getSleepSamples](/docs/getsleepsamples().md) + * [getFlightsClimbed](/docs/getFlightsClimbed().md) + * [getHeartRateSamples](/docs/getHeartRateSamples().md) + * [getHeightSamples](/docs/getHeightSamples().md) + * [getLatestBmi](/docs/getLatestBmi().md) + * [getLatestBodyFatPercentage](/docs/getLatestBodyFatPercentage().md) + * [getBodyFatPercentageSamples](/docs/getBodyFatPercentageSamples().md) + * [getLatestHeight](/docs/getLatestHeight().md) + * [getLatestLeanBodyMass](/docs/getLatestLeanBodyMass().md) + * [getLeanBodyMassSamples](/docs/getLeanBodyMassSamples().md) + * [getLatestWeight](/docs/getLatestWeight().md) + * [getRespiratoryRateSamples](/docs/getRespiratoryRateSamples().md) + * [getSleepSamples](/docs/getSleepSamples().md) * [getStepCount](/docs/getStepCount().md) - * [getWeightSamples](/docs/getweightsamples().md) + * [getWeightSamples](/docs/getWeightSamples().md) + * [getSamples](docs/getSamples().md) * Write Methods - * [saveBmi](/docs/savebmi().md) - * [saveHeight](/docs/saveheight().md) + * [saveBmi](/docs/saveBmi().md) + * [saveHeight](/docs/saveHeight().md) * [saveMindfulSession](/docs/saveMindfulSession().md) - * [saveWeight](/docs/saveweight().md) + * [saveWeight](/docs/saveWeight().md) * [saveSteps](/docs/saveSteps().md) + * [saveBodyFatPercentage](/docs/saveBodyFatPercentage().md) + * [saveLeanBodyMass](/docs/saveLeanBodyMass().md) * [References](#references) ## Supported Apple Permissions @@ -163,6 +177,7 @@ The available Healthkit permissions to use with `initHealthKit` | StepCount | [HKQuantityTypeIdentifierStepCount](https://developer.apple.com/reference/Healthkit/hkquantitytypeidentifierstepcount?language=objc) | ✓ | ✓ | | Steps | [HKQuantityTypeIdentifierSteps](https://developer.apple.com/reference/Healthkit/hkquantitytypeidentifiersteps?language=objc) | ✓ | ✓ | | Weight | [HKQuantityTypeIdentifierBodyMass](https://developer.apple.com/reference/Healthkit/hkquantitytypeidentifierbodymass?language=objc) | ✓ | ✓ | +| BodyFatPercentage | [HKQuantityTypeIdentifierBodyFatPercentage](https://developer.apple.com/reference/Healthkit/hkquantitytypeidentifierbodyfatpercentage?language=objc) | ✓ | ✓ | These permissions are exported as constants of the `rn-apple-healthkit` module. diff --git a/docs/authorizationStatusForType().md b/docs/authorizationStatusForType().md new file mode 100644 index 00000000..8687b89b --- /dev/null +++ b/docs/authorizationStatusForType().md @@ -0,0 +1,16 @@ +Check the authorization status for sharing (writing) the specified data type. + +Status will be one of `"NotDetermined"`, `"SharingDenied"`, `"SharingAuthorized"`. + +```javascript +try { +const status = await AppleHealthKit.authorizationStatusForType("StepCount") +if (status) { + console.log("status is", status) +} +} catch (error) { + console.warn(error) +} +``` + +There is no way to check authorization status for read permission, [see this](https://developer.apple.com/documentation/healthkit/hkhealthstore/1614154-authorizationstatusfortype?language=objc). diff --git a/docs/getBloodGlucoseSamples().md b/docs/getBloodGlucoseSamples().md index 5b2f86f3..a89ebe8f 100644 --- a/docs/getBloodGlucoseSamples().md +++ b/docs/getBloodGlucoseSamples().md @@ -10,7 +10,7 @@ let options = { ``` Available units are: `'mmolPerL'`, `'mgPerdL'`. -The callback function will be called with a `samples` array containing objects with *value*, *startDate*, and *endDate* fields +The callback function will be called with a `samples` array containing objects with *value*, *sourceId*, *sourceName*, *startDate*, and *endDate* fields ```javascript AppleHealthKit.getBloodGlucoseSamples(options, (err: Object, results: Array<Object>) => { diff --git a/docs/getBodyFatPercentageSamples().md b/docs/getBodyFatPercentageSamples().md new file mode 100644 index 00000000..8e3e8722 --- /dev/null +++ b/docs/getBodyFatPercentageSamples().md @@ -0,0 +1,27 @@ +Query for body fat percentage samples. the options object is used to setup a query to retrieve relevant samples. + +```javascript +let options = { + startDate: (new Date(2016,4,27)).toISOString(), // required + endDate: (new Date()).toISOString(), // optional; default now + ascending: false, // optional; default false + limit:10, // optional; default no limit +}; +``` + +```javascript +AppleHealthKit.getBodyFatPercentageSamples(options, (err: Object, results: Array<Object>) => { + if (err) { + return; + } + console.log(results) +}); +``` + +```javascript +[ + { value: 16.5, startDate: '2016-07-09T00:00:00.000-0400', endDate: '2016-07-10T00:00:00.000-0400' }, + { value: 16.1, startDate: '2016-07-08T00:00:00.000-0400', endDate: '2016-07-09T00:00:00.000-0400' }, + { value: 15.9, startDate: '2016-07-07T00:00:00.000-0400', endDate: '2016-07-08T00:00:00.000-0400' }, +] +``` diff --git a/docs/getDailyDistanceSwimmingSamples().md b/docs/getDailyDistanceSwimmingSamples().md new file mode 100644 index 00000000..22045149 --- /dev/null +++ b/docs/getDailyDistanceSwimmingSamples().md @@ -0,0 +1,19 @@ +```javascript +let options = { + startDate: (new Date(2016,4,27)).toISOString(), // required + endDate: (new Date()).toISOString(), // optional; default now + ascending: false, // optional; default false + limit:10, // optional; default no limit + period: 60, // time interval in minutes optional: default 60 + includeManuallyAdded: false. // optional: default false +}; +``` + +```javascript +AppleHealthKit.getDailyDistanceSwimmingSamples(options, (err: Object, results: Array<Object>) => { + if (err) { + return; + } + console.log(results) +}); +``` diff --git a/docs/getLeanBodyMassSamples().md b/docs/getLeanBodyMassSamples().md new file mode 100644 index 00000000..93a0af87 --- /dev/null +++ b/docs/getLeanBodyMassSamples().md @@ -0,0 +1,28 @@ +Query for lean body mass samples. the options object is used to setup a query to retrieve relevant samples. + +```javascript +let options = { + unit: 'pound', // optional; default 'pound' + startDate: (new Date(2016,4,27)).toISOString(), // required + endDate: (new Date()).toISOString(), // optional; default now + ascending: false, // optional; default false + limit:10, // optional; default no limit +}; +``` + +```javascript +AppleHealthKit.getLeanBodyMassSamples(options, (err: Object, results: Array<Object>) => { + if (err) { + return; + } + console.log(results) +}); +``` + +```javascript +[ + { value: 160, startDate: '2016-07-09T00:00:00.000-0400', endDate: '2016-07-10T00:00:00.000-0400' }, + { value: 161, startDate: '2016-07-08T00:00:00.000-0400', endDate: '2016-07-09T00:00:00.000-0400' }, + { value: 165, startDate: '2016-07-07T00:00:00.000-0400', endDate: '2016-07-08T00:00:00.000-0400' }, +] +``` diff --git a/docs/getSamples().md b/docs/getSamples().md new file mode 100644 index 00000000..a4c6cc68 --- /dev/null +++ b/docs/getSamples().md @@ -0,0 +1,52 @@ +Query to get all activities of given type with extended information about it. + +```javascript 1.7 +let options = { + startDate: (new Date(2016,4,27)).toISOString(), + endDate: (new Date()).toISOString(), + type: 'Walking', // one of: ['Walking', 'StairClimbing', 'Running', 'Cycling', 'Workout'] +}; +``` + +The callback function will be called with a `samples` array containing objects with *value*, *startDate*, and *endDate* fields + +```javascript 1.7 +AppleHealthKit.getSamples(options, (err: Object, results: Array<Object>) => { + if (err) { + return; + } + console.log(results) +}); +``` + +Resulting object has different fields for different types. +In case of workout: +``` +{ + activityId: Number, // [NSNumber numberWithInt:[sample workoutActivityType]] + activityName: Number, // [RCTAppleHealthKit stringForHKWorkoutActivityType:[sample workoutActivityType]] + calories: Number, // [[sample totalEnergyBurned] doubleValueForUnit:[HKUnit kilocalorieUnit]] + tracked: Boolean, // [[sample metadata][HKMetadataKeyWasUserEntered] intValue] !== 1 + sourceName: String, // [[[sample sourceRevision] source] name] + sourceId: String, // [[[sample sourceRevision] source] bundleIdentifier] + device: String, // [[sample sourceRevision] productType] or 'iPhone' + distance: Number, // [[sample totalDistance] doubleValueForUnit:[HKUnit mileUnit]] + start: String, // [RCTAppleHealthKit buildISO8601StringFromDate:sample.startDate]; + end: String, // [RCTAppleHealthKit buildISO8601StringFromDate:sample.endDate]; +} +``` +for other types: +``` +{ + tracked: Boolean, // [[sample metadata][HKMetadataKeyWasUserEntered] intValue] !== 1 + sourceName: String, // [[[sample sourceRevision] source] name] + sourceId: String, // [[[sample sourceRevision] source] bundleIdentifier] + device: String, // [[sample sourceRevision] productType] or 'iPhone' + start: String, // [RCTAppleHealthKit buildISO8601StringFromDate:sample.startDate]; + end: String, // [RCTAppleHealthKit buildISO8601StringFromDate:sample.endDate]; + + //based on required type, one of the following will be present. + distance: Number, // [[sample totalDistance] doubleValueForUnit:[HKUnit mileUnit]] + calories: Number, // [[sample totalEnergyBurned] doubleValueForUnit:[HKUnit kilocalorieUnit]] +} +``` diff --git a/docs/saveBodyFatPercentage().md b/docs/saveBodyFatPercentage().md new file mode 100644 index 00000000..e5f2bc39 --- /dev/null +++ b/docs/saveBodyFatPercentage().md @@ -0,0 +1,17 @@ +save a percentage body fat value to Healthkit + +`saveBodyFatPercentage` accepts an options object containing a percent value: +```javascript +let options = { + value: 16.7 // 16.7% +} +``` + +```javascript +AppleHealthKit.saveBodyFatPercentage(options: Object, (err: Object, results: Object) => { + if (err) { + return; + } + // body fat percentage successfully saved +}); +``` diff --git a/docs/saveLeanBodyMass().md b/docs/saveLeanBodyMass().md new file mode 100644 index 00000000..44b7a317 --- /dev/null +++ b/docs/saveLeanBodyMass().md @@ -0,0 +1,18 @@ +save a numeric lean body mass value to Healthkit + +`saveLeanBodyMass` accepts an options object containing a numeric weight value: +```javascript +let options = { + value: 155.6 // lbs +} +``` + +```javascript +AppleHealthKit.saveLeanBodyMass(options: Object, (err: Object, results: Object) => { + if (err) { + console.log("error saving lean body mass to Healthkit: ", err); + return; + } + // Done +}); +``` diff --git a/docs/setObserver().md b/docs/setObserver().md new file mode 100644 index 00000000..8f551613 --- /dev/null +++ b/docs/setObserver().md @@ -0,0 +1,15 @@ +Will listen for any updates in a given type data in healthKit and call app. + +type - one of the `['Walking', 'StairClimbing', 'Running', 'Cycling', 'Workout']` +```javascript 1.8 +import { NativeAppEventEmitter } from 'react-native'; +//...// +AppleHealthKit.setObserver({ type: 'Walking' }); +NativeAppEventEmitter.addListener( + 'observer', + callback + ); +``` + +So, callback would be call when new data of given type appears. When it happens, in order to get new info +need to call getSamples() function with proper arguments. diff --git a/package.json b/package.json index 86205ab3..40bf39af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rn-apple-healthkit", - "version": "0.6.4", + "version": "0.6.5", "description": "A React Native package for interacting with Apple HealthKit", "main": "index.js", "types": "index.d.ts",