From df7a5fbb314675010062660824f7c5a6bff5e3b9 Mon Sep 17 00:00:00 2001
From: Kolya Opahle <k.opahle@sap.com>
Date: Mon, 16 Nov 2020 18:23:30 +0100
Subject: [PATCH] =?UTF-8?q?Cannot=20delete=20tests=20older=20than=2021=20d?=
 =?UTF-8?q?ays=20-=20DE:=20Tests=20die=20=C3=A4lter=20als=2021=20Tage=20si?=
 =?UTF-8?q?nd,=20k=C3=B6nnen=20nicht=20gel=C3=B6scht=20werden=20(EXPOSUREA?=
 =?UTF-8?q?PP-2398)=20(#1586)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* 1. Replaced redundant uiStateState during test result fetch with a NetworkRequestWrapper that is able to represent multiple states. This allows for better request state handling throughout the app
2. Completely removed the SubmissionViewModel
3. Failure to fetch the test should now always display the invalid test card on the home screen

Signed-off-by: Kolya Opahle <k.opahle@sap.com>

* Keep displaying fetching card on server errors as this usually does not indicate any problems with the test itself

Signed-off-by: Kolya Opahle <k.opahle@sap.com>

* Fixing unit tests (mostly just syntax, one test case changed due to new card)

Signed-off-by: Kolya Opahle <k.opahle@sap.com>

* Promoted unknown host exception to server exception

Co-authored-by: chris-cwa <69595386+chris-cwa@users.noreply.github.com>
Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com>
---
 .../ui/TestRiskLevelCalculationFragment.kt    |   7 +-
 ...iskLevelCalculationFragmentCWAViewModel.kt |   6 +
 .../fragment_test_risk_level_calculation.xml  |   6 +-
 .../exception/http/CwaWebException.kt         |   2 +-
 .../rki/coronawarnapp/http/HttpErrorParser.kt |   3 +
 .../storage/SubmissionRepository.kt           |  34 ++-
 .../ui/main/home/SubmissionCardState.kt       | 137 +++++++-----
 .../main/home/SubmissionCardsStateProvider.kt |   8 +-
 .../SubmissionTestResultFragment.kt           |  11 +-
 .../SubmissionTestResultViewModel.kt          |  25 ++-
 .../testresult/TestResultUIState.kt           |   5 +-
 .../ui/viewmodel/SubmissionViewModel.kt       |  12 --
 .../util/NetworkRequestWrapper.kt             |  30 +++
 .../formatter/FormatterSubmissionHelper.kt    | 139 ++++++------
 .../fragment_submission_symptom_calendar.xml  |   4 -
 .../fragment_submission_symptom_intro.xml     |   4 -
 .../fragment_submission_test_result.xml       |   4 +-
 ...de_submission_symptom_length_selection.xml |   4 -
 .../res/layout/include_test_result_card.xml   |   2 +-
 .../main/home/HomeFragmentViewModelTest.kt    |   6 +-
 .../main/home/SubmissionCardStateTest.kt      |  21 +-
 .../home/SubmissionCardsStateProviderTest.kt  |  11 +-
 .../FormatterSubmissionHelperTest.kt          | 197 ++++++++++--------
 23 files changed, 386 insertions(+), 292 deletions(-)
 delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/NetworkRequestWrapper.kt

diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt
index 0867b6e6b..f9834cdc7 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragment.kt
@@ -15,7 +15,6 @@ import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange
 import de.rki.coronawarnapp.sharing.ExposureSharingService
 import de.rki.coronawarnapp.test.menu.ui.TestMenuItem
 import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
-import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.ui.observe2
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
@@ -39,7 +38,6 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le
     )
 
     private val settingsViewModel: SettingsViewModel by activityViewModels()
-    private val submissionViewModel: SubmissionViewModel by activityViewModels()
 
     private val binding: FragmentTestRiskLevelCalculationBinding by viewBindingLazy()
 
@@ -51,7 +49,10 @@ class TestRiskLevelCalculationFragment : Fragment(R.layout.fragment_test_risk_le
         }
 
         binding.settingsViewModel = settingsViewModel
-        binding.submissionViewModel = submissionViewModel
+
+        vm.showRiskStatusCard.observe2(this) {
+            binding.showRiskStatusCard = it
+        }
 
         binding.buttonRetrieveDiagnosisKeys.setOnClickListener { vm.retrieveDiagnosisKeys() }
         binding.buttonProvideKeyViaQr.setOnClickListener { vm.scanLocalQRCodeAndProvide() }
diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt
index 765ac237e..0294d6b5b 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt
@@ -22,11 +22,13 @@ import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange
 import de.rki.coronawarnapp.storage.AppDatabase
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.storage.RiskLevelRepository
+import de.rki.coronawarnapp.storage.SubmissionRepository
 import de.rki.coronawarnapp.task.TaskController
 import de.rki.coronawarnapp.task.common.DefaultTaskRequest
 import de.rki.coronawarnapp.task.submitBlocking
 import de.rki.coronawarnapp.ui.tracing.card.TracingCardStateProvider
 import de.rki.coronawarnapp.util.KeyFileHelper
+import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.di.AppContext
 import de.rki.coronawarnapp.util.di.AppInjector
@@ -35,6 +37,7 @@ import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.sample
 import kotlinx.coroutines.withContext
 import timber.log.Timber
@@ -63,6 +66,9 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor(
     val riskLevelResetEvent = SingleLiveEvent<Unit>()
     val apiKeysProvidedEvent = SingleLiveEvent<DiagnosisKeyProvidedEvent>()
     val riskScoreState = MutableLiveData<RiskScoreState>(RiskScoreState())
+    val showRiskStatusCard = SubmissionRepository.deviceUIStateFlow.map {
+        it.withSuccess(false) { true }
+    }.asLiveData(dispatcherProvider.Default)
 
     val tracingCardState = tracingCardStateProvider.state
         .sample(150L)
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml
index 9e29d88d4..a58a1ff43 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_risk_level_calculation.xml
@@ -11,8 +11,8 @@
         <import type="de.rki.coronawarnapp.util.formatter.FormatterSubmissionHelper" />
 
         <variable
-            name="submissionViewModel"
-            type="de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel" />
+            name="showRiskStatusCard"
+            type="Boolean" />
 
         <variable
             name="settingsViewModel"
@@ -48,7 +48,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_normal"
-                android:visibility="@{FormatterSubmissionHelper.formatShowRiskStatusCard(submissionViewModel.deviceUiState)}"
+                gone="@{showRiskStatusCard == null || !showRiskStatusCard}"
                 android:focusable="true"
                 android:backgroundTint="@{tracingCard.getRiskInfoContainerBackgroundTint(context)}"
                 android:backgroundTintMode="src_over">
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/http/CwaWebException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/http/CwaWebException.kt
index 4fa8b6eff..fc89eff46 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/http/CwaWebException.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/http/CwaWebException.kt
@@ -27,7 +27,6 @@ open class CwaSuccessResponseWithCodeMismatchNotSupportedError(statusCode: Int)
 open class CwaInformationalNotSupportedError(statusCode: Int) : CwaWebException(statusCode)
 open class CwaRedirectNotSupportedError(statusCode: Int) : CwaWebException(statusCode)
 
-class CwaUnknownHostException : CwaWebException(901)
 class BadRequestException : CwaClientError(400)
 class UnauthorizedException : CwaClientError(401)
 class ForbiddenException : CwaClientError(403)
@@ -44,5 +43,6 @@ class ServiceUnavailableException : CwaServerError(503)
 class GatewayTimeoutException : CwaServerError(504)
 class HTTPVersionNotSupported : CwaServerError(505)
 class NetworkAuthenticationRequiredException : CwaServerError(511)
+class CwaUnknownHostException : CwaServerError(597)
 class NetworkReadTimeoutException : CwaServerError(598)
 class NetworkConnectTimeoutException : CwaServerError(599)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt
index bc347abb9..1061abc7e 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/HttpErrorParser.kt
@@ -26,6 +26,7 @@ import de.rki.coronawarnapp.exception.http.UnauthorizedException
 import de.rki.coronawarnapp.exception.http.UnsupportedMediaTypeException
 import okhttp3.Interceptor
 import okhttp3.Response
+import java.net.SocketTimeoutException
 import java.net.UnknownHostException
 import javax.net.ssl.HttpsURLConnection
 
@@ -66,6 +67,8 @@ class HttpErrorParser : Interceptor {
                     throw CwaWebException(code)
                 }
             }
