Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix #90 don't dismiss failed message action #137

Merged
merged 16 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import edu.stanford.bdh.engagehf.R
import edu.stanford.bdh.engagehf.bluetooth.component.AppScreenEvents
import edu.stanford.bdh.engagehf.bluetooth.data.mapper.BluetoothUiStateMapper
import edu.stanford.bdh.engagehf.bluetooth.data.models.Action
Expand All @@ -27,7 +28,9 @@
import edu.stanford.spezi.core.navigation.Navigator
import edu.stanford.spezi.core.notification.notifier.FirebaseMessage
import edu.stanford.spezi.core.notification.notifier.FirebaseMessage.Companion.FIREBASE_MESSAGE_KEY
import edu.stanford.spezi.core.utils.MessageNotifier
import edu.stanford.spezi.modules.education.EducationNavigationEvent
import edu.stanford.spezi.modules.education.videos.Video
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
Expand All @@ -45,6 +48,7 @@
private val navigator: Navigator,
private val engageEducationRepository: EngageEducationRepository,
private val healthSummaryService: HealthSummaryService,
private val messageNotifier: MessageNotifier,
@ApplicationContext private val context: Context,
) : ViewModel() {
private val logger by speziLogger()
Expand Down Expand Up @@ -79,6 +83,7 @@
it.copy(measurementDialog = dialog)
}
}

is EngageBLEServiceEvent.DeviceDiscovered,
is EngageBLEServiceEvent.DeviceConnected,
is EngageBLEServiceEvent.DevicePaired,
Expand Down Expand Up @@ -218,9 +223,18 @@
messageId: String,
isDismissible: Boolean,
) {
var shouldDismissMessage = isDismissible
when (messagesAction) {
is MessagesAction.HealthSummaryAction -> {
handleHealthSummaryAction(messageId)
handleHealthSummaryAction(messageId).onFailure {
shouldDismissMessage = false
}
}

is MessagesAction.VideoSectionAction -> {
handleVideoSectionAction(messagesAction).onFailure {
shouldDismissMessage = false
}
}

is MessagesAction.MeasurementsAction -> {
Expand All @@ -242,32 +256,27 @@
)
)
}

is MessagesAction.VideoSectionAction -> {
viewModelScope.launch {
handleVideoSectionAction(messagesAction)
}
}
}
if (isDismissible) {
if (shouldDismissMessage) {
messageRepository.completeMessage(messageId = messageId)
}
}

