From 9fe45bda518fda6a886d44874c17a39e5d5eb46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlo=20Huam=C3=A1n?= <11746842+tohure@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:59:16 -0500 Subject: [PATCH] feat(salud): iOS suma activas + basal para el total quemado (paridad Android) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HealthKit no tiene un tipo "total" único como Health Connect, así que se leen HKActiveEnergyBurned + HKBasalEnergyBurned y se suman → "Calorías quemadas" = total, como en Android. CALORIES_BURNED ahora pide ambos permisos (activa y basal / "Resting Energy"). Smoke-test en simulador: compila, lanza y solicita el permiso basal; el número no es verificable sin datos reales (Google Health no sincroniza con Apple Health). --- .../data/health/HealthDataReader.ios.kt | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/shared/src/iosMain/kotlin/dev/tohure/tanayenai/data/health/HealthDataReader.ios.kt b/shared/src/iosMain/kotlin/dev/tohure/tanayenai/data/health/HealthDataReader.ios.kt index 2031db9..50cffc7 100644 --- a/shared/src/iosMain/kotlin/dev/tohure/tanayenai/data/health/HealthDataReader.ios.kt +++ b/shared/src/iosMain/kotlin/dev/tohure/tanayenai/data/health/HealthDataReader.ios.kt @@ -21,6 +21,7 @@ import platform.HealthKit.HKObjectType import platform.HealthKit.HKQuantityType import platform.HealthKit.HKQuantityTypeIdentifier import platform.HealthKit.HKQuantityTypeIdentifierActiveEnergyBurned +import platform.HealthKit.HKQuantityTypeIdentifierBasalEnergyBurned import platform.HealthKit.HKQuantityTypeIdentifierBodyMass import platform.HealthKit.HKQuantityTypeIdentifierHeartRateVariabilitySDNN import platform.HealthKit.HKQuantityTypeIdentifierStepCount @@ -55,7 +56,7 @@ actual class HealthDataReader { suspend fun requestPermissionsIos(permissions: Set): Boolean { if (!HKHealthStore.isHealthDataAvailable()) return false - val readTypes = permissions.mapNotNull { it.toHKType() }.toSet() + val readTypes = permissions.flatMap { it.toHKTypes() }.toSet() return suspendCancellableCoroutine { cont -> store.requestAuthorizationToShareTypes( @@ -86,7 +87,16 @@ actual class HealthDataReader { startDate, endDate, )?.let { it * 1000f } - val calories = readCalories(startDate, endDate)?.toInt() + // Total quemado = activas + basal (HealthKit no tiene un tipo "total" único como + // Health Connect; hay que sumar los dos). Coincide con "Calorías quemadas" de Android. + val activeKcal = readEnergy(HKQuantityTypeIdentifierActiveEnergyBurned, startDate, endDate) + val basalKcal = readEnergy(HKQuantityTypeIdentifierBasalEnergyBurned, startDate, endDate) + val calories = + if (activeKcal == null && basalKcal == null) { + null + } else { + ((activeKcal ?: 0.0) + (basalKcal ?: 0.0)).toInt() + } val weight = readQuantity( HKQuantityTypeIdentifierBodyMass, @@ -217,16 +227,16 @@ actual class HealthDataReader { store.executeQuery(query) } - // TODO(iOS): el card se llama "Calorías quemadas" (total). Aquí solo se lee la energía - // ACTIVA (HKActiveEnergyBurned); falta sumar la basal (HKBasalEnergyBurned) y pedir su - // permiso para que coincida con el total. Pendiente para cuando iOS se distribuya. - private suspend fun readCalories( + // Suma acumulada de un tipo de energía (activa o basal) en el rango. Reutilizable para + // componer el total quemado. + private suspend fun readEnergy( + identifier: HKQuantityTypeIdentifier, start: NSDate, end: NSDate, ): Double? = suspendCancellableCoroutine { cont -> val caloriesType = - HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned) + HKQuantityType.quantityTypeForIdentifier(identifier) ?: return@suspendCancellableCoroutine cont.resume(null) val predicate = HKSampleQuery.predicateForSamplesWithStartDate(start, end, 0u) val query = @@ -247,30 +257,30 @@ actual class HealthDataReader { // ── Helpers ─────────────────────────────────────────────────────────────── - private fun TanayenPermission.toHKType(): HKObjectType? = + // Un permiso puede mapear a varios tipos de HealthKit (calorías = activa + basal). + private fun TanayenPermission.toHKTypes(): Set = when (this) { TanayenPermission.SLEEP -> { - HKCategoryType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis) + setOfNotNull(HKCategoryType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis)) } TanayenPermission.HEART_RATE_VARIABILITY -> { - HKQuantityType.quantityTypeForIdentifier( - HKQuantityTypeIdentifierHeartRateVariabilitySDNN, - ) + setOfNotNull(HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRateVariabilitySDNN)) } TanayenPermission.CALORIES_BURNED -> { - HKQuantityType.quantityTypeForIdentifier( - HKQuantityTypeIdentifierActiveEnergyBurned, + setOfNotNull( + HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned), + HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBasalEnergyBurned), ) } TanayenPermission.WEIGHT -> { - HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass) + setOfNotNull(HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass)) } TanayenPermission.STEPS -> { - HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount) + setOfNotNull(HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)) } }