+        } catch (err: SocketTimeoutException) {
+            throw NetworkConnectTimeoutException()
         } catch (err: UnknownHostException) {
             throw CwaUnknownHostException()
         }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt
index b601e6d77..62ae3efc8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt
@@ -1,16 +1,13 @@
 package de.rki.coronawarnapp.storage
 
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.asLiveData
 import de.rki.coronawarnapp.exception.ExceptionCategory
 import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
 import de.rki.coronawarnapp.exception.http.CwaWebException
 import de.rki.coronawarnapp.exception.reporting.report
 import de.rki.coronawarnapp.service.submission.SubmissionService
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
 import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.Event
+import de.rki.coronawarnapp.util.NetworkRequestWrapper
+import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
 import de.rki.coronawarnapp.util.di.AppInjector
 import de.rki.coronawarnapp.util.formatter.TestResult
 import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
@@ -26,15 +23,12 @@ object SubmissionRepository {
         AppInjector.component.appScope
     }
 
-    val uiStateStateFlowInternal = MutableStateFlow(ApiRequestState.IDLE)
-    val uiStateStateFlow: Flow<ApiRequestState> = uiStateStateFlowInternal
-    val uiStateState: LiveData<ApiRequestState> = uiStateStateFlow.asLiveData()
-
     private val testResultReceivedDateFlowInternal = MutableStateFlow(Date())
     val testResultReceivedDateFlow: Flow<Date> = testResultReceivedDateFlowInternal
 
-    private val deviceUIStateFlowInternal = MutableStateFlow(DeviceUIState.UNPAIRED)
-    val deviceUIStateFlow: Flow<DeviceUIState> = deviceUIStateFlowInternal
+    private val deviceUIStateFlowInternal =
+        MutableStateFlow<NetworkRequestWrapper<DeviceUIState, Throwable>>(NetworkRequestWrapper.RequestIdle)
+    val deviceUIStateFlow: Flow<NetworkRequestWrapper<DeviceUIState, Throwable>> = deviceUIStateFlowInternal
 
     private val testResultFlow = MutableStateFlow<TestResult?>(null)
 
@@ -80,37 +74,33 @@ object SubmissionRepository {
         LocalData.teletan(teletan)
     }
 
-    private val uiStateErrorInternal = MutableLiveData<Event<CwaWebException>>(null)
-    val uiStateError: LiveData<Event<CwaWebException>> = uiStateErrorInternal
-
     // TODO this should be more UI agnostic
     fun refreshDeviceUIState(refreshTestResult: Boolean = true) {
         var refresh = refreshTestResult
 
-        deviceUIStateFlowInternal.value.let {
+        deviceUIStateFlowInternal.value.withSuccess {
             if (it != DeviceUIState.PAIRED_NO_RESULT && it != DeviceUIState.UNPAIRED) {
                 refresh = false
                 Timber.d("refreshDeviceUIState: Change refresh, state ${it.name} doesn't require refresh")
             }
         }
 
-        uiStateStateFlowInternal.value = ApiRequestState.STARTED
+        deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestStarted
+
         appScope.launch {
             try {
                 refreshUIState(refresh)
-                uiStateStateFlowInternal.value = ApiRequestState.SUCCESS
             } catch (err: CwaWebException) {
-                uiStateErrorInternal.postValue(Event(err))
-                uiStateStateFlowInternal.value = ApiRequestState.FAILED
+                deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestFailed(err)
             } catch (err: Exception) {
+                deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestFailed(err)
                 err.report(ExceptionCategory.INTERNAL)
             }
         }
     }
 
     fun reset() {
-        uiStateStateFlowInternal.value = ApiRequestState.IDLE
-        deviceUIStateFlowInternal.value = DeviceUIState.UNPAIRED
+        deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestIdle
     }
 
     // TODO this should be more UI agnostic
@@ -132,6 +122,6 @@ object SubmissionRepository {
                 }
             }
         }
-        deviceUIStateFlowInternal.value = uiState
+        deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestSuccessful(uiState)
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardState.kt
index 6592ad0ba..517a077b3 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardState.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardState.kt
@@ -3,7 +3,7 @@ package de.rki.coronawarnapp.ui.main.home
 import android.content.Context
 import android.graphics.drawable.Drawable
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
+import de.rki.coronawarnapp.exception.http.CwaServerError
 import de.rki.coronawarnapp.util.DeviceUIState
 import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_ERROR
 import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_NEGATIVE
@@ -12,72 +12,113 @@ import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE
 import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE_TELETAN
 import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_REDEEMED
 import de.rki.coronawarnapp.util.DeviceUIState.SUBMITTED_FINAL
