From bf1fb0ba38c3eb70ed6dc43d2eebcd8c1e8cbbee Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 25 Oct 2024 14:00:01 +0200 Subject: [PATCH 01/13] added SymptomsDescriptionBottomSheet Signed-off-by: Basler182 --- .../engagehf/health/symptoms/SymptomsViewModel.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt index ce0bc2f25..658556aa5 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt @@ -5,6 +5,7 @@ package edu.stanford.bdh.engagehf.health.symptoms import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import edu.stanford.bdh.engagehf.bluetooth.component.AppScreenEvents import edu.stanford.bdh.engagehf.health.AggregatedHealthData import edu.stanford.bdh.engagehf.health.HealthRepository import edu.stanford.bdh.engagehf.health.TableEntryData @@ -23,6 +24,7 @@ import javax.inject.Inject class SymptomsViewModel @Inject internal constructor( private val symptomsUiStateMapper: SymptomsUiStateMapper, private val healthRepository: HealthRepository, + private val appScreenEvents: AppScreenEvents, ) : ViewModel() { private val logger by speziLogger() @@ -56,7 +58,16 @@ class SymptomsViewModel @Inject internal constructor( fun onAction(action: Action) { when (action) { - Action.Info -> {} + is Action.Info -> { + val selectedSymptomType = + (uiState.value as? SymptomsUiState.Success)?.data?.headerData?.selectedSymptomType + if (selectedSymptomType != null) { + appScreenEvents.emit( + AppScreenEvents.Event.SymptomsDescriptionBottomSheet + ) + } + } + is Action.SelectSymptomType -> { _uiState.update { (it as? SymptomsUiState.Success)?.let { success -> From c6f295b2a7f89b1e54bfb28c464e647bf4695798 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 25 Oct 2024 14:00:49 +0200 Subject: [PATCH 02/13] unified heart health icons Signed-off-by: Basler182 --- .../engagehf/health/symptoms/SymptomsScreen.kt | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt index 69d3afb72..2d5caf05d 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt @@ -1,6 +1,5 @@ package edu.stanford.bdh.engagehf.health.symptoms -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -12,9 +11,9 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.outlined.Check import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu @@ -31,9 +30,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -71,7 +68,6 @@ import edu.stanford.bdh.engagehf.R import edu.stanford.bdh.engagehf.health.HealthTableItem import edu.stanford.spezi.core.design.component.CenteredBoxContent import edu.stanford.spezi.core.design.component.VerticalSpacer -import edu.stanford.spezi.core.design.theme.Colors.onPrimary import edu.stanford.spezi.core.design.theme.Colors.primary import edu.stanford.spezi.core.design.theme.Colors.secondary import edu.stanford.spezi.core.design.theme.Sizes @@ -163,14 +159,9 @@ private fun LazyListScope.listHeader( onClick = { onAction(SymptomsViewModel.Action.Info) } ) { Icon( - painter = painterResource(id = edu.stanford.spezi.core.design.R.drawable.ic_info), + imageVector = Icons.Filled.Info, contentDescription = stringResource(R.string.info_icon_content_description), - modifier = Modifier - .size(Sizes.Icon.medium) - .background(primary, shape = CircleShape) - .shadow(Spacings.small, CircleShape) - .padding(Spacings.small), - tint = onPrimary + tint = primary ) } } From affb56e5e6e3ee36350bbb4058434b143d19e287 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 25 Oct 2024 14:01:05 +0200 Subject: [PATCH 03/13] added SymptomsDescriptionBottomSheet event Signed-off-by: Basler182 --- .../stanford/bdh/engagehf/bluetooth/component/AppScreenEvents.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/bluetooth/component/AppScreenEvents.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/bluetooth/component/AppScreenEvents.kt index 57a866a2e..06aabf5dd 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/bluetooth/component/AppScreenEvents.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/bluetooth/component/AppScreenEvents.kt @@ -33,5 +33,6 @@ class AppScreenEvents @Inject constructor( data object AddBloodPressureRecord : Event data object AddHeartRateRecord : Event data class NavigateToTab(val bottomBarItem: BottomBarItem) : Event + data object SymptomsDescriptionBottomSheet : Event } } From 8fa35b00d3734c3c7ee661a3694f8ad25cfdb54a Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 25 Oct 2024 14:01:31 +0200 Subject: [PATCH 04/13] added SymptomsDescriptionBottomSheet Signed-off-by: Basler182 --- .../edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt | 2 ++ .../bdh/engagehf/navigation/screens/AppScreenViewModel.kt | 3 +++ 2 files changed, 5 insertions(+) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt index cb0bb3d0a..f83ad84cb 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreen.kt @@ -39,6 +39,7 @@ import edu.stanford.bdh.engagehf.health.bloodpressure.bottomsheet.AddBloodPressu import edu.stanford.bdh.engagehf.health.bloodpressure.bottomsheet.BloodPressureDescriptionBottomSheet import edu.stanford.bdh.engagehf.health.heartrate.bottomsheet.AddHeartRateBottomSheet import edu.stanford.bdh.engagehf.health.heartrate.bottomsheet.HeartRateDescriptionBottomSheet +import edu.stanford.bdh.engagehf.health.symptoms.SymptomsDescriptionBottomSheet import edu.stanford.bdh.engagehf.health.weight.bottomsheet.AddWeightBottomSheet import edu.stanford.bdh.engagehf.health.weight.bottomsheet.WeightDescriptionBottomSheet import edu.stanford.bdh.engagehf.medication.ui.MedicationScreen @@ -209,6 +210,7 @@ private fun BottomSheetContent( BottomSheetContent.BLOOD_PRESSURE_DESCRIPTION_INFO -> BloodPressureDescriptionBottomSheet() BottomSheetContent.HEART_RATE_DESCRIPTION_INFO -> HeartRateDescriptionBottomSheet() BottomSheetContent.BLUETOOTH_DEVICE_PAIRING -> BLEDevicePairingBottomSheet() + BottomSheetContent.SYMPTOMS_DESCRIPTION_INFO -> SymptomsDescriptionBottomSheet() } } diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreenViewModel.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreenViewModel.kt index 159bb2001..72c17ae4e 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreenViewModel.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreenViewModel.kt @@ -89,6 +89,8 @@ class AppScreenViewModel @Inject constructor( BottomSheetContent.BLOOD_PRESSURE_DESCRIPTION_INFO } + AppScreenEvents.Event.SymptomsDescriptionBottomSheet -> BottomSheetContent.SYMPTOMS_DESCRIPTION_INFO + AppScreenEvents.Event.CloseBottomSheet -> { null } @@ -187,6 +189,7 @@ enum class BottomSheetContent { ADD_WEIGHT_RECORD, ADD_BLOOD_PRESSURE_RECORD, ADD_HEART_RATE_RECORD, + SYMPTOMS_DESCRIPTION_INFO, BLUETOOTH_DEVICE_PAIRING, } From b9fcef52d7ab16ffdbbd596af31ad7c739362266 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 25 Oct 2024 14:41:29 +0200 Subject: [PATCH 05/13] added SymptomTypeTitleText Signed-off-by: Basler182 --- .../health/symptoms/SymptomsScreen.kt | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt index 2d5caf05d..b3b14cbfd 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt @@ -271,12 +271,12 @@ fun SymptomsChart( } @Composable -fun SymptomsDropdown(headerData: HeaderData, onAction: (SymptomsViewModel.Action) -> Unit) { +private fun SymptomsDropdown(headerData: HeaderData, onAction: (SymptomsViewModel.Action) -> Unit) { Box(modifier = Modifier.wrapContentSize(Alignment.TopStart)) { TextButton(onClick = { onAction(SymptomsViewModel.Action.ToggleSymptomTypeDropdown(true)) }) { - SymptomTypeText(headerData.selectedSymptomType) + SymptomTypeTitleText(headerData.selectedSymptomType) Icon(Icons.Default.ArrowDropDown, contentDescription = "ArrowDropDown") } DropdownMenu( @@ -308,7 +308,7 @@ fun SymptomsDropdown(headerData: HeaderData, onAction: (SymptomsViewModel.Action } @Composable -fun SymptomTypeText(symptomType: SymptomType) { +private fun SymptomTypeText(symptomType: SymptomType) { Text( text = when (symptomType) { @@ -321,3 +321,18 @@ fun SymptomTypeText(symptomType: SymptomType) { } ) } + +@Composable +private fun SymptomTypeTitleText(symptomType: SymptomType) { + Text( + text = + when (symptomType) { + SymptomType.OVERALL -> stringResource(R.string.overall_score_title) + SymptomType.PHYSICAL_LIMITS -> stringResource(R.string.physical_limits_score_title) + SymptomType.SOCIAL_LIMITS -> stringResource(R.string.social_limits_score_title) + SymptomType.QUALITY_OF_LIFE -> stringResource(R.string.quality_of_life_score_title) + SymptomType.SYMPTOMS_FREQUENCY -> stringResource(R.string.symptoms_frequency_score_title) + SymptomType.DIZZINESS -> stringResource(R.string.dizziness_score_title) + } + ) +} From cb5566fc536880200407fde4ec2f41c24042bc2b Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 25 Oct 2024 14:45:42 +0200 Subject: [PATCH 06/13] added string resources Signed-off-by: Basler182 --- app/src/main/res/values/strings.xml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ae6cc12fb..fdf154c3f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,7 +26,7 @@ Physical Social Quality - Specific + Symptoms Dizziness No medication recommendations Current Dose: @@ -108,4 +108,17 @@ Invitation Code Onboarding Contact + Understanding Your Symptoms + Overall Score + This score shows how your heart failure affects you overall. You can score 0 to 100. A higher score means you are doing better. Over 80 is often considered doing well. A goal of heart medicines is to improve this score over time or keep it from decreasing. + Physical Limits Score + This represents how your heart affects your ability to do activity. Higher scores mean you are less limited, and over 80 is often considered doing well. + Social Limits Score + This score represents how your heart affects your ability to do social activities. Higher scores mean you are able to do more, while lower scores mean you are more limited by your heart. + Quality of Life Score + This score represents how content you are with your heart symptoms. Higher scores mean you are overall more content, while lower scores mean you are less satisfied with how you are doing. + Symptom Frequency + This represents the frequency you are experiencing heart-related symptoms. Higher scores mean less frequent symptoms and lower scores mean more frequent symptoms. + Dizziness Score + Your dizziness is important to keep track of because dizziness can be a side effect of your heart or your heart medicines. From ea9d8cc94cefaceb353cdde8af76f3432412332a Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 25 Oct 2024 14:45:54 +0200 Subject: [PATCH 07/13] added missing de string resources Signed-off-by: Basler182 --- app/src/main/res/values-de/strings.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 95666d167..f41a0ca6c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -91,5 +91,20 @@ Deine Geräte Zuletzt gesehen am %1$s Abmeldung fehlgeschlagen. + Einladungscode + Onboarding Kontakt + Ihre Symptome verstehen + Gesamt Score + Dieser Wert zeigt, wie sich Ihre Herzinsuffizienz insgesamt auf Sie auswirkt. Sie können einen Wert zwischen 0 und 100 erreichen. Ein höherer Wert bedeutet, dass es Ihnen besser geht. Ein Wert über 80 wird oft als gut angesehen. Ein Ziel der Herzmedikamente ist es, diesen Wert im Laufe der Zeit zu verbessern oder zu verhindern, dass er sich verringert. + Physikalische Grenzen Score + Dieser Wert gibt an, wie sehr Ihr Herz Ihre Fähigkeit, sich zu betätigen, beeinträchtigt. Höhere Werte bedeuten, dass Sie weniger eingeschränkt sind, und ein Wert über 80 wird oft als gut angesehen. + Soziale Grenzen Score + Dieser Wert gibt an, wie Ihr Herz Ihre Fähigkeit zu sozialen Aktivitäten beeinflusst. Höhere Werte bedeuten, dass Sie mehr tun können, während niedrigere Werte bedeuten, dass Sie durch Ihr Herz stärker eingeschränkt sind. + Bewertung der Lebensqualität + Dieser Wert gibt an, wie zufrieden Sie mit Ihren Herzsymptomen sind. Höhere Werte bedeuten, dass Sie insgesamt zufriedener sind, während niedrigere Werte bedeuten, dass Sie weniger zufrieden mit Ihrer Situation sind. + Symptom Häufigkeit + Dieser Wert gibt die Häufigkeit an, mit der Sie herzbezogene Symptome verspüren. Höhere Werte bedeuten weniger häufige Symptome und niedrigere Werte bedeuten häufigere Symptome. + Schwindel Score + Es ist wichtig, Ihren Schwindel im Auge zu behalten, denn Schwindel kann eine Nebenwirkung Ihres Herzens oder Ihrer Herzmedikamente sein. From 8b713f9e4988d2d10cdafc235f4120aa8cb4952d Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 25 Oct 2024 15:04:59 +0200 Subject: [PATCH 08/13] adjusted tests Signed-off-by: Basler182 --- .../health/symptoms/SymptomsViewModelTest.kt | 21 ++++++++++++++++++- .../screens/AppScreenViewModelTest.kt | 14 +++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModelTest.kt b/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModelTest.kt index 716c82e9e..fccf55b22 100644 --- a/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModelTest.kt +++ b/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModelTest.kt @@ -1,11 +1,13 @@ package edu.stanford.bdh.engagehf.health.symptoms import com.google.common.truth.Truth.assertThat +import edu.stanford.bdh.engagehf.bluetooth.component.AppScreenEvents import edu.stanford.bdh.engagehf.health.HealthRepository import edu.stanford.spezi.core.testing.CoroutineTestRule import io.mockk.coEvery import io.mockk.every import io.mockk.mockk +import io.mockk.verify import kotlinx.coroutines.flow.flowOf import org.junit.Before import org.junit.Rule @@ -21,6 +23,7 @@ class SymptomsViewModelTest { private val symptomsUiStateMapper: SymptomsUiStateMapper = mockk() private val symptomScores = getSymptomScores() private val successState = SymptomsUiState.Success(getSymptomsUiData()) + private val appScreenEvents: AppScreenEvents = mockk(relaxed = true) private lateinit var viewModel: SymptomsViewModel @@ -99,6 +102,18 @@ class SymptomsViewModelTest { assertThat(uiState).isEqualTo(newState) } + @Test + fun `it should handle SymptomsDescriptionBottomSheet correctly`() { + // given + createViewModel() + + // when + viewModel.onAction(SymptomsViewModel.Action.Info) + + // then + verify { appScreenEvents.emit(AppScreenEvents.Event.SymptomsDescriptionBottomSheet) } + } + private fun getSymptomScores() = listOf( SymptomScore( overallScore = 80.0, @@ -121,6 +136,10 @@ class SymptomsViewModelTest { ) private fun createViewModel() { - viewModel = SymptomsViewModel(symptomsUiStateMapper, healthRepository) + viewModel = SymptomsViewModel( + symptomsUiStateMapper = symptomsUiStateMapper, + healthRepository = healthRepository, + appScreenEvents = appScreenEvents, + ) } } diff --git a/app/src/test/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreenViewModelTest.kt b/app/src/test/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreenViewModelTest.kt index 4f905e41a..9b2092c05 100644 --- a/app/src/test/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreenViewModelTest.kt +++ b/app/src/test/kotlin/edu/stanford/bdh/engagehf/navigation/screens/AppScreenViewModelTest.kt @@ -268,4 +268,18 @@ class AppScreenViewModelTest { val updatedUiState = viewModel.uiState.value assertThat(updatedUiState.bottomSheetContent).isEqualTo(BottomSheetContent.HEART_RATE_DESCRIPTION_INFO) } + + @Test + fun `given SymptomsDescriptionBottomSheet is received then uiState should be updated`() = + runTestUnconfined { + // Given + val event = AppScreenEvents.Event.SymptomsDescriptionBottomSheet + + // When + appScreenEventsFlow.emit(event) + + // Then + val updatedUiState = viewModel.uiState.value + assertThat(updatedUiState.bottomSheetContent).isEqualTo(BottomSheetContent.SYMPTOMS_DESCRIPTION_INFO) + } } From 6823e39a2c1d434df725c03896320b47486af689 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Fri, 25 Oct 2024 15:05:44 +0200 Subject: [PATCH 09/13] added SymptomsDescriptionBottomSheet Signed-off-by: Basler182 --- .../SymptomsDescriptionBottomSheet.kt | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsDescriptionBottomSheet.kt diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsDescriptionBottomSheet.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsDescriptionBottomSheet.kt new file mode 100644 index 000000000..a7155e412 --- /dev/null +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsDescriptionBottomSheet.kt @@ -0,0 +1,88 @@ +package edu.stanford.bdh.engagehf.health.symptoms + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import edu.stanford.bdh.engagehf.R +import edu.stanford.spezi.core.design.component.VerticalSpacer +import edu.stanford.spezi.core.design.theme.Spacings +import edu.stanford.spezi.core.design.theme.SpeziTheme +import edu.stanford.spezi.core.design.theme.TextStyles +import edu.stanford.spezi.core.design.theme.ThemePreviews + +@Composable +fun SymptomsDescriptionBottomSheet() { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .padding(Spacings.medium), + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + Text( + text = stringResource(R.string.symptoms_description_title), + style = TextStyles.titleLarge + ) + VerticalSpacer() + TitleDescriptionItem( + title = stringResource(R.string.overall_score_title), + description = stringResource(R.string.overall_score_description) + ) + TitleDescriptionItem( + title = stringResource(R.string.physical_limits_score_title), + description = stringResource(R.string.physical_limits_score_description) + ) + TitleDescriptionItem( + title = stringResource(R.string.social_limits_score_title), + description = stringResource(R.string.social_limits_score_description) + ) + TitleDescriptionItem( + title = stringResource(R.string.quality_of_life_score_title), + description = stringResource(R.string.quality_of_life_score_description) + ) + TitleDescriptionItem( + title = stringResource(R.string.symptoms_frequency_score_title), + description = stringResource(R.string.specific_symptoms_score_description) + ) + TitleDescriptionItem( + title = stringResource(R.string.dizziness_score_title), + description = stringResource(R.string.dizziness_score_description) + ) + VerticalSpacer() + } + } +} + +@Composable +fun TitleDescriptionItem(title: String, description: String) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = Spacings.small), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = title, + style = TextStyles.titleMedium + ) + Text( + text = description, + style = TextStyles.bodyMedium.copy(textAlign = TextAlign.Center) + ) + } +} + +@ThemePreviews +@Composable +fun SymptomsDescriptionBottomSheetPreview() { + SpeziTheme(isPreview = true) { + SymptomsDescriptionBottomSheet() + } +} From e6dd916cfa21bceba070f1a8a035c79c096b8288 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sun, 3 Nov 2024 10:39:21 +0100 Subject: [PATCH 10/13] removed not needed check Signed-off-by: Basler182 --- .../bdh/engagehf/health/symptoms/SymptomsViewModel.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt index 658556aa5..6e691b0b9 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt @@ -59,13 +59,9 @@ class SymptomsViewModel @Inject internal constructor( fun onAction(action: Action) { when (action) { is Action.Info -> { - val selectedSymptomType = - (uiState.value as? SymptomsUiState.Success)?.data?.headerData?.selectedSymptomType - if (selectedSymptomType != null) { - appScreenEvents.emit( - AppScreenEvents.Event.SymptomsDescriptionBottomSheet - ) - } + appScreenEvents.emit( + AppScreenEvents.Event.SymptomsDescriptionBottomSheet + ) } is Action.SelectSymptomType -> { From 186c564994cbf4f48d410606831225f3cbba0ff7 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sun, 3 Nov 2024 10:42:24 +0100 Subject: [PATCH 11/13] integrated feedback Signed-off-by: Basler182 --- .../engagehf/health/symptoms/SymptomsDescriptionBottomSheet.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsDescriptionBottomSheet.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsDescriptionBottomSheet.kt index a7155e412..5bd11285c 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsDescriptionBottomSheet.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsDescriptionBottomSheet.kt @@ -74,7 +74,8 @@ fun TitleDescriptionItem(title: String, description: String) { ) Text( text = description, - style = TextStyles.bodyMedium.copy(textAlign = TextAlign.Center) + style = TextStyles.bodyMedium, + textAlign = TextAlign.Center ) } } From 0290882993be5dc346c3b3bf46b06bfa6147db43 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sun, 3 Nov 2024 11:06:32 +0100 Subject: [PATCH 12/13] integrated feedback Signed-off-by: Basler182 --- .../engagehf/health/symptoms/SymptomsScreen.kt | 17 +---------------- .../health/symptoms/SymptomsUiStateMapper.kt | 16 +++++++++++++++- .../health/symptoms/SymptomsViewModel.kt | 2 ++ 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt index b3b14cbfd..07764413e 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsScreen.kt @@ -288,7 +288,7 @@ private fun SymptomsDropdown(headerData: HeaderData, onAction: (SymptomsViewMode val isSelected = headerData.selectedSymptomType == symptomType DropdownMenuItem( text = { - SymptomTypeText(symptomType) + headerData.selectedSymptomTypeText }, onClick = { onAction(SymptomsViewModel.Action.ToggleSymptomTypeDropdown(false)) @@ -307,21 +307,6 @@ private fun SymptomsDropdown(headerData: HeaderData, onAction: (SymptomsViewMode } } -@Composable -private fun SymptomTypeText(symptomType: SymptomType) { - Text( - text = - when (symptomType) { - SymptomType.OVERALL -> stringResource(R.string.symptom_type_overall) - SymptomType.PHYSICAL_LIMITS -> stringResource(R.string.symptom_type_physical) - SymptomType.SOCIAL_LIMITS -> stringResource(R.string.symptom_type_social) - SymptomType.QUALITY_OF_LIFE -> stringResource(R.string.symptom_type_quality) - SymptomType.SYMPTOMS_FREQUENCY -> stringResource(R.string.symptom_type_specific) - SymptomType.DIZZINESS -> stringResource(R.string.symptom_type_dizziness) - } - ) -} - @Composable private fun SymptomTypeTitleText(symptomType: SymptomType) { Text( diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsUiStateMapper.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsUiStateMapper.kt index dc00def18..0b9763957 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsUiStateMapper.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsUiStateMapper.kt @@ -1,8 +1,10 @@ package edu.stanford.bdh.engagehf.health.symptoms +import edu.stanford.bdh.engagehf.R import edu.stanford.bdh.engagehf.health.AggregatedHealthData import edu.stanford.bdh.engagehf.health.NewestHealthData import edu.stanford.bdh.engagehf.health.TableEntryData +import edu.stanford.spezi.core.design.component.StringResource import edu.stanford.spezi.core.utils.LocaleProvider import edu.stanford.spezi.core.utils.extensions.roundToDecimalPlaces import java.time.ZoneId @@ -40,13 +42,25 @@ class SymptomsUiStateMapper @Inject constructor( formattedValue = newestHealthData.formattedValue, formattedDate = newestHealthData.formattedDate, selectedSymptomType = selectedSymptomType, - isSelectedSymptomTypeDropdownExpanded = false + isSelectedSymptomTypeDropdownExpanded = false, + selectedSymptomTypeText = getSelectedSymptomTypeText(selectedSymptomType), ), valueFormatter = ::valueFormatter ) ) } + private fun getSelectedSymptomTypeText(selectedSymptomType: SymptomType): StringResource { + return when (selectedSymptomType) { + SymptomType.OVERALL -> StringResource(R.string.symptom_type_overall) + SymptomType.PHYSICAL_LIMITS -> StringResource(R.string.symptom_type_physical) + SymptomType.SOCIAL_LIMITS -> StringResource(R.string.symptom_type_social) + SymptomType.QUALITY_OF_LIFE -> StringResource(R.string.symptom_type_quality) + SymptomType.SYMPTOMS_FREQUENCY -> StringResource(R.string.symptom_type_specific) + SymptomType.DIZZINESS -> StringResource(R.string.symptom_type_dizziness) + } + } + private fun valueFormatter(value: Double): String { val year = value.toInt() val dayOfYearFraction = value - year diff --git a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt index 6e691b0b9..6447adb00 100644 --- a/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt +++ b/app/src/main/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModel.kt @@ -9,6 +9,7 @@ import edu.stanford.bdh.engagehf.bluetooth.component.AppScreenEvents import edu.stanford.bdh.engagehf.health.AggregatedHealthData import edu.stanford.bdh.engagehf.health.HealthRepository import edu.stanford.bdh.engagehf.health.TableEntryData +import edu.stanford.spezi.core.design.component.StringResource import edu.stanford.spezi.core.logging.speziLogger import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -117,6 +118,7 @@ data class SymptomsUiData( ) data class HeaderData( + val selectedSymptomTypeText: StringResource, val formattedValue: String, val formattedDate: String, val selectedSymptomType: SymptomType, From f7aacef346bca068ab902feff9b16ed2fceee2f7 Mon Sep 17 00:00:00 2001 From: Basler182 Date: Sun, 3 Nov 2024 11:12:07 +0100 Subject: [PATCH 13/13] tested selectedSymptomTypeText Signed-off-by: Basler182 --- .../symptoms/SymptomsUiStateMapperTest.kt | 57 +++++++++++++++++++ .../health/symptoms/SymptomsViewModelTest.kt | 3 + 2 files changed, 60 insertions(+) diff --git a/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsUiStateMapperTest.kt b/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsUiStateMapperTest.kt index 941ac5424..b43e16521 100644 --- a/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsUiStateMapperTest.kt +++ b/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsUiStateMapperTest.kt @@ -1,6 +1,8 @@ package edu.stanford.bdh.engagehf.health.symptoms import com.google.common.truth.Truth.assertThat +import edu.stanford.bdh.engagehf.R +import edu.stanford.spezi.core.design.component.StringResource import edu.stanford.spezi.core.utils.LocaleProvider import io.mockk.every import io.mockk.mockk @@ -125,6 +127,61 @@ class SymptomsUiStateMapperTest { assertThat(successStateOverall.data.chartData[0].xValues.size).isEqualTo(0) } + @Test + fun `mapSymptomsUiState when different symptomTypes returns correct selectedSymptomTypeText`() { + // Given + val symptomScores = listOf( + createSymptomScore(), + ) + + // When + val resultOverall = + symptomsUiStateMapper.mapSymptomsUiState(SymptomType.OVERALL, symptomScores) + val resultPhysical = + symptomsUiStateMapper.mapSymptomsUiState(SymptomType.PHYSICAL_LIMITS, symptomScores) + val resultSocial = + symptomsUiStateMapper.mapSymptomsUiState(SymptomType.SOCIAL_LIMITS, symptomScores) + val resultQuality = + symptomsUiStateMapper.mapSymptomsUiState(SymptomType.QUALITY_OF_LIFE, symptomScores) + val resultSpecific = + symptomsUiStateMapper.mapSymptomsUiState(SymptomType.SYMPTOMS_FREQUENCY, symptomScores) + val resultDizziness = + symptomsUiStateMapper.mapSymptomsUiState(SymptomType.DIZZINESS, symptomScores) + + // Then + assertThat(resultOverall).isInstanceOf(SymptomsUiState.Success::class.java) + assertThat(resultPhysical).isInstanceOf(SymptomsUiState.Success::class.java) + assertThat(resultSocial).isInstanceOf(SymptomsUiState.Success::class.java) + assertThat(resultQuality).isInstanceOf(SymptomsUiState.Success::class.java) + assertThat(resultSpecific).isInstanceOf(SymptomsUiState.Success::class.java) + assertThat(resultDizziness).isInstanceOf(SymptomsUiState.Success::class.java) + val successStateOverall = resultOverall as SymptomsUiState.Success + val successStatePhysical = resultPhysical as SymptomsUiState.Success + val successStateSocial = resultSocial as SymptomsUiState.Success + val successStateQuality = resultQuality as SymptomsUiState.Success + val successStateSpecific = resultSpecific as SymptomsUiState.Success + val successStateDizziness = resultDizziness as SymptomsUiState.Success + assertThat(successStateOverall.data.headerData.selectedSymptomTypeText).isEqualTo( + StringResource(R.string.symptom_type_overall) + ) + assertThat(successStatePhysical.data.headerData.selectedSymptomTypeText).isEqualTo( + StringResource(R.string.symptom_type_physical) + ) + + assertThat(successStateSocial.data.headerData.selectedSymptomTypeText).isEqualTo( + StringResource(R.string.symptom_type_social) + ) + assertThat(successStateQuality.data.headerData.selectedSymptomTypeText).isEqualTo( + StringResource(R.string.symptom_type_quality) + ) + assertThat(successStateSpecific.data.headerData.selectedSymptomTypeText).isEqualTo( + StringResource(R.string.symptom_type_specific) + ) + assertThat(successStateDizziness.data.headerData.selectedSymptomTypeText).isEqualTo( + StringResource(R.string.symptom_type_dizziness) + ) + } + private fun createSymptomScore( year: Int = 2024, month: Int = 8, diff --git a/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModelTest.kt b/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModelTest.kt index fccf55b22..df576d5d4 100644 --- a/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModelTest.kt +++ b/app/src/test/kotlin/edu/stanford/bdh/engagehf/health/symptoms/SymptomsViewModelTest.kt @@ -1,8 +1,10 @@ package edu.stanford.bdh.engagehf.health.symptoms import com.google.common.truth.Truth.assertThat +import edu.stanford.bdh.engagehf.R import edu.stanford.bdh.engagehf.bluetooth.component.AppScreenEvents import edu.stanford.bdh.engagehf.health.HealthRepository +import edu.stanford.spezi.core.design.component.StringResource import edu.stanford.spezi.core.testing.CoroutineTestRule import io.mockk.coEvery import io.mockk.every @@ -132,6 +134,7 @@ class SymptomsViewModelTest { formattedDate = "", formattedValue = "", selectedSymptomType = SymptomType.SYMPTOMS_FREQUENCY, + selectedSymptomTypeText = StringResource(R.string.symptom_type_overall), ) )