private suspend fun handleVideoSectionAction(messageAction: MessagesAction.VideoSectionAction) {
engageEducationRepository.getVideoBySectionAndVideoId(
private suspend fun handleVideoSectionAction(messageAction: MessagesAction.VideoSectionAction): Result<Video> {

Check warning on line 265 in app/src/main/kotlin/edu/stanford/bdh/engagehf/bluetooth/BluetoothViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/edu/stanford/bdh/engagehf/bluetooth/BluetoothViewModel.kt#L265

Added line #L265 was not covered by tests
return engageEducationRepository.getVideoBySectionAndVideoId(
messageAction.videoSectionVideo.videoSectionId,
messageAction.videoSectionVideo.videoId
).getOrNull()?.let { video ->
navigator.navigateTo(
EducationNavigationEvent.VideoSectionClicked(
video = video
)
).onSuccess { video ->
navigator.navigateTo(EducationNavigationEvent.VideoSectionClicked(video))
}.onFailure {
messageNotifier.notify(
messageId = R.string.error_while_handling_message_action
)
logger.e(it) { "Error while getting video by section and video id" }
}
}

private fun handleHealthSummaryAction(messageId: String) {
private suspend fun handleHealthSummaryAction(messageId: String): Result<Unit> {

Check warning on line 279 in app/src/main/kotlin/edu/stanford/bdh/engagehf/bluetooth/BluetoothViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/edu/stanford/bdh/engagehf/bluetooth/BluetoothViewModel.kt#L279

Added line #L279 was not covered by tests
val setLoading = { loading: Boolean ->
_uiState.update {
it.copy(
Expand All @@ -281,11 +290,10 @@
)
}
}
viewModelScope.launch {
setLoading(true)
healthSummaryService.generateHealthSummaryPdf()
setLoading(false)
}
setLoading(true)
val result = healthSummaryService.generateHealthSummaryPdf()
setLoading(false)
return result
}

public override fun onCleared() {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,5 @@
<string name="specific_symptoms_score_description">This represents the frequency you are experiencing heart-related symptoms. Higher scores mean less frequent symptoms and lower scores mean more frequent symptoms.</string>
<string name="dizziness_score_title">Dizziness Score</string>
<string name="dizziness_score_description">Your dizziness is important to keep track of because dizziness can be a side effect of your heart or your heart medicines.</string>
<string name="error_while_handling_message_action">Error while handling message action.</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.health.connect.client.records.BloodPressureRecord
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.WeightRecord
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.bluetooth.data.mapper.BluetoothUiStateMapper
import edu.stanford.bdh.engagehf.bluetooth.data.models.Action
Expand All @@ -28,6 +29,7 @@ import edu.stanford.bdh.engagehf.navigation.screens.BottomBarItem
import edu.stanford.spezi.core.navigation.Navigator
import edu.stanford.spezi.core.testing.CoroutineTestRule
import edu.stanford.spezi.core.testing.runTestUnconfined
import edu.stanford.spezi.core.utils.MessageNotifier
import edu.stanford.spezi.modules.education.EducationNavigationEvent
import edu.stanford.spezi.modules.education.videos.Video
import io.mockk.Runs
Expand All @@ -53,6 +55,7 @@ class BluetoothViewModelTest {
private val engageEducationRepository = mockk<EngageEducationRepository>(relaxed = true)
private val healthSummaryService = mockk<HealthSummaryService>(relaxed = true)
private val context: Context = mockk(relaxed = true)
private val messageNotifier = mockk<MessageNotifier>(relaxed = true)

private val bleServiceState =
MutableStateFlow<EngageBLEServiceState>(EngageBLEServiceState.Idle)
Expand Down Expand Up @@ -312,6 +315,53 @@ class BluetoothViewModelTest {
coVerify { messageRepository.completeMessage(messageId = messageId) }
}

@Test
fun `it should not dismiss health summary message on error result`() {
// given
val action = Action.MessageItemClicked(message = message)
every {
uiStateMapper.mapMessagesAction(messageAction)
} returns Result.success(MessagesAction.HealthSummaryAction)
coEvery {
healthSummaryService.generateHealthSummaryPdf()
} returns Result.failure(Throwable())
createViewModel()

// when
bluetoothViewModel.onAction(action = action)

// then
coVerify(
exactly = 0
) { messageRepository.completeMessage(messageId = messageId) }
}

@Test
fun `it should not dismiss video section message on error result`() {
// given
val action = Action.MessageItemClicked(message = message)
every {
uiStateMapper.mapMessagesAction(messageAction)
} returns Result.success(MessagesAction.VideoSectionAction(VideoSectionVideo("1", "2")))
coEvery {
engageEducationRepository.getVideoBySectionAndVideoId(any(), any())
} returns Result.failure(Throwable())
createViewModel()

// when
bluetoothViewModel.onAction(action = action)

// then
coVerify(
exactly = 0
) { messageRepository.completeMessage(messageId = messageId) }
coVerify {
messageNotifier.notify(
messageId = R.string.error_while_handling_message_action
)
}
}

@Test
fun `it should handle MeasurementsAction correctly`() {
// given
Expand Down Expand Up @@ -509,6 +559,7 @@ class BluetoothViewModelTest {
engageEducationRepository = engageEducationRepository,
healthSummaryService = healthSummaryService,
context = context,
messageNotifier = messageNotifier
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import edu.stanford.spezi.core.design.component.ComposeContentActivity
import edu.stanford.spezi.core.design.component.StringResource
import edu.stanford.spezi.modules.education.R
import org.junit.Rule
import org.junit.Test

Expand Down Expand Up @@ -48,7 +50,7 @@ class EducationScreenTest {
fun `education screen should display retry button`() {
composeTestRule.activity.setScreen {
EducationScreen(
uiState = UiState.Error("Could not load videos"),
uiState = UiState.Error(StringResource(R.string.failed_to_load_video_sections)),
onAction = {},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
import androidx.hilt.navigation.compose.hiltViewModel
import edu.stanford.spezi.core.design.component.Button
import edu.stanford.spezi.core.design.component.RepeatingLazyColumn
import edu.stanford.spezi.core.design.component.StringResource
import edu.stanford.spezi.core.design.component.VerticalSpacer
import edu.stanford.spezi.core.design.theme.Colors
import edu.stanford.spezi.core.design.theme.Spacings
import edu.stanford.spezi.core.design.theme.SpeziTheme
import edu.stanford.spezi.core.design.theme.ThemePreviews
import edu.stanford.spezi.core.utils.extensions.testIdentifier
import edu.stanford.spezi.modules.education.R
import edu.stanford.spezi.modules.education.videos.component.ExpandableVideoSection
import edu.stanford.spezi.modules.education.videos.component.LoadingVideoCard

Expand Down Expand Up @@ -64,7 +66,7 @@
) {
Column {
Text(
text = uiState.message,
text = uiState.message.text(),
modifier = Modifier.align(Alignment.CenterHorizontally),
color = Colors.error
)
Expand Down Expand Up @@ -114,7 +116,7 @@
PreviewParameterProvider<Pair<UiState, (Action) -> Unit>> {
override val values: Sequence<Pair<UiState, (Action) -> Unit>> = sequenceOf(
Pair(UiState.Loading) {},
Pair(UiState.Error("An error occurred")) {},
Pair(UiState.Error(StringResource(R.string.failed_to_load_video_sections))) {},

Check warning on line 119 in modules/education/src/main/java/edu/stanford/spezi/modules/education/videos/EducationScreen.kt

View check run for this annotation

Codecov / codecov/patch

modules/education/src/main/java/edu/stanford/spezi/modules/education/videos/EducationScreen.kt#L119

Added line #L119 was not covered by tests
Pair(
UiState.Success(
EducationUiState(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.stanford.spezi.modules.education.videos

import edu.stanford.spezi.core.design.component.StringResource
import kotlinx.serialization.Serializable

data class EducationUiState(
Expand All @@ -9,7 +10,7 @@ data class EducationUiState(
sealed interface UiState {
data object Loading : UiState
data class Success(val data: EducationUiState) : UiState
data class Error(val message: String) : UiState
data class Error(val message: StringResource) : UiState
}

data class VideoSection(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import edu.stanford.spezi.core.design.component.StringResource
import edu.stanford.spezi.core.logging.speziLogger
import edu.stanford.spezi.core.navigation.Navigator
import edu.stanford.spezi.modules.education.EducationNavigationEvent
import edu.stanford.spezi.modules.education.R
import edu.stanford.spezi.modules.education.videos.data.repository.EducationRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -17,6 +20,7 @@
private val educationRepository: EducationRepository,
private val navigator: Navigator,
) : ViewModel() {
private val logger by speziLogger()
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow()

Expand All @@ -27,11 +31,12 @@
private fun loadVideoSections() {
viewModelScope.launch {
_uiState.value = UiState.Loading
val result = educationRepository.getVideoSections()
_uiState.value = if (result.isSuccess) {
UiState.Success(EducationUiState(videoSections = result.getOrNull() ?: emptyList()))
} else {
UiState.Error("Failed to load video sections")
educationRepository.getVideoSections().onFailure {
logger.e(it) { "Failed to load video sections" }
_uiState.value =
UiState.Error(StringResource(R.string.failed_to_load_video_sections))

Check warning on line 37 in modules/education/src/main/java/edu/stanford/spezi/modules/education/videos/EducationViewModel.kt

View check run for this annotation

Codecov / codecov/patch

modules/education/src/main/java/edu/stanford/spezi/modules/education/videos/EducationViewModel.kt#L35-L37

Added lines #L35 - L37 were not covered by tests
}.onSuccess { videoSections ->
_uiState.value = UiState.Success(EducationUiState(videoSections = videoSections))
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions modules/education/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="failed_to_load_video_sections">Failed to load video sections</string>
</resources>
Loading