+import de.rki.coronawarnapp.util.NetworkRequestWrapper
+import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
 
 data class SubmissionCardState(
-    val deviceUiState: DeviceUIState,
-    val isDeviceRegistered: Boolean,
-    val uiStateState: ApiRequestState
+    val deviceUiState: NetworkRequestWrapper<DeviceUIState, Throwable>,
+    val isDeviceRegistered: Boolean
 ) {
 
-    fun isRiskCardVisible(): Boolean = deviceUiState != PAIRED_POSITIVE &&
-        deviceUiState != PAIRED_POSITIVE_TELETAN &&
-        deviceUiState != SUBMITTED_FINAL
+    fun isRiskCardVisible(): Boolean =
+        deviceUiState.withSuccess(true) {
+            when (it) {
+                PAIRED_POSITIVE, PAIRED_POSITIVE_TELETAN, SUBMITTED_FINAL -> false
+                else -> true
+            }
+        }
 
     fun isUnregisteredCardVisible(): Boolean = !isDeviceRegistered
 
     fun isFetchingCardVisible(): Boolean =
-        isDeviceRegistered && (uiStateState == ApiRequestState.STARTED || uiStateState == ApiRequestState.FAILED)
+        isDeviceRegistered && when (deviceUiState) {
+            is NetworkRequestWrapper.RequestFailed -> deviceUiState.error is CwaServerError
+            is NetworkRequestWrapper.RequestStarted -> true
+            else -> false
+        }
 
     fun isFailedCardVisible(): Boolean =
-        isDeviceRegistered && uiStateState == ApiRequestState.SUCCESS && deviceUiState == PAIRED_REDEEMED
+        isDeviceRegistered && when (deviceUiState) {
+            is NetworkRequestWrapper.RequestFailed -> deviceUiState.error !is CwaServerError
+            is NetworkRequestWrapper.RequestSuccessful -> deviceUiState.data == PAIRED_REDEEMED
+            else -> false
+        }
 
-    fun isPositiveSubmissionCardVisible(): Boolean = uiStateState == ApiRequestState.SUCCESS &&
-        (deviceUiState == PAIRED_POSITIVE ||
-            deviceUiState == PAIRED_POSITIVE_TELETAN)
+    fun isPositiveSubmissionCardVisible(): Boolean =
+        deviceUiState.withSuccess(false) {
+            when (it) {
+                PAIRED_POSITIVE, PAIRED_POSITIVE_TELETAN -> true
+                else -> false
+            }
+        }
 
     fun isSubmissionDoneCardVisible(): Boolean =
-        uiStateState == ApiRequestState.SUCCESS && deviceUiState == SUBMITTED_FINAL
+        when (deviceUiState) {
+            is NetworkRequestWrapper.RequestSuccessful -> deviceUiState.data == SUBMITTED_FINAL
+            else -> false
+        }
 
     fun isContentCardVisible(): Boolean =
-        uiStateState == ApiRequestState.SUCCESS && (deviceUiState == PAIRED_ERROR ||
-            deviceUiState == PAIRED_NEGATIVE ||
-            deviceUiState == PAIRED_NO_RESULT)
+        deviceUiState.withSuccess(false) {
+            when (it) {
+                PAIRED_ERROR, PAIRED_NEGATIVE, PAIRED_NO_RESULT -> true
+                else -> false
+            }
+        }
 
-    fun getContentCardTitleText(c: Context): String = when (deviceUiState) {
-        PAIRED_ERROR, PAIRED_REDEEMED, PAIRED_NEGATIVE -> R.string.submission_status_card_title_available
-        PAIRED_NO_RESULT -> R.string.submission_status_card_title_pending
-        else -> R.string.submission_status_card_title_pending
-    }.let { c.getString(it) }
+    fun getContentCardTitleText(c: Context): String =
+        deviceUiState.withSuccess(R.string.submission_status_card_title_pending) {
+            when (it) {
+                PAIRED_ERROR, PAIRED_REDEEMED, PAIRED_NEGATIVE -> R.string.submission_status_card_title_available
+                PAIRED_NO_RESULT -> R.string.submission_status_card_title_pending
+                else -> R.string.submission_status_card_title_pending
+            }
+        }.let { c.getString(it) }
 
-    fun getContentCardSubTitleText(c: Context): String = when (deviceUiState) {
-        PAIRED_NEGATIVE -> R.string.submission_status_card_subtitle_negative
-        PAIRED_ERROR, PAIRED_REDEEMED -> R.string.submission_status_card_subtitle_invalid
-        else -> null
-    }?.let { c.getString(it) } ?: ""
+    fun getContentCardSubTitleText(c: Context): String =
+        deviceUiState.withSuccess(null) {
+            when (it) {
+                PAIRED_NEGATIVE -> R.string.submission_status_card_subtitle_negative
+                PAIRED_ERROR, PAIRED_REDEEMED -> R.string.submission_status_card_subtitle_invalid
+                else -> null
+            }
+        }?.let { c.getString(it) } ?: ""
 
-    fun getContentCardSubTitleTextColor(c: Context): Int = when (deviceUiState) {
-        PAIRED_NEGATIVE -> R.color.colorTextSemanticGreen
-        PAIRED_ERROR, PAIRED_REDEEMED -> R.color.colorTextSemanticNeutral
-        else -> R.color.colorTextPrimary1
-    }.let { c.getColor(it) }
+    fun getContentCardSubTitleTextColor(c: Context): Int =
+        deviceUiState.withSuccess(R.color.colorTextPrimary1) {
+            when (it) {
+                PAIRED_NEGATIVE -> R.color.colorTextSemanticGreen
+                PAIRED_ERROR, PAIRED_REDEEMED -> R.color.colorTextSemanticNeutral
+                else -> R.color.colorTextPrimary1
+            }
+        }.let { c.getColor(it) }
 
-    fun isContentCardStatusTextVisible(): Boolean = when (deviceUiState) {
-        PAIRED_NEGATIVE, PAIRED_REDEEMED, PAIRED_ERROR -> true
-        else -> false
-    }
+    fun isContentCardStatusTextVisible(): Boolean =
+        deviceUiState.withSuccess(false) {
+            when (it) {
+                PAIRED_NEGATIVE, PAIRED_REDEEMED, PAIRED_ERROR -> true
+                else -> false
+            }
+        }
 
-    fun getContentCardBodyText(c: Context): String = when (deviceUiState) {
-        PAIRED_ERROR, PAIRED_REDEEMED -> R.string.submission_status_card_body_invalid
-        PAIRED_NEGATIVE -> R.string.submission_status_card_body_negative
-        PAIRED_NO_RESULT -> R.string.submission_status_card_body_pending
-        else -> R.string.submission_status_card_body_pending
-    }.let { c.getString(it) }
+    fun getContentCardBodyText(c: Context): String =
+        deviceUiState.withSuccess(R.string.submission_status_card_body_pending) {
+            when (it) {
+                PAIRED_ERROR, PAIRED_REDEEMED -> R.string.submission_status_card_body_invalid
+                PAIRED_NEGATIVE -> R.string.submission_status_card_body_negative
+                PAIRED_NO_RESULT -> R.string.submission_status_card_body_pending
+                else -> R.string.submission_status_card_body_pending
+            }
+        }.let { c.getString(it) }
 
-    fun getContentCardIcon(c: Context): Drawable? = when (deviceUiState) {
-        PAIRED_NO_RESULT -> R.drawable.ic_main_illustration_pending
-        PAIRED_POSITIVE, PAIRED_POSITIVE_TELETAN -> R.drawable.ic_main_illustration_pending
-        PAIRED_NEGATIVE -> R.drawable.ic_main_illustration_negative
-        PAIRED_ERROR, PAIRED_REDEEMED -> R.drawable.ic_main_illustration_invalid
-        else -> R.drawable.ic_main_illustration_invalid
-    }.let { c.getDrawable(it) }
+    fun getContentCardIcon(c: Context): Drawable? =
+        deviceUiState.withSuccess(R.drawable.ic_main_illustration_invalid) {
+            when (it) {
+                PAIRED_NO_RESULT -> R.drawable.ic_main_illustration_pending
+                PAIRED_POSITIVE, PAIRED_POSITIVE_TELETAN -> R.drawable.ic_main_illustration_pending
+                PAIRED_NEGATIVE -> R.drawable.ic_main_illustration_negative
+                PAIRED_ERROR, PAIRED_REDEEMED -> R.drawable.ic_main_illustration_invalid
+                else -> R.drawable.ic_main_illustration_invalid
+            }
+        }.let { c.getDrawable(it) }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardsStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardsStateProvider.kt
index 087c1e1f7..9fc9fa976 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardsStateProvider.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardsStateProvider.kt
@@ -3,8 +3,6 @@ package de.rki.coronawarnapp.ui.main.home
 import dagger.Reusable
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.storage.SubmissionRepository
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
-import de.rki.coronawarnapp.util.DeviceUIState
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.onCompletion
@@ -17,12 +15,10 @@ import javax.inject.Inject
 class SubmissionCardsStateProvider @Inject constructor() {
 
     val state: Flow<SubmissionCardState> = combine(
-        SubmissionRepository.deviceUIStateFlow,
-        SubmissionRepository.uiStateStateFlow
+        SubmissionRepository.deviceUIStateFlow
     ) { args ->
         SubmissionCardState(
-            deviceUiState = args[0] as DeviceUIState,
-            uiStateState = args[1] as ApiRequestState,
+            deviceUiState = args[0],
             isDeviceRegistered = LocalData.registrationToken() != null
         )
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt
index a1d42d396..0de5f0efa 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt
@@ -15,8 +15,8 @@ import de.rki.coronawarnapp.exception.http.CwaWebException
 import de.rki.coronawarnapp.storage.SubmissionRepository
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
 import de.rki.coronawarnapp.util.DialogHelper
+import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withFailure
 import de.rki.coronawarnapp.util.di.AutoInject
-import de.rki.coronawarnapp.util.observeEvent
 import de.rki.coronawarnapp.util.ui.doNavigate
 import de.rki.coronawarnapp.util.ui.observe2
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
@@ -79,6 +79,11 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_
 
         viewModel.uiState.observe2(this) {
             binding.uiState = it
+            it.deviceUiState.withFailure {
+                if (it is CwaWebException) {
+                    DialogHelper.showDialog(buildErrorDialog(it))
+                }
+            }
         }
 
         // registers callback when the os level back is pressed
@@ -99,10 +104,6 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_
             DialogHelper.showDialog(tracingRequiredDialog)
         }
 
-        viewModel.uiStateError.observeEvent(viewLifecycleOwner) {
-            DialogHelper.showDialog(buildErrorDialog(it))
-        }
-
         viewModel.showRedeemedTokenWarning.observe2(this) {
             val dialog = DialogHelper.DialogInstance(
                 requireActivity(),
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt
index 97586ba50..370ecbf29 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt
@@ -3,7 +3,6 @@ package de.rki.coronawarnapp.ui.submission.testresult
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.asLiveData
 import com.squareup.inject.assisted.AssistedInject
-import de.rki.coronawarnapp.exception.http.CwaWebException
 import de.rki.coronawarnapp.nearby.ENFClient
 import de.rki.coronawarnapp.notification.TestResultNotificationService
 import de.rki.coronawarnapp.service.submission.SubmissionService
@@ -12,7 +11,7 @@ import de.rki.coronawarnapp.storage.SubmissionRepository
 import de.rki.coronawarnapp.submission.Symptoms
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
 import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.Event
+import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
 import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
@@ -37,20 +36,22 @@ class SubmissionTestResultViewModel @AssistedInject constructor(
     private val tokenErrorMutex = Mutex()
 
     val uiState: LiveData<TestResultUIState> = combineTransform(
-        SubmissionRepository.uiStateStateFlow,
         SubmissionRepository.deviceUIStateFlow,
         SubmissionRepository.testResultReceivedDateFlow
-    ) { apiRequestState, deviceUiState, resultDate ->
+    ) { deviceUiState, resultDate ->
 
         tokenErrorMutex.withLock {
-            if (!wasRedeemedTokenErrorShown && deviceUiState == DeviceUIState.PAIRED_REDEEMED) {
-                wasRedeemedTokenErrorShown = true
-                showRedeemedTokenWarning.postValue(Unit)
+            if (!wasRedeemedTokenErrorShown) {
+                deviceUiState.withSuccess {
+                    if (it == DeviceUIState.PAIRED_REDEEMED) {
+                        wasRedeemedTokenErrorShown = true
+                        showRedeemedTokenWarning.postValue(Unit)
+                    }
+                }
             }
         }
 
         TestResultUIState(
-            apiRequestState = apiRequestState,
             deviceUiState = deviceUiState,
             testResultReceivedDate = resultDate
         ).let { emit(it) }
@@ -58,11 +59,13 @@ class SubmissionTestResultViewModel @AssistedInject constructor(
 
     suspend fun observeTestResultToSchedulePositiveTestResultReminder() =
         SubmissionRepository.deviceUIStateFlow
-            .first { it == DeviceUIState.PAIRED_POSITIVE || it == DeviceUIState.PAIRED_POSITIVE_TELETAN }
+            .first { request ->
+                request.withSuccess(false) {
+                    it == DeviceUIState.PAIRED_POSITIVE || it == DeviceUIState.PAIRED_POSITIVE_TELETAN
+                }
+            }
             .also { testResultNotificationService.schedulePositiveTestResultReminder() }
 
-    val uiStateError: LiveData<Event<CwaWebException>> = SubmissionRepository.uiStateError
-
     fun onBackPressed() {
         routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity)
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/TestResultUIState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/TestResultUIState.kt
index b1909eb80..a34a02cad 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/TestResultUIState.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/TestResultUIState.kt
@@ -1,11 +1,10 @@
 package de.rki.coronawarnapp.ui.submission.testresult
 
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
 import de.rki.coronawarnapp.util.DeviceUIState
+import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import java.util.Date
 
 data class TestResultUIState(
-    val apiRequestState: ApiRequestState,
-    val deviceUiState: DeviceUIState,
+    val deviceUiState: NetworkRequestWrapper<DeviceUIState, Throwable>,
     val testResultReceivedDate: Date?
 )
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt
deleted file mode 100644
index 6b668e6c4..000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package de.rki.coronawarnapp.ui.viewmodel
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.asLiveData
-import de.rki.coronawarnapp.storage.SubmissionRepository
-import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
-
-class SubmissionViewModel : CWAViewModel() {
-
-    val deviceUiState: LiveData<DeviceUIState> = SubmissionRepository.deviceUIStateFlow.asLiveData()
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/NetworkRequestWrapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/NetworkRequestWrapper.kt
new file mode 100644
index 000000000..47d1c57e4
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/NetworkRequestWrapper.kt
@@ -0,0 +1,30 @@
+package de.rki.coronawarnapp.util
+
+sealed class NetworkRequestWrapper<out T, out U> {
+    object RequestIdle : NetworkRequestWrapper<Nothing, Nothing>()
+    object RequestStarted : NetworkRequestWrapper<Nothing, Nothing>()
+    data class RequestSuccessful<T, U>(val data: T) : NetworkRequestWrapper<T, U>()
+    data class RequestFailed<T, U>(val error: U) : NetworkRequestWrapper<T, U>()
+
+    companion object {
+        fun <T, U, W> NetworkRequestWrapper<T, U>?.withSuccess(without: W, block: (data: T) -> W): W {
+            return if (this is RequestSuccessful) {
+                block(this.data)
+            } else {
+                without
+            }
+        }
+
+        fun <T, U> NetworkRequestWrapper<T, U>?.withSuccess(block: (data: T) -> Unit) {
+            if (this is RequestSuccessful) {
+                block(this.data)
+            }
+        }
+
+        fun <T, U> NetworkRequestWrapper<T, U>?.withFailure(block: (error: U) -> Unit) {
+            if (this is RequestFailed) {
+                block(this.error)
+            }
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt
index 156f8efee..e573e2023 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt
@@ -8,11 +8,13 @@ import android.text.Spannable
 import android.text.SpannableString
 import android.text.SpannableStringBuilder
 import android.text.style.ForegroundColorSpan
+import android.view.View
 import de.rki.coronawarnapp.CoronaWarnApplication
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.submission.Symptoms
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
 import de.rki.coronawarnapp.util.DeviceUIState
+import de.rki.coronawarnapp.util.NetworkRequestWrapper
+import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUIFormat
 import java.util.Date
 import java.util.Locale
@@ -49,33 +51,37 @@ fun isEnableSymptomCalendarButtonByState(currentState: Symptoms.StartOf?): Boole
     return currentState != null
 }
 
-fun formatTestResultSpinnerVisible(uiStateState: ApiRequestState?): Int =
-    formatVisibility(uiStateState != ApiRequestState.SUCCESS)
-
-fun formatTestResultVisible(uiStateState: ApiRequestState?): Int =
-    formatVisibility(uiStateState == ApiRequestState.SUCCESS)
-
-fun formatTestResultStatusText(uiState: DeviceUIState?): String {
-    val appContext = CoronaWarnApplication.getAppContext()
-    return when (uiState) {
-        DeviceUIState.PAIRED_NEGATIVE -> appContext.getString(R.string.test_result_card_status_negative)
-        DeviceUIState.PAIRED_POSITIVE,
-        DeviceUIState.PAIRED_POSITIVE_TELETAN -> appContext.getString(R.string.test_result_card_status_positive)
-        else -> appContext.getString(R.string.test_result_card_status_invalid)
+fun formatTestResultSpinnerVisible(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Int =
+    uiState.withSuccess(View.VISIBLE) {
+        View.GONE
     }
-}
 
-fun formatTestResultStatusColor(uiState: DeviceUIState?): Int {
-    val appContext = CoronaWarnApplication.getAppContext()
-    return when (uiState) {
-        DeviceUIState.PAIRED_NEGATIVE -> appContext.getColor(R.color.colorTextSemanticGreen)
-        DeviceUIState.PAIRED_POSITIVE,
-        DeviceUIState.PAIRED_POSITIVE_TELETAN -> appContext.getColor(R.color.colorTextSemanticRed)
-        else -> appContext.getColor(R.color.colorTextSemanticRed)
+fun formatTestResultVisible(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Int =
+    uiState.withSuccess(View.GONE) {
+        View.VISIBLE
     }
-}
 
-fun formatTestResult(uiState: DeviceUIState?): Spannable {
+fun formatTestResultStatusText(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): String =
+    uiState.withSuccess(R.string.test_result_card_status_invalid) {
+        when (it) {
+            DeviceUIState.PAIRED_NEGATIVE -> R.string.test_result_card_status_negative
+            DeviceUIState.PAIRED_POSITIVE,
+            DeviceUIState.PAIRED_POSITIVE_TELETAN -> R.string.test_result_card_status_positive
+            else -> R.string.test_result_card_status_invalid
+        }
+    }.let { CoronaWarnApplication.getAppContext().getString(it) }
+
+fun formatTestResultStatusColor(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Int =
+    uiState.withSuccess(R.color.colorTextSemanticRed) {
+        when (it) {
+            DeviceUIState.PAIRED_NEGATIVE -> R.color.colorTextSemanticGreen
+            DeviceUIState.PAIRED_POSITIVE,
+            DeviceUIState.PAIRED_POSITIVE_TELETAN -> R.color.colorTextSemanticRed
+            else -> R.color.colorTextSemanticRed
+        }
+    }.let { CoronaWarnApplication.getAppContext().getColor(it) }
+
+fun formatTestResult(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Spannable {
     val appContext = CoronaWarnApplication.getAppContext()
     return SpannableStringBuilder()
         .append(appContext.getString(R.string.test_result_card_virus_name_text))
@@ -87,33 +93,36 @@ fun formatTestResult(uiState: DeviceUIState?): Spannable {
         )
 }
 
-fun formatTestResultCardContent(uiState: DeviceUIState?): Spannable {
-    val appContext = CoronaWarnApplication.getAppContext()
-    return when (uiState) {
-        DeviceUIState.PAIRED_NO_RESULT ->
-            SpannableString(appContext.getString(R.string.test_result_card_status_pending))
-        DeviceUIState.PAIRED_ERROR,
-        DeviceUIState.PAIRED_REDEEMED ->
-            SpannableString(appContext.getString(R.string.test_result_card_status_invalid))
-
-        DeviceUIState.PAIRED_POSITIVE,
-        DeviceUIState.PAIRED_POSITIVE_TELETAN,
-        DeviceUIState.PAIRED_NEGATIVE -> formatTestResult(uiState)
-        else -> SpannableString("")
+fun formatTestResultCardContent(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Spannable {
+    return uiState.withSuccess(SpannableString("")) {
+        val appContext = CoronaWarnApplication.getAppContext()
+        when (it) {
+            DeviceUIState.PAIRED_NO_RESULT ->
+                SpannableString(appContext.getString(R.string.test_result_card_status_pending))
+            DeviceUIState.PAIRED_ERROR,
+            DeviceUIState.PAIRED_REDEEMED ->
+                SpannableString(appContext.getString(R.string.test_result_card_status_invalid))
+
+            DeviceUIState.PAIRED_POSITIVE,
+            DeviceUIState.PAIRED_POSITIVE_TELETAN,
+            DeviceUIState.PAIRED_NEGATIVE -> formatTestResult(uiState)
+            else -> SpannableString("")
+        }
     }
 }
 
-fun formatTestStatusIcon(uiState: DeviceUIState?): Drawable? {
-    val appContext = CoronaWarnApplication.getAppContext()
-    return when (uiState) {
-        DeviceUIState.PAIRED_NO_RESULT -> appContext.getDrawable(R.drawable.ic_test_result_illustration_pending)
-        DeviceUIState.PAIRED_POSITIVE_TELETAN,
-        DeviceUIState.PAIRED_POSITIVE -> appContext.getDrawable(R.drawable.ic_test_result_illustration_positive)
-        DeviceUIState.PAIRED_NEGATIVE -> appContext.getDrawable(R.drawable.ic_test_result_illustration_negative)
-        DeviceUIState.PAIRED_ERROR,
-        DeviceUIState.PAIRED_REDEEMED -> appContext.getDrawable(R.drawable.ic_test_result_illustration_invalid)
-        else -> appContext.getDrawable(R.drawable.ic_test_result_illustration_invalid)
-    }
+fun formatTestStatusIcon(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Drawable? {
+    return uiState.withSuccess(R.drawable.ic_test_result_illustration_invalid) {
+        when (it) {
+            DeviceUIState.PAIRED_NO_RESULT -> R.drawable.ic_test_result_illustration_pending
+            DeviceUIState.PAIRED_POSITIVE_TELETAN,
+            DeviceUIState.PAIRED_POSITIVE -> R.drawable.ic_test_result_illustration_positive
+            DeviceUIState.PAIRED_NEGATIVE -> R.drawable.ic_test_result_illustration_negative
+            DeviceUIState.PAIRED_ERROR,
+            DeviceUIState.PAIRED_REDEEMED -> R.drawable.ic_test_result_illustration_invalid
+            else -> R.drawable.ic_test_result_illustration_invalid
+        }
+    }.let { CoronaWarnApplication.getAppContext().getDrawable(it) }
 }
 
 fun formatTestResultRegisteredAtText(registeredAt: Date?): String {
@@ -122,24 +131,30 @@ fun formatTestResultRegisteredAtText(registeredAt: Date?): String {
         .format(registeredAt?.toUIFormat(appContext))
 }
 
-fun formatTestResultPendingStepsVisible(uiState: DeviceUIState?): Int =
-    formatVisibility(uiState == DeviceUIState.PAIRED_NO_RESULT)
+fun formatTestResultPendingStepsVisible(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Int =
+    uiState.withSuccess(View.GONE) { formatVisibility(it == DeviceUIState.PAIRED_NO_RESULT) }
 
-fun formatTestResultNegativeStepsVisible(uiState: DeviceUIState?): Int =
-    formatVisibility(uiState == DeviceUIState.PAIRED_NEGATIVE)
+fun formatTestResultNegativeStepsVisible(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Int =
+    uiState.withSuccess(View.GONE) { formatVisibility(it == DeviceUIState.PAIRED_NEGATIVE) }
 
-fun formatTestResultPositiveStepsVisible(uiState: DeviceUIState?): Int =
-    formatVisibility(uiState == DeviceUIState.PAIRED_POSITIVE || uiState == DeviceUIState.PAIRED_POSITIVE_TELETAN)
+fun formatTestResultPositiveStepsVisible(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Int =
+    uiState.withSuccess(View.GONE) {
+        formatVisibility(it == DeviceUIState.PAIRED_POSITIVE || it == DeviceUIState.PAIRED_POSITIVE_TELETAN)
+    }
 
-fun formatTestResultInvalidStepsVisible(uiState: DeviceUIState?): Int =
-    formatVisibility(uiState == DeviceUIState.PAIRED_ERROR || uiState == DeviceUIState.PAIRED_REDEEMED)
+fun formatTestResultInvalidStepsVisible(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Int =
+    uiState.withSuccess(View.GONE) {
+        formatVisibility(it == DeviceUIState.PAIRED_ERROR || it == DeviceUIState.PAIRED_REDEEMED)
+    }
 
-fun formatShowRiskStatusCard(deviceUiState: DeviceUIState?): Int =
-    formatVisibility(
-        deviceUiState != DeviceUIState.PAIRED_POSITIVE &&
-                deviceUiState != DeviceUIState.PAIRED_POSITIVE_TELETAN &&
-                deviceUiState != DeviceUIState.SUBMITTED_FINAL
-    )
+fun formatShowRiskStatusCard(uiState: NetworkRequestWrapper<DeviceUIState, Throwable>?): Int =
+    uiState.withSuccess(View.GONE) {
+        formatVisibility(
+            it != DeviceUIState.PAIRED_POSITIVE &&
+                it != DeviceUIState.PAIRED_POSITIVE_TELETAN &&
+                it != DeviceUIState.SUBMITTED_FINAL
+        )
+    }
 
 fun formatCountryIsoTagToLocalizedName(isoTag: String?): String {
     val country = if (isoTag != null) Locale("", isoTag).displayCountry else ""
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_calendar.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_calendar.xml
index a34166dd5..ecf73ebc4 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_calendar.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_calendar.xml
@@ -9,10 +9,6 @@
 
         <import type="de.rki.coronawarnapp.submission.Symptoms.StartOf" />
 
-        <variable
-            name="submissionViewModel"
-            type="de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel" />
-
     </data>
 
     <ScrollView
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_intro.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_intro.xml
index 0fb5a5e02..e57734171 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_intro.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_intro.xml
@@ -7,10 +7,6 @@
 
         <import type="de.rki.coronawarnapp.util.formatter.FormatterSubmissionHelper" />
 
-        <variable
-            name="submissionViewModel"
-            type="de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel" />
-
     </data>
 
     <ScrollView
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result.xml
index 3521faa0f..9b40a94aa 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result.xml
@@ -34,7 +34,7 @@
             style="?android:attr/progressBarStyle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:visibility="@{FormatterSubmissionHelper.formatTestResultSpinnerVisible(uiState.apiRequestState)}"
+            android:visibility="@{FormatterSubmissionHelper.formatTestResultSpinnerVisible(uiState.deviceUiState)}"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -48,7 +48,7 @@
             android:layout_width="@dimen/match_constraint"
             android:layout_height="@dimen/match_constraint"
             android:layout_marginBottom="@dimen/button_padding_top_bottom"
-            android:visibility="@{FormatterSubmissionHelper.formatTestResultVisible(uiState.apiRequestState)}"
+            android:visibility="@{FormatterSubmissionHelper.formatTestResultVisible(uiState.deviceUiState)}"
             app:layout_constraintBottom_toTopOf="@+id/include_submission_test_result_buttons"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_symptom_length_selection.xml b/Corona-Warn-App/src/main/res/layout/include_submission_symptom_length_selection.xml
index d95f4ed9f..6cbbd3180 100644
--- a/Corona-Warn-App/src/main/res/layout/include_submission_symptom_length_selection.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_submission_symptom_length_selection.xml
@@ -8,10 +8,6 @@
 
         <import type="de.rki.coronawarnapp.submission.Symptoms.StartOf" />
 
-        <variable
-            name="submissionViewModel"
-            type="de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel" />
-
     </data>
 
     <androidx.constraintlayout.widget.ConstraintLayout
diff --git a/Corona-Warn-App/src/main/res/layout/include_test_result_card.xml b/Corona-Warn-App/src/main/res/layout/include_test_result_card.xml
index dcdcdea37..01364c789 100644
--- a/Corona-Warn-App/src/main/res/layout/include_test_result_card.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_test_result_card.xml
@@ -13,7 +13,7 @@
 
         <variable
             name="deviceUIState"
-            type="de.rki.coronawarnapp.util.DeviceUIState" />
+            type="de.rki.coronawarnapp.util.NetworkRequestWrapper&lt;de.rki.coronawarnapp.util.DeviceUIState,java.lang.Throwable>" />
     </data>
 
     <androidx.constraintlayout.widget.ConstraintLayout
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt
index 44a4980d7..c636f8180 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt
@@ -9,12 +9,12 @@ import de.rki.coronawarnapp.ui.main.home.HomeFragmentViewModel
 import de.rki.coronawarnapp.ui.main.home.SubmissionCardState
 import de.rki.coronawarnapp.ui.main.home.SubmissionCardsStateProvider
 import de.rki.coronawarnapp.ui.main.home.TracingHeaderState
-import de.rki.coronawarnapp.ui.submission.ApiRequestState.SUCCESS
 import de.rki.coronawarnapp.ui.tracing.card.TracingCardState
 import de.rki.coronawarnapp.ui.tracing.card.TracingCardStateProvider
 import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel
 import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE
 import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE_TELETAN
+import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
@@ -128,7 +128,7 @@ class HomeFragmentViewModelTest : BaseTest() {
 
     @Test
     fun `positive test result notification is triggered on positive QR code result`() {
-        val state = SubmissionCardState(PAIRED_POSITIVE, true, SUCCESS)
+        val state = SubmissionCardState(NetworkRequestWrapper.RequestSuccessful(PAIRED_POSITIVE), true)
         every { submissionCardsStateProvider.state } returns flowOf(state)
         every { testResultNotificationService.schedulePositiveTestResultReminder() } returns Unit
 
@@ -142,7 +142,7 @@ class HomeFragmentViewModelTest : BaseTest() {
 
     @Test
     fun `positive test result notification is triggered on positive TeleTan code result`() {
-        val state = SubmissionCardState(PAIRED_POSITIVE_TELETAN, true, SUCCESS)
+        val state = SubmissionCardState(NetworkRequestWrapper.RequestSuccessful(PAIRED_POSITIVE_TELETAN), true)
         every { submissionCardsStateProvider.state } returns flowOf(state)
         every { testResultNotificationService.schedulePositiveTestResultReminder() } returns Unit
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardStateTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardStateTest.kt
index 37d908b10..e661eb23a 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardStateTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardStateTest.kt
@@ -5,6 +5,7 @@ import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.ui.main.home.SubmissionCardState
 import de.rki.coronawarnapp.ui.submission.ApiRequestState
 import de.rki.coronawarnapp.util.DeviceUIState
+import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.clearAllMocks
@@ -33,12 +34,18 @@ class SubmissionCardStateTest : BaseTest() {
     private fun instance(
         deviceUiState: DeviceUIState = mockk(),
         isDeviceRegistered: Boolean = true,
-        uiStateState: ApiRequestState = mockk()
-    ) = SubmissionCardState(
-        deviceUiState = deviceUiState,
-        isDeviceRegistered = isDeviceRegistered,
-        uiStateState = uiStateState
-    )
+        uiStateState: ApiRequestState = ApiRequestState.SUCCESS
+    ) =
+        when (uiStateState) {
+            ApiRequestState.SUCCESS ->
+                SubmissionCardState(NetworkRequestWrapper.RequestSuccessful(deviceUiState), isDeviceRegistered)
+            ApiRequestState.FAILED ->
+                SubmissionCardState(NetworkRequestWrapper.RequestFailed(mockk()), isDeviceRegistered)
+            ApiRequestState.STARTED ->
+                SubmissionCardState(NetworkRequestWrapper.RequestStarted, isDeviceRegistered)
+            ApiRequestState.IDLE ->
+                SubmissionCardState(NetworkRequestWrapper.RequestIdle, isDeviceRegistered)
+        }
 
     @Test
     fun `risk card visibility`() {
@@ -163,7 +170,7 @@ class SubmissionCardStateTest : BaseTest() {
             isFetchingCardVisible() shouldBe false
         }
         instance(isDeviceRegistered = true, uiStateState = ApiRequestState.FAILED).apply {
-            isFetchingCardVisible() shouldBe true
+            isFetchingCardVisible() shouldBe false
         }
     }
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardsStateProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardsStateProviderTest.kt
index 2eaac14f6..b5bc2d451 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardsStateProviderTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardsStateProviderTest.kt
@@ -5,8 +5,8 @@ import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.storage.SubmissionRepository
 import de.rki.coronawarnapp.ui.main.home.SubmissionCardState
 import de.rki.coronawarnapp.ui.main.home.SubmissionCardsStateProvider
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
 import de.rki.coronawarnapp.util.DeviceUIState
+import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import io.kotest.matchers.shouldBe
 import io.mockk.MockKAnnotations
 import io.mockk.clearAllMocks
@@ -46,20 +46,19 @@ class SubmissionCardsStateProviderTest : BaseTest() {
 
     @Test
     fun `state is combined correctly`() = runBlockingTest {
-        every { SubmissionRepository.deviceUIStateFlow } returns flow { emit(DeviceUIState.PAIRED_POSITIVE) }
-        every { SubmissionRepository.uiStateStateFlow } returns flow { emit(ApiRequestState.SUCCESS) }
+        every { SubmissionRepository.deviceUIStateFlow } returns flow {
+            emit(NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>(DeviceUIState.PAIRED_POSITIVE))
+        }
         every { LocalData.registrationToken() } returns "token"
 
         createInstance().apply {
             state.first() shouldBe SubmissionCardState(
-                deviceUiState = DeviceUIState.PAIRED_POSITIVE,
-                uiStateState = ApiRequestState.SUCCESS,
+                deviceUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE),
                 isDeviceRegistered = true
             )
 
             verify {
                 SubmissionRepository.deviceUIStateFlow
-                SubmissionRepository.uiStateStateFlow
                 LocalData.registrationToken()
             }
         }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt
index c83108653..c7f8e7b15 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt
@@ -8,8 +8,8 @@ import android.text.style.ForegroundColorSpan
 import android.view.View
 import de.rki.coronawarnapp.CoronaWarnApplication
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
 import de.rki.coronawarnapp.util.DeviceUIState
+import de.rki.coronawarnapp.util.NetworkRequestWrapper
 import io.mockk.MockKAnnotations
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
@@ -74,52 +74,76 @@ class FormatterSubmissionHelperTest {
         every { context.getDrawable(R.drawable.ic_test_result_illustration_negative) } returns drawable
     }
 
-    private fun formatTestResultSpinnerVisibleBase(oUiStateState: ApiRequestState?, iResult: Int) {
-        val result = formatTestResultSpinnerVisible(uiStateState = oUiStateState)
+    private fun formatTestResultSpinnerVisibleBase(
+        oUiStateState: NetworkRequestWrapper<DeviceUIState, Throwable>?,
+        iResult: Int
+    ) {
+        val result = formatTestResultSpinnerVisible(uiState = oUiStateState)
         assertThat(result, `is`(iResult))
     }
 
-    private fun formatTestResultVisibleBase(oUiStateState: ApiRequestState?, iResult: Int) {
-        val result = formatTestResultVisible(uiStateState = oUiStateState)
+    private fun formatTestResultVisibleBase(
+        oUiStateState: NetworkRequestWrapper<DeviceUIState, Throwable>?,
+        iResult: Int
+    ) {
+        val result = formatTestResultVisible(uiState = oUiStateState)
         assertThat(result, `is`(iResult))
     }
 
-    private fun formatTestResultStatusTextBase(oUiState: DeviceUIState?, iResult: String) {
+    private fun formatTestResultStatusTextBase(
+        oUiState: NetworkRequestWrapper<DeviceUIState, Throwable>?,
+        iResult: String
+    ) {
         val result = formatTestResultStatusText(uiState = oUiState)
         assertThat(result, `is`(iResult))
     }
 
-    private fun formatTestResultStatusColorBase(oUiState: DeviceUIState?, iResult: Int) {
+    private fun formatTestResultStatusColorBase(
+        oUiState: NetworkRequestWrapper<DeviceUIState, Throwable>?,
+        iResult: Int
+    ) {
         val result = formatTestResultStatusColor(uiState = oUiState)
         assertThat(result, `is`(iResult))
     }
 
-    private fun formatTestStatusIconBase(oUiState: DeviceUIState?) {
+    private fun formatTestStatusIconBase(oUiState: NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>?) {
         val result = formatTestStatusIcon(uiState = oUiState)
         assertThat(result, `is`(drawable))
     }
 
-    private fun formatTestResultPendingStepsVisibleBase(oUiState: DeviceUIState?, iResult: Int) {
+    private fun formatTestResultPendingStepsVisibleBase(
+        oUiState: NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>?,
+        iResult: Int
+    ) {
         val result = formatTestResultPendingStepsVisible(uiState = oUiState)
         assertThat(result, `is`(iResult))
     }
 
-    private fun formatTestResultNegativeStepsVisibleBase(oUiState: DeviceUIState?, iResult: Int) {
+    private fun formatTestResultNegativeStepsVisibleBase(
+        oUiState: NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>?,
+        iResult: Int
+    ) {
         val result = formatTestResultNegativeStepsVisible(uiState = oUiState)
         assertThat(result, `is`(iResult))
     }
 
-    private fun formatTestResultPositiveStepsVisibleBase(oUiState: DeviceUIState?, iResult: Int) {
+    private fun formatTestResultPositiveStepsVisibleBase(
+        oUiState: NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>?,
+        iResult: Int
+    ) {
         val result = formatTestResultPositiveStepsVisible(uiState = oUiState)
         assertThat(result, `is`(iResult))
     }
 
-    private fun formatTestResultInvalidStepsVisibleBase(oUiState: DeviceUIState?, iResult: Int) {
+    private fun formatTestResultInvalidStepsVisibleBase(
+        oUiState: NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>?,
+        iResult: Int
+    ) {
         val result = formatTestResultInvalidStepsVisible(uiState = oUiState)
         assertThat(result, `is`(iResult))
     }
 
-    private fun formatTestResultBase(oUiState: DeviceUIState?) {
+    private fun formatTestResultBase(oUiState: NetworkRequestWrapper.RequestSuccessful<DeviceUIState, Throwable>?) {
         mockkConstructor(SpannableStringBuilder::class)
 
         val spannableStringBuilder1 =
@@ -147,19 +171,19 @@ class FormatterSubmissionHelperTest {
     fun formatTestResultSpinnerVisible() {
         formatTestResultSpinnerVisibleBase(oUiStateState = null, iResult = View.VISIBLE)
         formatTestResultSpinnerVisibleBase(
-            oUiStateState = ApiRequestState.FAILED,
+            oUiStateState = NetworkRequestWrapper.RequestFailed(mockk()),
             iResult = View.VISIBLE
         )
         formatTestResultSpinnerVisibleBase(
-            oUiStateState = ApiRequestState.IDLE,
+            oUiStateState = NetworkRequestWrapper.RequestIdle,
             iResult = View.VISIBLE
         )
         formatTestResultSpinnerVisibleBase(
-            oUiStateState = ApiRequestState.STARTED,
+            oUiStateState = NetworkRequestWrapper.RequestStarted,
             iResult = View.VISIBLE
         )
         formatTestResultSpinnerVisibleBase(
-            oUiStateState = ApiRequestState.SUCCESS,
+            oUiStateState = NetworkRequestWrapper.RequestSuccessful(mockk()),
             iResult = View.GONE
         )
     }
@@ -167,10 +191,13 @@ class FormatterSubmissionHelperTest {
     @Test
     fun formatTestResultVisible() {
         formatTestResultVisibleBase(oUiStateState = null, iResult = View.GONE)
-        formatTestResultVisibleBase(oUiStateState = ApiRequestState.FAILED, iResult = View.GONE)
-        formatTestResultVisibleBase(oUiStateState = ApiRequestState.IDLE, iResult = View.GONE)
-        formatTestResultVisibleBase(oUiStateState = ApiRequestState.STARTED, iResult = View.GONE)
-        formatTestResultVisibleBase(oUiStateState = ApiRequestState.SUCCESS, iResult = View.VISIBLE)
+        formatTestResultVisibleBase(oUiStateState = NetworkRequestWrapper.RequestFailed(mockk()), iResult = View.GONE)
+        formatTestResultVisibleBase(oUiStateState = NetworkRequestWrapper.RequestIdle, iResult = View.GONE)
+        formatTestResultVisibleBase(oUiStateState = NetworkRequestWrapper.RequestStarted, iResult = View.GONE)
+        formatTestResultVisibleBase(
+            oUiStateState = NetworkRequestWrapper.RequestSuccessful(mockk()),
+            iResult = View.VISIBLE
+        )
     }
 
     @Test
@@ -180,35 +207,35 @@ class FormatterSubmissionHelperTest {
             iResult = context.getString(R.string.test_result_card_status_invalid)
         )
         formatTestResultStatusTextBase(
-            oUiState = DeviceUIState.PAIRED_NEGATIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE),
             iResult = context.getString(R.string.test_result_card_status_negative)
         )
         formatTestResultStatusTextBase(
-            oUiState = DeviceUIState.PAIRED_ERROR,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR),
             iResult = context.getString(R.string.test_result_card_status_invalid)
         )
         formatTestResultStatusTextBase(
-            oUiState = DeviceUIState.PAIRED_NO_RESULT,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT),
             iResult = context.getString(R.string.test_result_card_status_invalid)
         )
         formatTestResultStatusTextBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE),
             iResult = context.getString(R.string.test_result_card_status_positive)
         )
         formatTestResultStatusTextBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN),
             iResult = context.getString(R.string.test_result_card_status_positive)
         )
         formatTestResultStatusTextBase(
-            oUiState = DeviceUIState.SUBMITTED_FINAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL),
             iResult = context.getString(R.string.test_result_card_status_invalid)
         )
         formatTestResultStatusTextBase(
-            oUiState = DeviceUIState.SUBMITTED_INITIAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL),
             iResult = context.getString(R.string.test_result_card_status_invalid)
         )
         formatTestResultStatusTextBase(
-            oUiState = DeviceUIState.UNPAIRED,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED),
             iResult = context.getString(R.string.test_result_card_status_invalid)
         )
     }
@@ -220,35 +247,35 @@ class FormatterSubmissionHelperTest {
             iResult = context.getColor(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = DeviceUIState.PAIRED_NEGATIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE),
             iResult = context.getColor(R.color.colorTextSemanticGreen)
         )
         formatTestResultStatusColorBase(
-            oUiState = DeviceUIState.PAIRED_ERROR,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR),
             iResult = context.getColor(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = DeviceUIState.PAIRED_NO_RESULT,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT),
             iResult = context.getColor(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE),
             iResult = context.getColor(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN),
             iResult = context.getColor(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = DeviceUIState.SUBMITTED_FINAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL),
             iResult = context.getColor(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = DeviceUIState.SUBMITTED_INITIAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL),
             iResult = context.getColor(R.color.colorTextSemanticRed)
         )
         formatTestResultStatusColorBase(
-            oUiState = DeviceUIState.UNPAIRED,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED),
             iResult = context.getColor(R.color.colorTextSemanticRed)
         )
     }
@@ -256,49 +283,49 @@ class FormatterSubmissionHelperTest {
     @Test
     fun formatTestStatusIcon() {
         formatTestStatusIconBase(oUiState = null)
-        formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_NEGATIVE)
-        formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_ERROR)
-        formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_NO_RESULT)
-        formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_POSITIVE)
-        formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN)
-        formatTestStatusIconBase(oUiState = DeviceUIState.SUBMITTED_FINAL)
-        formatTestStatusIconBase(oUiState = DeviceUIState.SUBMITTED_INITIAL)
-        formatTestStatusIconBase(oUiState = DeviceUIState.UNPAIRED)
+        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE))
+        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR))
+        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT))
+        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE))
+        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN))
+        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL))
+        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL))
+        formatTestStatusIconBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED))
     }
 
     @Test
     fun formatTestResultPendingStepsVisible() {
         formatTestResultPendingStepsVisibleBase(oUiState = null, iResult = View.GONE)
         formatTestResultPendingStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_NEGATIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE),
             iResult = View.GONE
         )
         formatTestResultPendingStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_ERROR,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR),
             iResult = View.GONE
         )
         formatTestResultPendingStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_NO_RESULT,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT),
             iResult = View.VISIBLE
         )
         formatTestResultPendingStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE),
             iResult = View.GONE
         )
         formatTestResultPendingStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN),
             iResult = View.GONE
         )
         formatTestResultPendingStepsVisibleBase(
-            oUiState = DeviceUIState.SUBMITTED_FINAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL),
             iResult = View.GONE
         )
         formatTestResultPendingStepsVisibleBase(
-            oUiState = DeviceUIState.SUBMITTED_INITIAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL),
             iResult = View.GONE
         )
         formatTestResultPendingStepsVisibleBase(
-            oUiState = DeviceUIState.UNPAIRED,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED),
             iResult = View.GONE
         )
     }
