From 57e95dfc1841482662d23e1d7c135d62bde10e03 Mon Sep 17 00:00:00 2001 From: Chilja Gossow <49635654+chiljamgossow@users.noreply.github.com> Date: Fri, 15 Jan 2021 14:36:48 +0100 Subject: [PATCH] Show pending card when offline/ request fails (EXPOSUREAPP-4507) (#2074) * show pending card when offline * dismiss alert dialog when leaving pending result screen * handle errors dialog properly when multiple request are triggered through repeated opening of screen or going to background --- .../ui/homecards/SubmissionStateProvider.kt | 12 +-- .../SubmissionTestResultPendingFragment.kt | 88 +++++++++++-------- .../SubmissionTestResultPendingViewModel.kt | 15 ++-- .../util/ui/LiveDataExtensions.kt | 11 ++- 4 files changed, 72 insertions(+), 54 deletions(-) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt index cbb701de1..474e84b1b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt @@ -68,7 +68,7 @@ class SubmissionStateProvider @Inject constructor( fun isFetching(): Boolean = isDeviceRegistered && when (deviceUiState) { - is NetworkRequestWrapper.RequestFailed -> deviceUiState.error is CwaServerError + is NetworkRequestWrapper.RequestFailed -> false is NetworkRequestWrapper.RequestStarted -> true is NetworkRequestWrapper.RequestIdle -> true else -> false @@ -112,11 +112,13 @@ class SubmissionStateProvider @Inject constructor( } fun isPending(): Boolean = - deviceUiState.withSuccess(false) { - when (it) { - DeviceUIState.PAIRED_ERROR, DeviceUIState.PAIRED_NO_RESULT -> true - else -> false + when (deviceUiState) { + is NetworkRequestWrapper.RequestFailed -> true + is NetworkRequestWrapper.RequestSuccessful -> { + deviceUiState.data == DeviceUIState.PAIRED_ERROR || + deviceUiState.data == DeviceUIState.PAIRED_NO_RESULT } + else -> false } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt index 2a2949b43..38721c989 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt @@ -1,9 +1,9 @@ package de.rki.coronawarnapp.ui.submission.testresult.pending -import android.app.AlertDialog import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent +import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultPendingBinding @@ -12,11 +12,11 @@ import de.rki.coronawarnapp.exception.http.CwaServerError import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat import de.rki.coronawarnapp.util.DialogHelper -import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withFailure -import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess +import de.rki.coronawarnapp.util.NetworkRequestWrapper import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.doNavigate import de.rki.coronawarnapp.util.ui.observe2 +import de.rki.coronawarnapp.util.ui.observeOnce import de.rki.coronawarnapp.util.ui.popBackStack import de.rki.coronawarnapp.util.ui.setInvisible import de.rki.coronawarnapp.util.ui.viewBindingLazy @@ -33,6 +33,8 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio private var skipInitialTestResultRefresh = false + private var errorDialog: AlertDialog? = null + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -41,19 +43,10 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio } pendingViewModel.testState.observe2(this) { result -> - result.deviceUiState.withFailure { - if (it is CwaWebException) { - DialogHelper.showDialog(buildErrorDialog(it)) - } - } - - val hasResult = result.deviceUiState.withSuccess(false) { true } - + val hasResult = result.deviceUiState is NetworkRequestWrapper.RequestSuccessful binding.apply { submissionTestResultSection.setTestResultSection(result.deviceUiState, result.testResultReceivedDate) - submissionTestResultSpinner.setInvisible(hasResult) - submissionTestResultContent.setInvisible(!hasResult) buttonContainer.setInvisible(!hasResult) } @@ -98,8 +91,16 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio super.onResume() binding.submissionTestResultContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) pendingViewModel.refreshDeviceUIState(refreshTestResult = !skipInitialTestResultRefresh) - skipInitialTestResultRefresh = false + pendingViewModel.cwaWebExceptionLiveData.observeOnce(this.viewLifecycleOwner) { exception -> + handleError(exception) + } + } + + override fun onPause() { + pendingViewModel.cwaWebExceptionLiveData.removeObservers(this.viewLifecycleOwner) + errorDialog?.dismiss() + super.onPause() } private fun removeTestAfterConfirmation() { @@ -118,33 +119,42 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio } } + private fun handleError(exception: CwaWebException) { + errorDialog = when (exception) { + is CwaClientError, is CwaServerError -> { + DialogHelper.showDialog(buildErrorDialog(exception)) + } + else -> { + DialogHelper.showDialog(genericErrorDialog) + } + } + } + private fun navigateToMainScreen() { popBackStack() } - private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance { - return when (exception) { - is CwaClientError, is CwaServerError -> DialogHelper.DialogInstance( - requireActivity(), - R.string.submission_error_dialog_web_generic_error_title, - getString( - R.string.submission_error_dialog_web_generic_network_error_body, - exception.statusCode - ), - R.string.submission_error_dialog_web_generic_error_button_positive, - null, - true, - ::navigateToMainScreen - ) - else -> DialogHelper.DialogInstance( - requireActivity(), - R.string.submission_error_dialog_web_generic_error_title, - R.string.submission_error_dialog_web_generic_error_body, - R.string.submission_error_dialog_web_generic_error_button_positive, - null, - true, - ::navigateToMainScreen - ) - } - } + private fun buildErrorDialog(exception: CwaWebException) = DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_error_dialog_web_generic_error_title, + getString( + R.string.submission_error_dialog_web_generic_network_error_body, + exception.statusCode + ), + R.string.submission_error_dialog_web_generic_error_button_positive, + null, + true, + ::navigateToMainScreen + ) + + private val genericErrorDialog: DialogHelper.DialogInstance + get() = DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_error_dialog_web_generic_error_title, + R.string.submission_error_dialog_web_generic_error_body, + R.string.submission_error_dialog_web_generic_error_button_positive, + null, + true, + ::navigateToMainScreen + ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt index 6149c922a..b77a82130 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt @@ -1,9 +1,9 @@ package de.rki.coronawarnapp.ui.submission.testresult.pending -import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import androidx.navigation.NavDirections import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.notification.TestResultNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState @@ -16,7 +16,9 @@ import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -57,7 +59,8 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( testResultReceivedDate = resultDate ) } - val testState: LiveData<TestResultUIState> = testResultFlow + + val testState = testResultFlow .onEach { testResultUIState -> testResultUIState.deviceUiState.withSuccess { deviceState -> when (deviceState) { @@ -85,9 +88,10 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( } .asLiveData(context = dispatcherProvider.Default) - fun onTestOpened() { - submissionRepository.setViewedTestResult() - } + val cwaWebExceptionLiveData = submissionRepository.deviceUIStateFlow + .filterIsInstance<NetworkRequestWrapper.RequestFailed<DeviceUIState, CwaWebException>>() + .map { it.error } + .asLiveData() fun observeTestResultToSchedulePositiveTestResultReminder() = launch { submissionRepository.deviceUIStateFlow @@ -103,7 +107,6 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( Timber.d("deregisterTestFromDevice()") launch { submissionRepository.removeTestFromDevice() - routeToScreen.postValue(null) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt index f31e15bfc..e1d652cd0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt @@ -4,15 +4,18 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer +import androidx.lifecycle.observe fun <T> LiveData<T>.observe2(fragment: Fragment, callback: (T) -> Unit) { - observe(fragment.viewLifecycleOwner, { callback.invoke(it) }) + observe(fragment.viewLifecycleOwner) { + callback.invoke(it) + } } -fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner? = null, observer: Observer<T>) { +fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner? = null, onValueChanged: (t: T) -> Unit) { val internalObserver = object : Observer<T> { - override fun onChanged(t: T?) { - observer.onChanged(t) + override fun onChanged(t: T) { + onValueChanged(t) removeObserver(this) } } -- GitLab