1 /** <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * ``` 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * ``` 10 * 11 * Unless required by applicable law or agreed to in writing, software distributed under the License 12 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 * or implied. See the License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.android.healthconnect.controller.dataentries.formatters 17 18 import android.content.Context 19 import android.health.connect.datatypes.HeartRateRecord 20 import android.icu.text.MessageFormat 21 import androidx.annotation.StringRes 22 import com.android.healthconnect.controller.R 23 import com.android.healthconnect.controller.data.entries.FormattedEntry 24 import com.android.healthconnect.controller.dataentries.formatters.shared.EntryFormatter 25 import com.android.healthconnect.controller.dataentries.formatters.shared.RecordDetailsFormatter 26 import com.android.healthconnect.controller.dataentries.units.UnitPreferences 27 import com.android.healthconnect.controller.utils.LocalDateTimeFormatter 28 import dagger.hilt.android.qualifiers.ApplicationContext 29 import javax.inject.Inject 30 import javax.inject.Singleton 31 32 /** Formatter for printing HeartRate data. */ 33 @Singleton 34 class HeartRateFormatter @Inject constructor(@ApplicationContext private val context: Context) : 35 EntryFormatter<HeartRateRecord>(context), RecordDetailsFormatter<HeartRateRecord> { 36 37 private val timeFormatter = LocalDateTimeFormatter(context) 38 39 override suspend fun formatRecord( 40 record: HeartRateRecord, 41 header: String, 42 headerA11y: String, 43 unitPreferences: UnitPreferences 44 ): FormattedEntry { 45 return FormattedEntry.SeriesDataEntry( 46 uuid = record.metadata.id, 47 header = header, 48 headerA11y = headerA11y, 49 title = formatValue(record, unitPreferences), 50 titleA11y = formatA11yValue(record, unitPreferences), 51 dataType = getDataType(record)) 52 } 53 54 override suspend fun formatValue( 55 record: HeartRateRecord, 56 unitPreferences: UnitPreferences 57 ): String { 58 return if (record.samples.size == 1) { 59 formatSampleValue(R.string.heart_rate_value, record.samples.first().beatsPerMinute) 60 } else { 61 formatRange(R.string.heart_rate_series_range, record) { heartRate: Long -> 62 formatSampleValue(R.string.heart_rate_value, heartRate) 63 } 64 } 65 } 66 67 override suspend fun formatA11yValue( 68 record: HeartRateRecord, 69 unitPreferences: UnitPreferences 70 ): String { 71 return if (record.samples.size == 1) { 72 formatSampleValue(R.string.heart_rate_long_value, record.samples.first().beatsPerMinute) 73 } else { 74 return formatRange( 75 R.string.heart_rate_series_range_long, 76 record, 77 ) { heartRate: Long -> 78 formatSampleValue(R.string.heart_rate_long_value, heartRate) 79 } 80 } 81 } 82 83 override suspend fun formatRecordDetails(record: HeartRateRecord): List<FormattedEntry> { 84 val samples = 85 record.samples.sortedBy { it.time }.map { formatSample(record.metadata.id, it) } 86 return buildList { 87 if (samples.isNotEmpty()) { 88 addAll(samples) 89 } 90 } 91 } 92 93 private fun formatSample( 94 id: String, 95 sample: HeartRateRecord.HeartRateSample 96 ): FormattedEntry.FormattedSessionDetail { 97 return FormattedEntry.FormattedSessionDetail( 98 uuid = id, 99 header = timeFormatter.formatTime(sample.time), 100 headerA11y = timeFormatter.formatTime(sample.time), 101 title = formatSampleValue(R.string.heart_rate_value, sample.beatsPerMinute), 102 titleA11y = formatSampleValue(R.string.heart_rate_long_value, sample.beatsPerMinute), 103 ) 104 } 105 106 private fun formatSampleValue(@StringRes res: Int, heartRate: Long): String { 107 return MessageFormat.format(context.getString(res), mapOf("count" to heartRate)) 108 } 109 110 private fun formatRange( 111 @StringRes res: Int, 112 record: HeartRateRecord, 113 getSample: (heartRate: Long) -> String 114 ): String { 115 val min = record.samples.minOf { it.beatsPerMinute } 116 val max = record.samples.maxOf { it.beatsPerMinute } 117 return context.getString(res, getSample(min), getSample(max)) 118 } 119 } 120