@@ -307,35 +334,35 @@ class FormatterSubmissionHelperTest {
     fun formatTestResultNegativeStepsVisible() {
         formatTestResultNegativeStepsVisibleBase(oUiState = null, iResult = View.GONE)
         formatTestResultNegativeStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_NEGATIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE),
             iResult = View.VISIBLE
         )
         formatTestResultNegativeStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_ERROR,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR),
             iResult = View.GONE
         )
         formatTestResultNegativeStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_NO_RESULT,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT),
             iResult = View.GONE
         )
         formatTestResultNegativeStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE),
             iResult = View.GONE
         )
         formatTestResultNegativeStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN),
             iResult = View.GONE
         )
         formatTestResultNegativeStepsVisibleBase(
-            oUiState = DeviceUIState.SUBMITTED_FINAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL),
             iResult = View.GONE
         )
         formatTestResultNegativeStepsVisibleBase(
-            oUiState = DeviceUIState.SUBMITTED_INITIAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL),
             iResult = View.GONE
         )
         formatTestResultNegativeStepsVisibleBase(
-            oUiState = DeviceUIState.UNPAIRED,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED),
             iResult = View.GONE
         )
     }
@@ -344,35 +371,35 @@ class FormatterSubmissionHelperTest {
     fun formatTestResultPositiveStepsVisible() {
         formatTestResultPositiveStepsVisibleBase(oUiState = null, iResult = View.GONE)
         formatTestResultPositiveStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_NEGATIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE),
             iResult = View.GONE
         )
         formatTestResultPositiveStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_ERROR,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR),
             iResult = View.GONE
         )
         formatTestResultPositiveStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_NO_RESULT,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT),
             iResult = View.GONE
         )
         formatTestResultPositiveStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE),
             iResult = View.VISIBLE
         )
         formatTestResultPositiveStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN),
             iResult = View.VISIBLE
         )
         formatTestResultPositiveStepsVisibleBase(
-            oUiState = DeviceUIState.SUBMITTED_FINAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL),
             iResult = View.GONE
         )
         formatTestResultPositiveStepsVisibleBase(
-            oUiState = DeviceUIState.SUBMITTED_INITIAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL),
             iResult = View.GONE
         )
         formatTestResultPositiveStepsVisibleBase(
-            oUiState = DeviceUIState.UNPAIRED,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED),
             iResult = View.GONE
         )
     }
