diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt index ba0db624c..8a82d3cf2 100644 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt +++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt @@ -110,6 +110,76 @@ class HealthDataReader( } } + /** + * Retrieves single health data point by given UUID and type. + * + * @param call Method call containing 'UUID' and 'dataTypeKey' + * @param result Flutter result callback returning list of health data maps + */ + fun getDataByUUID(call: MethodCall, result: Result) { + val dataType = call.argument("dataTypeKey")!! + val uuid = call.argument("uuid")!! + var healthPoint = mapOf() + + if (!HealthConstants.mapToType.containsKey(dataType)) { + Log.w("FLUTTER_HEALTH::ERROR", "Datatype $dataType not found in HC") + result.success(null) + return + } + + val classType = HealthConstants.mapToType[dataType]!! + + scope.launch { + try { + + Log.i("FLUTTER_HEALTH", "Getting $uuid with $classType") + + // Execute the request + val response = healthConnectClient.readRecord(classType, uuid) + + // Find the record with the matching UUID + val matchingRecord = response.record + + if (matchingRecord != null) { + // Handle special cases using shared logic + when (dataType) { + WORKOUT -> { + val tempData = mutableListOf>() + handleWorkoutData(listOf(matchingRecord), emptyList(), tempData) + healthPoint = if (tempData.isNotEmpty()) tempData[0] else mapOf() + } + SLEEP_SESSION, SLEEP_ASLEEP, SLEEP_AWAKE, SLEEP_AWAKE_IN_BED, + SLEEP_LIGHT, SLEEP_DEEP, SLEEP_REM, SLEEP_OUT_OF_BED, SLEEP_UNKNOWN -> { + if (matchingRecord is SleepSessionRecord) { + val tempData = mutableListOf>() + handleSleepData(listOf(matchingRecord), emptyList(), dataType, tempData) + healthPoint = if (tempData.isNotEmpty()) tempData[0] else mapOf() + } + } + else -> { + healthPoint = dataConverter.convertRecord(matchingRecord, dataType)[0] + } + } + + Log.i( + "FLUTTER_HEALTH", + "Success: $healthPoint" + ) + + Handler(context.mainLooper).run { result.success(healthPoint) } + } else { + Log.e("FLUTTER_HEALTH::ERROR", "Record not found for UUID: $uuid") + result.success(null) + } + } catch (e: Exception) { + Log.e("FLUTTER_HEALTH::ERROR", "Error fetching record with UUID: $uuid") + Log.e("FLUTTER_HEALTH::ERROR", e.message ?: "unknown error") + Log.e("FLUTTER_HEALTH::ERROR", e.stackTraceToString()) + result.success(null) + } + } + } + /** * Retrieves aggregated health data grouped by time intervals. * Calculates totals, averages, or counts over specified time periods. @@ -289,18 +359,22 @@ class HealthDataReader( * by querying related records within the workout time period. * * @param records List of ExerciseSessionRecord objects - * @param recordingMethodsToFilter Recording methods to exclude + * @param recordingMethodsToFilter Recording methods to exclude (empty list means no filtering) * @param healthConnectData Mutable list to append processed workout data */ private suspend fun handleWorkoutData( records: List, - recordingMethodsToFilter: List, + recordingMethodsToFilter: List = emptyList(), healthConnectData: MutableList> ) { - val filteredRecords = recordingFilter.filterRecordsByRecordingMethods( - recordingMethodsToFilter, + val filteredRecords = if (recordingMethodsToFilter.isEmpty()) { records - ) + } else { + recordingFilter.filterRecordsByRecordingMethods( + recordingMethodsToFilter, + records + ) + } for (rec in filteredRecords) { val record = rec as ExerciseSessionRecord @@ -366,8 +440,8 @@ class HealthDataReader( "totalSteps" to if (totalSteps == 0.0) null else totalSteps, "totalStepsUnit" to "COUNT", "unit" to "MINUTES", - "date_from" to rec.startTime.toEpochMilli(), - "date_to" to rec.endTime.toEpochMilli(), + "date_from" to record.startTime.toEpochMilli(), + "date_to" to record.endTime.toEpochMilli(), "source_id" to "", "source_name" to record.metadata.dataOrigin.packageName, ), @@ -381,20 +455,24 @@ class HealthDataReader( * Converts sleep stage enumerations to meaningful duration and type information. * * @param records List of SleepSessionRecord objects - * @param recordingMethodsToFilter Recording methods to exclude + * @param recordingMethodsToFilter Recording methods to exclude (empty list means no filtering) * @param dataType Specific sleep data type being requested * @param healthConnectData Mutable list to append processed sleep data */ private fun handleSleepData( records: List, - recordingMethodsToFilter: List, + recordingMethodsToFilter: List = emptyList(), dataType: String, healthConnectData: MutableList> ) { - val filteredRecords = recordingFilter.filterRecordsByRecordingMethods( - recordingMethodsToFilter, + val filteredRecords = if (recordingMethodsToFilter.isEmpty()) { records - ) + } else { + recordingFilter.filterRecordsByRecordingMethods( + recordingMethodsToFilter, + records + ) + } for (rec in filteredRecords) { if (rec is SleepSessionRecord) { diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt index 27c94c76d..83a861487 100644 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt +++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt @@ -151,6 +151,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : // Reading data "getData" -> dataReader.getData(call, result) + "getDataByUUID" -> dataReader.getDataByUUID(call, result) "getIntervalData" -> dataReader.getIntervalData(call, result) "getAggregateData" -> dataReader.getAggregateData(call, result) "getTotalStepsInInterval" -> dataReader.getTotalStepsInInterval(call, result)