@flomentumsolutions/capacitor-health-extended 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -334,7 +334,7 @@ Query aggregated data
|
|
|
334
334
|
**Returns:** <code>Promise<<a href="#queryaggregatedresponse">QueryAggregatedResponse</a>></code>
|
|
335
335
|
|
|
336
336
|
- Blood-pressure aggregates return the systolic average in `value` plus `systolic`, `diastolic`, and `unit`.
|
|
337
|
-
-
|
|
337
|
+
- `total-calories` is derived as active + basal energy on both iOS and Android for latest samples, aggregated queries, and workouts. We fall back to the platform's total‑calories metric (or active calories) when basal data isn't available or permission is missing. Request both `READ_ACTIVE_CALORIES` and `READ_BASAL_CALORIES` for full totals.
|
|
338
338
|
- Weight/height aggregation returns the latest sample per day (no averaging).
|
|
339
339
|
- Android aggregation currently supports daily buckets; unsupported buckets will be rejected.
|
|
340
340
|
- Android `distance-cycling` aggregates distance recorded during biking exercise sessions (requires distance + workouts permissions).
|
|
@@ -374,6 +374,8 @@ Query latest sample for a specific data type
|
|
|
374
374
|
|
|
375
375
|
**Returns:** <code>Promise<<a href="#querylatestsampleresponse">QueryLatestSampleResponse</a>></code>
|
|
376
376
|
|
|
377
|
+
- Latest sleep sample returns the most recent complete sleep session (asleep states only) from the last ~36 hours; if a longer overnight session exists, shorter naps are ignored.
|
|
378
|
+
|
|
377
379
|
--------------------
|
|
378
380
|
|
|
379
381
|
|
|
@@ -804,7 +804,72 @@ class HealthPlugin : Plugin() {
|
|
|
804
804
|
}
|
|
805
805
|
}
|
|
806
806
|
|
|
807
|
+
private suspend fun sumActiveAndBasalCalories(
|
|
808
|
+
timeRange: TimeRangeFilter,
|
|
809
|
+
includeTotalMetricFallback: Boolean = false
|
|
810
|
+
): Double? {
|
|
811
|
+
val metrics = mutableSetOf<AggregateMetric<*>>()
|
|
812
|
+
val includeActive = hasPermission(CapHealthPermission.READ_ACTIVE_CALORIES)
|
|
813
|
+
val includeBasal = hasPermission(CapHealthPermission.READ_BASAL_CALORIES)
|
|
814
|
+
val includeTotal = includeTotalMetricFallback && hasPermission(CapHealthPermission.READ_TOTAL_CALORIES)
|
|
815
|
+
|
|
816
|
+
if (includeActive) metrics.add(ActiveCaloriesBurnedRecord.ACTIVE_CALORIES_TOTAL)
|
|
817
|
+
if (includeBasal) metrics.add(BasalMetabolicRateRecord.BASAL_CALORIES_TOTAL)
|
|
818
|
+
if (includeTotal) metrics.add(TotalCaloriesBurnedRecord.ENERGY_TOTAL)
|
|
819
|
+
|
|
820
|
+
if (metrics.isEmpty()) {
|
|
821
|
+
return null
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
val aggregation = healthConnectClient.aggregate(
|
|
825
|
+
AggregateRequest(
|
|
826
|
+
metrics = metrics,
|
|
827
|
+
timeRangeFilter = timeRange,
|
|
828
|
+
dataOriginFilter = emptySet()
|
|
829
|
+
)
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
val active = if (includeActive) {
|
|
833
|
+
aggregation[ActiveCaloriesBurnedRecord.ACTIVE_CALORIES_TOTAL]?.inKilocalories
|
|
834
|
+
} else null
|
|
835
|
+
val basal = if (includeBasal) {
|
|
836
|
+
aggregation[BasalMetabolicRateRecord.BASAL_CALORIES_TOTAL]?.inKilocalories
|
|
837
|
+
} else null
|
|
838
|
+
val total = if (includeTotal) {
|
|
839
|
+
aggregation[TotalCaloriesBurnedRecord.ENERGY_TOTAL]?.inKilocalories
|
|
840
|
+
} else null
|
|
841
|
+
|
|
842
|
+
return when {
|
|
843
|
+
active != null || basal != null -> (active ?: 0.0) + (basal ?: 0.0)
|
|
844
|
+
includeTotal -> total
|
|
845
|
+
else -> null
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
807
849
|
private suspend fun readLatestTotalCalories(): JSObject {
|
|
850
|
+
val zone = ZoneId.systemDefault()
|
|
851
|
+
val now = LocalDateTime.now(zone)
|
|
852
|
+
|
|
853
|
+
val derivedTotal = try {
|
|
854
|
+
sumActiveAndBasalCalories(
|
|
855
|
+
TimeRangeFilter.between(
|
|
856
|
+
now.toLocalDate().atStartOfDay(),
|
|
857
|
+
now
|
|
858
|
+
)
|
|
859
|
+
)
|
|
860
|
+
} catch (e: Exception) {
|
|
861
|
+
Log.w(tag, "readLatestTotalCalories: Failed to derive active+basal calories, falling back to total metric", e)
|
|
862
|
+
null
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (derivedTotal != null) {
|
|
866
|
+
return JSObject().apply {
|
|
867
|
+
put("value", derivedTotal)
|
|
868
|
+
put("timestamp", now.atZone(zone).toInstant().toEpochMilli())
|
|
869
|
+
put("unit", "kcal")
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
808
873
|
if (!hasPermission(CapHealthPermission.READ_TOTAL_CALORIES)) {
|
|
809
874
|
throw Exception("Permission for total calories not granted")
|
|
810
875
|
}
|
|
@@ -952,13 +1017,17 @@ class HealthPlugin : Plugin() {
|
|
|
952
1017
|
}
|
|
953
1018
|
|
|
954
1019
|
else -> {
|
|
955
|
-
val
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1020
|
+
val aggregated = if (dataType == "total-calories") {
|
|
1021
|
+
aggregateTotalCalories(timeRange, period)
|
|
1022
|
+
} else {
|
|
1023
|
+
val metricAndMapper = getMetricAndMapper(dataType)
|
|
1024
|
+
queryAggregatedMetric(
|
|
1025
|
+
metricAndMapper,
|
|
1026
|
+
timeRange,
|
|
1027
|
+
period
|
|
1028
|
+
)
|
|
1029
|
+
}
|
|
1030
|
+
aggregated.forEach { aggregatedList.put(it.toJs()) }
|
|
962
1031
|
}
|
|
963
1032
|
}
|
|
964
1033
|
|
|
@@ -1068,6 +1137,51 @@ class HealthPlugin : Plugin() {
|
|
|
1068
1137
|
|
|
1069
1138
|
}
|
|
1070
1139
|
|
|
1140
|
+
private suspend fun aggregateTotalCalories(
|
|
1141
|
+
timeRange: TimeRangeFilter,
|
|
1142
|
+
period: Period
|
|
1143
|
+
): List<AggregatedSample> {
|
|
1144
|
+
val includeActive = hasPermission(CapHealthPermission.READ_ACTIVE_CALORIES)
|
|
1145
|
+
val includeBasal = hasPermission(CapHealthPermission.READ_BASAL_CALORIES)
|
|
1146
|
+
val includeTotal = hasPermission(CapHealthPermission.READ_TOTAL_CALORIES)
|
|
1147
|
+
|
|
1148
|
+
val metrics = mutableSetOf<AggregateMetric<*>>()
|
|
1149
|
+
if (includeActive) metrics.add(ActiveCaloriesBurnedRecord.ACTIVE_CALORIES_TOTAL)
|
|
1150
|
+
if (includeBasal) metrics.add(BasalMetabolicRateRecord.BASAL_CALORIES_TOTAL)
|
|
1151
|
+
if (includeTotal) metrics.add(TotalCaloriesBurnedRecord.ENERGY_TOTAL)
|
|
1152
|
+
|
|
1153
|
+
if (metrics.isEmpty()) {
|
|
1154
|
+
return emptyList()
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
val response: List<AggregationResultGroupedByPeriod> = healthConnectClient.aggregateGroupByPeriod(
|
|
1158
|
+
AggregateGroupByPeriodRequest(
|
|
1159
|
+
metrics = metrics,
|
|
1160
|
+
timeRangeFilter = timeRange,
|
|
1161
|
+
timeRangeSlicer = period
|
|
1162
|
+
)
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
return response.map {
|
|
1166
|
+
val active = if (includeActive) {
|
|
1167
|
+
it.result[ActiveCaloriesBurnedRecord.ACTIVE_CALORIES_TOTAL]?.inKilocalories
|
|
1168
|
+
} else null
|
|
1169
|
+
val basal = if (includeBasal) {
|
|
1170
|
+
it.result[BasalMetabolicRateRecord.BASAL_CALORIES_TOTAL]?.inKilocalories
|
|
1171
|
+
} else null
|
|
1172
|
+
val total = if (includeTotal) {
|
|
1173
|
+
it.result[TotalCaloriesBurnedRecord.ENERGY_TOTAL]?.inKilocalories
|
|
1174
|
+
} else null
|
|
1175
|
+
|
|
1176
|
+
val value = when {
|
|
1177
|
+
active != null || basal != null -> (active ?: 0.0) + (basal ?: 0.0)
|
|
1178
|
+
else -> total ?: 0.0
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
AggregatedSample(it.startTime, it.endTime, value)
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1071
1185
|
private suspend fun aggregateHrvByPeriod(
|
|
1072
1186
|
timeRange: TimeRangeFilter,
|
|
1073
1187
|
period: Period
|
|
@@ -1409,9 +1523,12 @@ class HealthPlugin : Plugin() {
|
|
|
1409
1523
|
addWorkoutMetric(workout, workoutObject, getMetricAndMapper("steps"))
|
|
1410
1524
|
}
|
|
1411
1525
|
|
|
1412
|
-
val
|
|
1413
|
-
if(!
|
|
1414
|
-
addWorkoutMetric(workout, workoutObject, getMetricAndMapper("
|
|
1526
|
+
val derivedTotalAdded = addWorkoutTotalCalories(workout, workoutObject)
|
|
1527
|
+
if (!derivedTotalAdded) {
|
|
1528
|
+
val readTotalCaloriesResult = addWorkoutMetric(workout, workoutObject, getMetricAndMapper("total-calories"))
|
|
1529
|
+
if(!readTotalCaloriesResult) {
|
|
1530
|
+
addWorkoutMetric(workout, workoutObject, getMetricAndMapper("active-calories"))
|
|
1531
|
+
}
|
|
1415
1532
|
}
|
|
1416
1533
|
|
|
1417
1534
|
addWorkoutMetric(workout, workoutObject, getMetricAndMapper("distance"))
|
|
@@ -1451,6 +1568,27 @@ class HealthPlugin : Plugin() {
|
|
|
1451
1568
|
}
|
|
1452
1569
|
}
|
|
1453
1570
|
|
|
1571
|
+
private suspend fun addWorkoutTotalCalories(
|
|
1572
|
+
workout: ExerciseSessionRecord,
|
|
1573
|
+
jsWorkout: JSObject
|
|
1574
|
+
): Boolean {
|
|
1575
|
+
return try {
|
|
1576
|
+
val totalCalories = sumActiveAndBasalCalories(
|
|
1577
|
+
TimeRangeFilter.between(workout.startTime, workout.endTime),
|
|
1578
|
+
includeTotalMetricFallback = true
|
|
1579
|
+
)
|
|
1580
|
+
if (totalCalories != null) {
|
|
1581
|
+
jsWorkout.put("calories", totalCalories)
|
|
1582
|
+
true
|
|
1583
|
+
} else {
|
|
1584
|
+
false
|
|
1585
|
+
}
|
|
1586
|
+
} catch (e: Exception) {
|
|
1587
|
+
Log.e(tag, "addWorkoutTotalCalories: Failed to derive calories", e)
|
|
1588
|
+
false
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1454
1592
|
private suspend fun addWorkoutMetric(
|
|
1455
1593
|
workout: ExerciseSessionRecord,
|
|
1456
1594
|
jsWorkout: JSObject,
|
|
@@ -161,26 +161,85 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
161
161
|
call.reject("Sleep type not available")
|
|
162
162
|
return
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
let
|
|
166
|
-
let
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
164
|
+
|
|
165
|
+
let endDate = Date()
|
|
166
|
+
let startDate = Calendar.current.date(byAdding: .hour, value: -36, to: endDate) ?? endDate.addingTimeInterval(-36 * 3600)
|
|
167
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictEndDate)
|
|
168
|
+
|
|
169
|
+
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
170
|
+
if let error = error {
|
|
171
|
+
call.reject("Error fetching latest sleep sample", "NO_SAMPLE", error)
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
guard let categorySamples = samples as? [HKCategorySample], !categorySamples.isEmpty else {
|
|
175
|
+
call.reject("No sleep sample found", "NO_SAMPLE")
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let asleepValues: Set<Int> = {
|
|
180
|
+
var values: [Int] = [HKCategoryValueSleepAnalysis.asleepUnspecified.rawValue]
|
|
181
|
+
if #available(iOS 16.0, *) {
|
|
182
|
+
values.append(contentsOf: [
|
|
183
|
+
HKCategoryValueSleepAnalysis.asleepCore.rawValue,
|
|
184
|
+
HKCategoryValueSleepAnalysis.asleepDeep.rawValue,
|
|
185
|
+
HKCategoryValueSleepAnalysis.asleepREM.rawValue
|
|
186
|
+
])
|
|
187
|
+
}
|
|
188
|
+
return Set(values)
|
|
189
|
+
}()
|
|
190
|
+
|
|
191
|
+
func isAsleep(_ value: Int) -> Bool {
|
|
192
|
+
if asleepValues.contains(value) {
|
|
193
|
+
return true
|
|
174
194
|
}
|
|
195
|
+
// Fallback: treat any non in-bed/awake as asleep
|
|
196
|
+
return value != HKCategoryValueSleepAnalysis.inBed.rawValue &&
|
|
197
|
+
value != HKCategoryValueSleepAnalysis.awake.rawValue
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let asleepSamples = categorySamples
|
|
201
|
+
.filter { isAsleep($0.value) }
|
|
202
|
+
.sorted { $0.startDate < $1.startDate }
|
|
203
|
+
|
|
204
|
+
guard !asleepSamples.isEmpty else {
|
|
205
|
+
call.reject("No sleep sample found", "NO_SAMPLE")
|
|
175
206
|
return
|
|
176
207
|
}
|
|
177
|
-
|
|
208
|
+
|
|
209
|
+
let maxGap: TimeInterval = 90 * 60 // 90 minutes separates sessions
|
|
210
|
+
var sessions: [(start: Date, end: Date, duration: TimeInterval)] = []
|
|
211
|
+
var currentStart: Date?
|
|
212
|
+
var currentEnd: Date?
|
|
213
|
+
var currentDuration: TimeInterval = 0
|
|
214
|
+
|
|
215
|
+
for sample in asleepSamples {
|
|
216
|
+
if let lastEnd = currentEnd, sample.startDate.timeIntervalSince(lastEnd) > maxGap {
|
|
217
|
+
sessions.append((start: currentStart ?? lastEnd, end: lastEnd, duration: currentDuration))
|
|
218
|
+
currentStart = nil
|
|
219
|
+
currentEnd = nil
|
|
220
|
+
currentDuration = 0
|
|
221
|
+
}
|
|
222
|
+
if currentStart == nil { currentStart = sample.startDate }
|
|
223
|
+
currentEnd = sample.endDate
|
|
224
|
+
currentDuration += sample.endDate.timeIntervalSince(sample.startDate)
|
|
225
|
+
}
|
|
226
|
+
if let start = currentStart, let end = currentEnd {
|
|
227
|
+
sessions.append((start: start, end: end, duration: currentDuration))
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
guard !sessions.isEmpty else {
|
|
231
|
+
call.reject("No sleep sample found", "NO_SAMPLE")
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let minSessionDuration: TimeInterval = 3 * 3600 // prefer sessions 3h+
|
|
236
|
+
let preferredSession = sessions.reversed().first { $0.duration >= minSessionDuration } ?? sessions.last!
|
|
237
|
+
|
|
178
238
|
call.resolve([
|
|
179
|
-
"value":
|
|
180
|
-
"timestamp":
|
|
181
|
-
"endTimestamp":
|
|
182
|
-
"unit": "min"
|
|
183
|
-
"metadata": ["state": sleepSample.value]
|
|
239
|
+
"value": preferredSession.duration / 60,
|
|
240
|
+
"timestamp": preferredSession.start.timeIntervalSince1970 * 1000,
|
|
241
|
+
"endTimestamp": preferredSession.end.timeIntervalSince1970 * 1000,
|
|
242
|
+
"unit": "min"
|
|
184
243
|
])
|
|
185
244
|
}
|
|
186
245
|
healthStore.execute(query)
|
|
@@ -725,8 +784,9 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
725
784
|
let calendar = Calendar.current
|
|
726
785
|
if let categorySamples = samples as? [HKCategorySample], error == nil {
|
|
727
786
|
for sample in categorySamples {
|
|
728
|
-
// Ignore in-bed samples; we care about actual sleep
|
|
787
|
+
// Ignore in-bed and awake samples; we care about actual sleep
|
|
729
788
|
if sample.value == HKCategoryValueSleepAnalysis.inBed.rawValue { continue }
|
|
789
|
+
if sample.value == HKCategoryValueSleepAnalysis.awake.rawValue { continue }
|
|
730
790
|
let startOfDay = calendar.startOfDay(for: sample.startDate)
|
|
731
791
|
let duration = sample.endDate.timeIntervalSince(sample.startDate)
|
|
732
792
|
dailyDurations[startOfDay, default: 0] += duration
|
|
@@ -821,52 +881,55 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
821
881
|
healthStore.execute(query)
|
|
822
882
|
}
|
|
823
883
|
|
|
884
|
+
private func sumEnergy(
|
|
885
|
+
_ identifier: HKQuantityTypeIdentifier,
|
|
886
|
+
startDate: Date,
|
|
887
|
+
endDate: Date,
|
|
888
|
+
completion: @escaping (Double?, Error?) -> Void
|
|
889
|
+
) {
|
|
890
|
+
guard let type = HKObjectType.quantityType(forIdentifier: identifier) else {
|
|
891
|
+
completion(nil, NSError(domain: "HealthKit", code: -1, userInfo: [NSLocalizedDescriptionKey: "Quantity type unavailable"]))
|
|
892
|
+
return
|
|
893
|
+
}
|
|
894
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
895
|
+
let query = HKStatisticsQuery(
|
|
896
|
+
quantityType: type,
|
|
897
|
+
quantitySamplePredicate: predicate,
|
|
898
|
+
options: .cumulativeSum
|
|
899
|
+
) { _, result, error in
|
|
900
|
+
if let error = error {
|
|
901
|
+
completion(nil, error)
|
|
902
|
+
return
|
|
903
|
+
}
|
|
904
|
+
let value = result?.sumQuantity()?.doubleValue(for: HKUnit.kilocalorie())
|
|
905
|
+
completion(value, nil)
|
|
906
|
+
}
|
|
907
|
+
healthStore.execute(query)
|
|
908
|
+
}
|
|
909
|
+
|
|
824
910
|
private func queryLatestTotalCalories(_ call: CAPPluginCall) {
|
|
825
911
|
let unit = HKUnit.kilocalorie()
|
|
826
912
|
let group = DispatchGroup()
|
|
913
|
+
let now = Date()
|
|
914
|
+
let startOfDay = Calendar.current.startOfDay(for: now)
|
|
827
915
|
|
|
828
|
-
var
|
|
829
|
-
var
|
|
830
|
-
var activeDate: Date?
|
|
831
|
-
var basalDate: Date?
|
|
916
|
+
var activeTotal: Double?
|
|
917
|
+
var basalTotal: Double?
|
|
832
918
|
var queryError: Error?
|
|
833
919
|
let basalSupported = HKObjectType.quantityType(forIdentifier: .basalEnergyBurned) != nil
|
|
834
920
|
|
|
835
|
-
func fetchLatest(_ identifier: HKQuantityTypeIdentifier, completion: @escaping (Double?, Date?, Error?) -> Void) {
|
|
836
|
-
guard let type = HKObjectType.quantityType(forIdentifier: identifier) else {
|
|
837
|
-
completion(nil, nil, NSError(domain: "HealthKit", code: -1, userInfo: [NSLocalizedDescriptionKey: "Quantity type unavailable"]))
|
|
838
|
-
return
|
|
839
|
-
}
|
|
840
|
-
let predicate = HKQuery.predicateForSamples(withStart: Date.distantPast, end: Date(), options: .strictEndDate)
|
|
841
|
-
let sort = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
|
|
842
|
-
let query = HKSampleQuery(sampleType: type, predicate: predicate, limit: 1, sortDescriptors: [sort]) { _, samples, error in
|
|
843
|
-
if let error = error {
|
|
844
|
-
completion(nil, nil, error)
|
|
845
|
-
return
|
|
846
|
-
}
|
|
847
|
-
guard let sample = samples?.first as? HKQuantitySample else {
|
|
848
|
-
completion(nil, nil, nil)
|
|
849
|
-
return
|
|
850
|
-
}
|
|
851
|
-
completion(sample.quantity.doubleValue(for: unit), sample.startDate, nil)
|
|
852
|
-
}
|
|
853
|
-
healthStore.execute(query)
|
|
854
|
-
}
|
|
855
|
-
|
|
856
921
|
group.enter()
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
queryError = queryError ?? error
|
|
922
|
+
sumEnergy(.activeEnergyBurned, startDate: startOfDay, endDate: now) { value, error in
|
|
923
|
+
activeTotal = value
|
|
924
|
+
if queryError == nil { queryError = error }
|
|
861
925
|
group.leave()
|
|
862
926
|
}
|
|
863
927
|
|
|
864
928
|
if basalSupported {
|
|
865
929
|
group.enter()
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
queryError = queryError ?? error
|
|
930
|
+
sumEnergy(.basalEnergyBurned, startDate: startOfDay, endDate: now) { value, error in
|
|
931
|
+
basalTotal = value
|
|
932
|
+
if queryError == nil { queryError = error }
|
|
870
933
|
group.leave()
|
|
871
934
|
}
|
|
872
935
|
}
|
|
@@ -876,15 +939,14 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
876
939
|
call.reject("Error fetching total calories: \(error.localizedDescription)")
|
|
877
940
|
return
|
|
878
941
|
}
|
|
879
|
-
guard
|
|
942
|
+
guard activeTotal != nil || basalTotal != nil else {
|
|
880
943
|
call.reject("No sample found", "NO_SAMPLE")
|
|
881
944
|
return
|
|
882
945
|
}
|
|
883
|
-
let total = (
|
|
884
|
-
let timestamp = max(activeDate?.timeIntervalSince1970 ?? 0, basalDate?.timeIntervalSince1970 ?? 0) * 1000
|
|
946
|
+
let total = (activeTotal ?? 0) + (basalTotal ?? 0)
|
|
885
947
|
call.resolve([
|
|
886
948
|
"value": total,
|
|
887
|
-
"timestamp":
|
|
949
|
+
"timestamp": now.timeIntervalSince1970 * 1000,
|
|
888
950
|
"unit": unit.unitString
|
|
889
951
|
])
|
|
890
952
|
}
|
package/package.json
CHANGED