@@ -381,35 +408,35 @@ class FormatterSubmissionHelperTest {
     fun formatTestResultInvalidStepsVisible() {
         formatTestResultInvalidStepsVisibleBase(oUiState = null, iResult = View.GONE)
         formatTestResultInvalidStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_NEGATIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE),
             iResult = View.GONE
         )
         formatTestResultInvalidStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_ERROR,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR),
             iResult = View.VISIBLE
         )
         formatTestResultInvalidStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_NO_RESULT,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT),
             iResult = View.GONE
         )
         formatTestResultInvalidStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE),
             iResult = View.GONE
         )
         formatTestResultInvalidStepsVisibleBase(
-            oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN),
             iResult = View.GONE
         )
         formatTestResultInvalidStepsVisibleBase(
-            oUiState = DeviceUIState.SUBMITTED_FINAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL),
             iResult = View.GONE
         )
         formatTestResultInvalidStepsVisibleBase(
-            oUiState = DeviceUIState.SUBMITTED_INITIAL,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL),
             iResult = View.GONE
         )
         formatTestResultInvalidStepsVisibleBase(
-            oUiState = DeviceUIState.UNPAIRED,
+            oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED),
             iResult = View.GONE
         )
     }
@@ -417,14 +444,14 @@ class FormatterSubmissionHelperTest {
     @Test
     fun formatTestResult() {
         formatTestResultBase(oUiState = null)
-        formatTestResultBase(oUiState = DeviceUIState.PAIRED_NEGATIVE)
-        formatTestResultBase(oUiState = DeviceUIState.PAIRED_ERROR)
-        formatTestResultBase(oUiState = DeviceUIState.PAIRED_NO_RESULT)
-        formatTestResultBase(oUiState = DeviceUIState.PAIRED_POSITIVE)
-        formatTestResultBase(oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN)
-        formatTestResultBase(oUiState = DeviceUIState.SUBMITTED_FINAL)
-        formatTestResultBase(oUiState = DeviceUIState.SUBMITTED_INITIAL)
-        formatTestResultBase(oUiState = DeviceUIState.UNPAIRED)
+        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NEGATIVE))
+        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_ERROR))
+        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT))
+        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE))
+        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE_TELETAN))
+        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL))
+        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_INITIAL))
+        formatTestResultBase(oUiState = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED))
     }
 
     @After
-- 
GitLab