diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle
index 5ed563edf22fbc1e6d163ad4feb0e33cf3814a5a..b27fde297bd9296ae225904b1597a36d2f16630e 100644
--- a/Corona-Warn-App/build.gradle
+++ b/Corona-Warn-App/build.gradle
@@ -293,6 +293,12 @@ task jacocoTestReportDeviceRelease(type: JacocoReport, dependsOn: 'testDeviceRel
     getExecutionData().from(fileTree(dir: "$buildDir", includes: ["jacoco/testDeviceReleaseUnitTest.exec"]))
 }
 
+configurations.all {
+    resolutionStrategy {
+        force "androidx.test:monitor:1.3.0"
+    }
+}
+
 dependencies {
     // KOTLIN
     def coroutineVersion = "1.4.0-M1"
@@ -381,6 +387,8 @@ dependencies {
     androidTestImplementation "io.mockk:mockk-android:1.10.2"
     debugImplementation 'androidx.fragment:fragment-testing:1.2.5'
 
+    androidTestImplementation 'tools.fastlane:screengrab:2.0.0'
+
     // Play Services
     implementation 'com.google.android.play:core:1.7.3'
     implementation 'com.google.android.gms:play-services-base:17.3.0'
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt
index 289a23b9b886e81a787ecd8486ccbf0fd39f50e5..d109adabf7c87a4a5a87aa9f4bb5ab519277d698 100644
--- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt
@@ -3,7 +3,6 @@ package de.rki.coronawarnapp.ui.main.home
 import androidx.fragment.app.testing.launchFragment
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.SavedStateHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import dagger.Module
 import dagger.android.ContributesAndroidInjector
@@ -34,7 +33,8 @@ class HomeFragmentTest : BaseUITest() {
         every { viewModel.refreshRequiredData() } just Runs
 
         setupMockViewModel(object : HomeFragmentViewModel.Factory {
-            override fun create(handle: SavedStateHandle): HomeFragmentViewModel = viewModel
+            // override fun create(handle: SavedStateHandle){} HomeFragmentViewModel = viewModel}
+            override fun create(): HomeFragmentViewModel = viewModel
         })
     }
 
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5e262d08eb3ddcf76f2b7b40760a1ab8fa91dc08
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt
@@ -0,0 +1,66 @@
+package de.rki.coronawarnapp.ui.submission
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.ui.submission.fragment.SubmissionContactFragment
+import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionContactViewModel
+import io.mockk.MockKAnnotations
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+
+@RunWith(AndroidJUnit4::class)
+class SubmissionContactFragmentTest : BaseUITest() {
+
+    @MockK lateinit var viewModel: SubmissionContactViewModel
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+        setupMockViewModel(object : SubmissionContactViewModel.Factory {
+            override fun create(): SubmissionContactViewModel = viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<SubmissionContactFragment>()
+    }
+
+    @Test fun testContactCallClicked() {
+        val scenario = launchFragmentInContainer<SubmissionContactFragment>()
+        onView(withId(R.id.submission_contact_button_call))
+            .perform(click())
+
+        // TODO verify result
+    }
+
+    @Test fun testContactEnterTanClicked() {
+        val scenario = launchFragmentInContainer<SubmissionContactFragment>()
+        onView(withId(R.id.submission_contact_button_enter))
+            .perform(click())
+
+        // TODO verify result
+    }
+}
+
+@Module
+abstract class SubmissionContactTestModule {
+    @ContributesAndroidInjector
+    abstract fun submissionContactScreen(): SubmissionContactFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fc417a990b5c53a98167039c839d67bbee6fcb07
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt
@@ -0,0 +1,78 @@
+package de.rki.coronawarnapp.ui.submission
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.scrollTo
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.ui.submission.fragment.SubmissionDispatcherFragment
+import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionDispatcherViewModel
+import io.mockk.MockKAnnotations
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+
+@RunWith(AndroidJUnit4::class)
+class SubmissionDispatcherFragmentTest : BaseUITest() {
+
+    @MockK lateinit var viewModel: SubmissionDispatcherViewModel
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+        setupMockViewModel(object : SubmissionDispatcherViewModel.Factory {
+            override fun create(): SubmissionDispatcherViewModel = viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<SubmissionDispatcherFragment>()
+    }
+
+    @Test fun testEventQRClicked() {
+        val scenario = launchFragmentInContainer<SubmissionDispatcherFragment>()
+        onView(withId(R.id.submission_dispatcher_qr))
+            .perform(scrollTo())
+            .perform(click())
+
+        // TODO verify result
+    }
+
+    @Test fun testEventTeleClicked() {
+        val scenario = launchFragmentInContainer<SubmissionDispatcherFragment>()
+        onView(withId(R.id.submission_dispatcher_tan_tele))
+            .perform(scrollTo())
+            .perform(click())
+
+        // TODO verify result
+    }
+
+    @Test fun testEventTanClicked() {
+        val scenario = launchFragmentInContainer<SubmissionDispatcherFragment>()
+        onView(withId(R.id.submission_dispatcher_tan_code))
+            .perform(scrollTo())
+            .perform(click())
+
+        // TODO verify result
+    }
+}
+
+@Module
+abstract class SubmissionDispatcherTestModule {
+    @ContributesAndroidInjector
+    abstract fun submissionDispatcherScreen(): SubmissionDispatcherFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDoneFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDoneFragmentTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..989bbcdcbeb17fcc04c3e0df15b236c11da33df8
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDoneFragmentTest.kt
@@ -0,0 +1,58 @@
+package de.rki.coronawarnapp.ui.submission
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.ui.submission.fragment.SubmissionDoneFragment
+import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionDoneViewModel
+import io.mockk.MockKAnnotations
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+
+@RunWith(AndroidJUnit4::class)
+class SubmissionDoneFragmentTest : BaseUITest() {
+
+    @MockK lateinit var viewModel: SubmissionDoneViewModel
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+        setupMockViewModel(object : SubmissionDoneViewModel.Factory {
+            override fun create(): SubmissionDoneViewModel = viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<SubmissionDoneFragment>()
+    }
+
+    @Test fun testDoneClicked() {
+        val scenario = launchFragmentInContainer<SubmissionDoneFragment>()
+        onView(withId(R.id.submission_done_button_done))
+            .perform(click())
+
+        // TODO verify result
+    }
+}
+
+@Module
+abstract class SubmissionDoneTestModule {
+    @ContributesAndroidInjector
+    abstract fun submissionDoneScreen(): SubmissionDoneFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionIntroFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionIntroFragmentTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5f3cd05665c1a9e4b49d18ecd496c21a488ce46c
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionIntroFragmentTest.kt
@@ -0,0 +1,69 @@
+package de.rki.coronawarnapp.ui.submission
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.ui.submission.fragment.SubmissionIntroFragment
+import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionIntroViewModel
+import io.mockk.MockKAnnotations
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+import tools.fastlane.screengrab.Screengrab
+import tools.fastlane.screengrab.locale.LocaleTestRule
+
+@RunWith(AndroidJUnit4::class)
+class SubmissionIntroFragmentTest : BaseUITest() {
+
+    /*@get:Rule
+    var activityRule = ActivityTestRule(MainActivity::class.java)*/
+
+    @Rule @JvmField
+    val localeTestRule = LocaleTestRule()
+
+    @MockK lateinit var viewModel: SubmissionIntroViewModel
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+
+        setupMockViewModel(object : SubmissionIntroViewModel.Factory {
+            override fun create(): SubmissionIntroViewModel = viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<SubmissionIntroFragment>()
+        Screengrab.screenshot("submission_Intro_fragment_opened")
+    }
+
+    @Test fun testEventNextClicked() {
+        val scenario = launchFragmentInContainer<SubmissionIntroFragment>()
+        onView(withId(R.id.submission_intro_button_next))
+            .perform(click())
+
+        // TODO verify result
+    }
+}
+
+@Module
+abstract class SubmissionIntroTestModule {
+    @ContributesAndroidInjector
+    abstract fun submissionIntroScreen(): SubmissionIntroFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionOtherWarningFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionOtherWarningFragmentTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..eb4a3dba7d25d21d9461208ff1d5604a5cf1f794
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionOtherWarningFragmentTest.kt
@@ -0,0 +1,83 @@
+package de.rki.coronawarnapp.ui.submission
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.scrollTo
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.submission.Symptoms
+import de.rki.coronawarnapp.ui.submission.symptoms.calendar.SubmissionSymptomCalendarViewModel
+import de.rki.coronawarnapp.ui.submission.symptoms.introduction.SubmissionSymptomIntroductionViewModel
+import de.rki.coronawarnapp.ui.submission.warnothers.SubmissionResultPositiveOtherWarningFragment
+import de.rki.coronawarnapp.ui.submission.warnothers.SubmissionResultPositiveOtherWarningViewModel
+import io.mockk.MockKAnnotations
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+
+@RunWith(AndroidJUnit4::class)
+class SubmissionOtherWarningFragmentTest : BaseUITest() {
+
+    @MockK lateinit var viewModel: SubmissionResultPositiveOtherWarningViewModel
+    @MockK lateinit var symptomIntroViewModel: SubmissionSymptomIntroductionViewModel
+    @MockK lateinit var symptomCalendarViewModel: SubmissionSymptomCalendarViewModel
+
+    //  @MockK lateinit var symptoms: Symptoms
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+        setupMockViewModel(object : SubmissionSymptomIntroductionViewModel.Factory {
+            override fun create(): SubmissionSymptomIntroductionViewModel = symptomIntroViewModel
+        })
+
+        /* every { symptomIntroViewModel.symptomIndication } returns MutableLiveData()
+         every { symptomIntroViewModel. } returns MutableLiveData()
+
+         symptomIntroViewModel.onPositiveSymptomIndication()
+         symptomIntroViewModel.symptomIndication.postValue("tesasdf") */
+
+        setupMockViewModel(object : SubmissionSymptomCalendarViewModel.Factory {
+            override fun create(symptomIndication: Symptoms.Indication): SubmissionSymptomCalendarViewModel =
+                symptomCalendarViewModel
+        })
+
+        setupMockViewModel(object : SubmissionResultPositiveOtherWarningViewModel.Factory {
+            override fun create(symptoms: Symptoms): SubmissionResultPositiveOtherWarningViewModel =
+                viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<SubmissionResultPositiveOtherWarningFragment>()
+    }
+
+    @Test fun testOtherWarningNextClicked() {
+        val scenario = launchFragmentInContainer<SubmissionResultPositiveOtherWarningFragment>()
+        onView(withId(R.id.submission_positive_other_warning_button_next))
+            .perform(scrollTo())
+            .perform(click())
+
+        // TODO verify result
+    }
+}
+
+@Module
+abstract class SubmissionOtherWarningTestModule {
+    @ContributesAndroidInjector
+    abstract fun submissionOtherWarningScreen(): SubmissionResultPositiveOtherWarningFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeInfoFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeInfoFragmentTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5e2b2ceaffa72d225586eb2b612a42f985372f14
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeInfoFragmentTest.kt
@@ -0,0 +1,58 @@
+package de.rki.coronawarnapp.ui.submission
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.ui.submission.qrcode.info.SubmissionQRCodeInfoFragment
+import de.rki.coronawarnapp.ui.submission.qrcode.info.SubmissionQRCodeInfoFragmentViewModel
+import io.mockk.MockKAnnotations
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+
+@RunWith(AndroidJUnit4::class)
+class SubmissionQrCodeInfoFragmentTest : BaseUITest() {
+
+    @MockK lateinit var viewModel: SubmissionQRCodeInfoFragmentViewModel
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+        setupMockViewModel(object : SubmissionQRCodeInfoFragmentViewModel.Factory {
+            override fun create(): SubmissionQRCodeInfoFragmentViewModel = viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<SubmissionQRCodeInfoFragment>()
+    }
+
+    @Test fun testQRInfoNextClicked() {
+        val scenario = launchFragmentInContainer<SubmissionQRCodeInfoFragment>()
+        onView(withId(R.id.submission_qr_info_button_next))
+            .perform(click())
+
+        // TODO verify result
+    }
+}
+
+@Module
+abstract class SubmissionQRInfoFragmentModule {
+    @ContributesAndroidInjector
+    abstract fun submissionQRInfoScreen(): SubmissionQRCodeInfoFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeScanFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeScanFragmentTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..245374a2a1013fd21b244be791f6cd459e5355bf
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeScanFragmentTest.kt
@@ -0,0 +1,45 @@
+package de.rki.coronawarnapp.ui.submission
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.ui.submission.qrcode.scan.SubmissionQRCodeScanFragment
+import de.rki.coronawarnapp.ui.submission.qrcode.scan.SubmissionQRCodeScanViewModel
+import io.mockk.MockKAnnotations
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+
+@RunWith(AndroidJUnit4::class)
+class SubmissionQrCodeScanFragmentTest : BaseUITest() {
+
+    @MockK lateinit var viewModel: SubmissionQRCodeScanViewModel
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+        setupMockViewModel(object : SubmissionQRCodeScanViewModel.Factory {
+            override fun create(): SubmissionQRCodeScanViewModel = viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<SubmissionQRCodeScanFragment>()
+    }
+}
+
+@Module
+abstract class SubmissionQRScanFragmentModule {
+    @ContributesAndroidInjector
+    abstract fun submissionQRScanScreen(): SubmissionQRCodeScanFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragmentTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b592443c863b3a5d634d83696455ae1b42252418
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragmentTest.kt
@@ -0,0 +1,68 @@
+package de.rki.coronawarnapp.ui.submission
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.scrollTo
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.submission.Symptoms
+import de.rki.coronawarnapp.ui.submission.symptoms.calendar.SubmissionSymptomCalendarFragment
+import de.rki.coronawarnapp.ui.submission.symptoms.calendar.SubmissionSymptomCalendarViewModel
+import io.mockk.MockKAnnotations
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+
+@RunWith(AndroidJUnit4::class)
+class SubmissionSymptomCalendarFragmentTest : BaseUITest() {
+
+    @MockK lateinit var viewModel: SubmissionSymptomCalendarViewModel
+
+    /*  private val symptoms = Symptoms(Symptoms.StartOf.OneToTwoWeeksAgo, POSITIVE)
+      private val positiveSymptomIndication = POSITIVE;
+      private val negativeSymptomIndication = Symptoms.Indication.NEGATIVE;
+      private val noSymptomIndication = Symptoms.Indication.NO_INFORMATION;*/
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+
+        setupMockViewModel(object : SubmissionSymptomCalendarViewModel.Factory {
+            override fun create(symptomIndication: Symptoms.Indication): SubmissionSymptomCalendarViewModel =
+                viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<SubmissionSymptomCalendarFragment>()
+    }
+
+    @Test fun testSymptomCalendarNextClicked() {
+        val scenario = launchFragmentInContainer<SubmissionSymptomCalendarFragment>()
+        onView(withId(R.id.symptom_button_next))
+            .perform(scrollTo())
+            .perform(click())
+
+        // TODO verify result
+    }
+}
+
+@Module
+abstract class SubmissionSymptomCalendarFragmentTestModule {
+    @ContributesAndroidInjector
+    abstract fun submissionSymptomCalendarScreen(): SubmissionSymptomCalendarFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ed959a210f9b3b33f3821b245fc5b0af3900cf1f
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt
@@ -0,0 +1,60 @@
+package de.rki.coronawarnapp.ui.submission
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.scrollTo
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.ui.submission.symptoms.introduction.SubmissionSymptomIntroductionFragment
+import de.rki.coronawarnapp.ui.submission.symptoms.introduction.SubmissionSymptomIntroductionViewModel
+import io.mockk.MockKAnnotations
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+
+@RunWith(AndroidJUnit4::class)
+class SubmissionSymptomIntroFragmentTest : BaseUITest() {
+
+    @MockK lateinit var viewModel: SubmissionSymptomIntroductionViewModel
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+        setupMockViewModel(object : SubmissionSymptomIntroductionViewModel.Factory {
+            override fun create(): SubmissionSymptomIntroductionViewModel = viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<SubmissionSymptomIntroductionFragment>()
+    }
+
+    @Test fun testSymptomNextClicked() {
+        val scenario = launchFragmentInContainer<SubmissionSymptomIntroductionFragment>()
+        onView(withId(R.id.symptom_button_next))
+            .perform(scrollTo())
+            .perform(click())
+
+        // TODO verify result
+    }
+}
+
+@Module
+abstract class SubmissionSymptomIntroFragmentTestModule {
+    @ContributesAndroidInjector
+    abstract fun submissionSymptomIntroScreen(): SubmissionSymptomIntroductionFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b2dc3033d936ec5bcbb81754816dcbf80498e5c5
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt
@@ -0,0 +1,63 @@
+package de.rki.coronawarnapp.ui.submission
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.scrollTo
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.ui.submission.tan.SubmissionTanFragment
+import de.rki.coronawarnapp.ui.submission.tan.SubmissionTanViewModel
+import io.mockk.MockKAnnotations
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+
+@RunWith(AndroidJUnit4::class)
+class SubmissionTanFragmentTest : BaseUITest() {
+
+    @MockK lateinit var viewModel: SubmissionTanViewModel
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+
+        setupMockViewModel(object : SubmissionTanViewModel.Factory {
+            override fun create(): SubmissionTanViewModel = viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<SubmissionTanFragment>()
+    }
+
+    @Test fun testEventTanNextClicked() {
+        val scenario = launchFragmentInContainer<SubmissionTanFragment>()
+        ViewActions.closeSoftKeyboard()
+        onView(withId(R.id.submission_tan_button_enter))
+            .perform(scrollTo())
+            .perform(click())
+
+        // TODO verify result
+    }
+}
+
+@Module
+abstract class SubmissionTanTestModule {
+    @ContributesAndroidInjector
+    abstract fun submissionTanScreen(): SubmissionTanFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ba62622e5fcd3f294fd3036257653624d5ad27b3
--- /dev/null
+++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt
@@ -0,0 +1,117 @@
+package de.rki.coronawarnapp.ui.submission
+
+import androidx.fragment.app.testing.launchFragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.lifecycle.MutableLiveData
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.ui.submission.testresult.SubmissionTestResultFragment
+import de.rki.coronawarnapp.ui.submission.testresult.SubmissionTestResultViewModel
+import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import testhelpers.BaseUITest
+
+@RunWith(AndroidJUnit4::class)
+class SubmissionTestResultFragmentTest : BaseUITest() {
+
+    @MockK lateinit var viewModel: SubmissionTestResultViewModel
+    @MockK lateinit var uiState: TestResultUIState
+
+    @Before
+    fun setup() {
+        MockKAnnotations.init(this, relaxed = true)
+
+        every { viewModel.uiState } returns MutableLiveData()
+
+        setupMockViewModel(object : SubmissionTestResultViewModel.Factory {
+            override fun create(): SubmissionTestResultViewModel = viewModel
+        })
+    }
+
+    @After
+    fun teardown() {
+        clearAllViewModels()
+    }
+
+    @Test
+    fun launch_fragment() {
+        launchFragment<SubmissionTestResultFragment>()
+    }
+
+    @Test
+    fun testEventPendingRefreshClicked() {
+        val scenario = launchFragmentInContainer<SubmissionTestResultFragment>()
+        Espresso.onView(ViewMatchers.withId(R.id.submission_test_result_button_pending_refresh))
+            .perform(ViewActions.scrollTo())
+            .perform(ViewActions.click())
+
+        // TODO verify result
+    }
+
+    @Test
+    fun testEventPendingRemoveClicked() {
+        val scenario = launchFragmentInContainer<SubmissionTestResultFragment>()
+        Espresso.onView(ViewMatchers.withId(R.id.submission_test_result_button_pending_remove_test))
+            .perform(ViewActions.scrollTo())
+            .perform(ViewActions.click())
+
+        // TODO verify result
+    }
+
+    @Test
+    fun testEventInvalidRemoveClicked() {
+        val scenario = launchFragmentInContainer<SubmissionTestResultFragment>()
+        Espresso.onView(ViewMatchers.withId(R.id.submission_test_result_button_invalid_remove_test))
+            .perform(ViewActions.scrollTo())
+            .perform(ViewActions.click())
+
+        // TODO verify result
+    }
+
+    @Test
+    fun testEventPositiveContinueWithSymptomsClicked() {
+        val scenario = launchFragmentInContainer<SubmissionTestResultFragment>()
+        Espresso.onView(ViewMatchers.withId(R.id.submission_test_result_button_positive_continue))
+            .perform(ViewActions.scrollTo())
+            .perform(ViewActions.click())
+
+        // TODO verify result
+    }
+
+    @Test
+    fun testEventPositiveContinueWithoutSymptomsClicked() {
+        val scenario = launchFragmentInContainer<SubmissionTestResultFragment>()
+        Espresso.onView(ViewMatchers.withId(R.id.submission_test_result_button_positive_continue_without_symptoms))
+            .perform(ViewActions.scrollTo())
+            .perform(ViewActions.click())
+
+        // TODO verify result
+    }
+
+    @Test
+    fun testEventNegativeRemoveClicked() {
+        val scenario = launchFragmentInContainer<SubmissionTestResultFragment>()
+        Espresso.onView(ViewMatchers.withId(R.id.submission_test_result_button_negative_remove_test))
+            .perform(ViewActions.scrollTo())
+            .perform(ViewActions.click())
+
+        // TODO verify result
+    }
+}
+
+@Module
+abstract class SubmissionTestResultTestModule {
+    @ContributesAndroidInjector
+    abstract fun submissionTestResultScreen(): SubmissionTestResultFragment
+}
diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt
index da725b563e82176894ab4217cd2ae37f699ad42e..e50180f6ccf93867a659830bf1b655a25e592831 100644
--- a/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt
+++ b/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt
@@ -8,16 +8,40 @@ import de.rki.coronawarnapp.ui.onboarding.OnboardingNotificationsTestModule
 import de.rki.coronawarnapp.ui.onboarding.OnboardingPrivacyTestModule
 import de.rki.coronawarnapp.ui.onboarding.OnboardingTestFragmentModule
 import de.rki.coronawarnapp.ui.onboarding.OnboardingTracingFragmentTestModule
+import de.rki.coronawarnapp.ui.submission.SubmissionContactTestModule
+import de.rki.coronawarnapp.ui.submission.SubmissionDispatcherTestModule
+import de.rki.coronawarnapp.ui.submission.SubmissionDoneTestModule
+import de.rki.coronawarnapp.ui.submission.SubmissionIntroTestModule
+import de.rki.coronawarnapp.ui.submission.SubmissionOtherWarningTestModule
+import de.rki.coronawarnapp.ui.submission.SubmissionQRInfoFragmentModule
+import de.rki.coronawarnapp.ui.submission.SubmissionQRScanFragmentModule
+import de.rki.coronawarnapp.ui.submission.SubmissionSymptomCalendarFragmentTestModule
+import de.rki.coronawarnapp.ui.submission.SubmissionSymptomIntroFragmentTestModule
+import de.rki.coronawarnapp.ui.submission.SubmissionTanTestModule
+import de.rki.coronawarnapp.ui.submission.SubmissionTestResultTestModule
 
 @Module(
     includes = [
         HomeFragmentTestModule::class,
+        // Onboarding
         OnboardingFragmentTestModule::class,
         OnboardingDeltaInteroperabilityFragmentTestModule::class,
         OnboardingNotificationsTestModule::class,
         OnboardingPrivacyTestModule::class,
         OnboardingTestFragmentModule::class,
-        OnboardingTracingFragmentTestModule::class
+        OnboardingTracingFragmentTestModule::class,
+        // Submission
+        SubmissionIntroTestModule::class,
+        SubmissionDispatcherTestModule::class,
+        SubmissionTanTestModule::class,
+        SubmissionTestResultTestModule::class,
+        SubmissionOtherWarningTestModule::class,
+        SubmissionSymptomIntroFragmentTestModule::class,
+        SubmissionSymptomCalendarFragmentTestModule::class,
+        SubmissionContactTestModule::class,
+        SubmissionDoneTestModule::class,
+        SubmissionQRInfoFragmentModule::class,
+        SubmissionQRScanFragmentModule::class
     ]
 )
 class FragmentTestModuleRegistrar
diff --git a/Corona-Warn-App/src/debug/AndroidManifest.xml b/Corona-Warn-App/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ba24510e29ef4b6b35dd0c3ec856f1decd626ca4
--- /dev/null
+++ b/Corona-Warn-App/src/debug/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- Allows unlocking your device and activating its screen so UI tests can succeed -->
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <!-- Allows for storing and retrieving screenshots -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <!-- Allows changing locales -->
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+
+</manifest>
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 eff4ebae82a41cb2f9d9d032d2f70a520ef5c2bf..ed9100cda73ba3dad4bf70ede1eda68443f3dd14 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
@@ -32,11 +32,9 @@ object SubmissionRepository {
 
     private val testResultReceivedDateFlowInternal = MutableStateFlow(Date())
     val testResultReceivedDateFlow: Flow<Date> = testResultReceivedDateFlowInternal
-    val testResultReceivedDate = testResultReceivedDateFlow.asLiveData()
 
     private val deviceUIStateFlowInternal = MutableStateFlow(DeviceUIState.UNPAIRED)
     val deviceUIStateFlow: Flow<DeviceUIState> = deviceUIStateFlowInternal
-    val deviceUIState = deviceUIStateFlow.asLiveData()
 
     private val testResultFlow = MutableStateFlow<TestResult?>(null)
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt
index 81327c8e1182373949179b771c87b4e3936e01dd..b29b2469819cf00badd319ae1a24ef7c3a6fafbf 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/interoperability/InteroperabilityRepository.kt
@@ -1,11 +1,12 @@
 package de.rki.coronawarnapp.storage.interoperability
 
 import android.text.TextUtils
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.Transformations
+import androidx.lifecycle.asLiveData
 import de.rki.coronawarnapp.appconfig.AppConfigProvider
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.ui.Country
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.runBlocking
 import timber.log.Timber
 import java.util.Locale
@@ -21,8 +22,11 @@ class InteroperabilityRepository @Inject constructor(
         LocalData.isInteroperabilityShownAtLeastOnce = true
     }
 
-    private val _countryList: MutableLiveData<List<Country>> = MutableLiveData(listOf())
-    val countryList = Transformations.distinctUntilChanged(_countryList)
+    private val countryListFlowInternal = MutableStateFlow(listOf<Country>())
+    val countryListFlow: Flow<List<Country>> = countryListFlowInternal
+
+    @Deprecated("Use  countryListFlow")
+    val countryList = countryListFlow.asLiveData()
 
     init {
         getAllCountries()
@@ -44,16 +48,16 @@ class InteroperabilityRepository @Inject constructor(
                         if (mappedCountry == null) Timber.e("Unknown countrycode: %s", rawCode)
                         mappedCountry
                     }
-                _countryList.postValue(countries)
+                countryListFlowInternal.value = countries
                 Timber.d("Country list: ${TextUtils.join(System.lineSeparator(), countries)}")
             } catch (e: Exception) {
                 Timber.e(e)
-                _countryList.postValue(listOf())
+                countryListFlowInternal.value = emptyList()
             }
         }
     }
 
     fun clear() {
-        _countryList.postValue(emptyList())
+        countryListFlowInternal.value = emptyList()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVectorDeterminator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVectorDeterminator.kt
index e7a6c4179315a9f031c59ae5d97094ed5f7894b4..037dfc69052e4aa8674fde07ce589b8b2ce04f70 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVectorDeterminator.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/DaysSinceOnsetOfSymptomsVectorDeterminator.kt
@@ -31,10 +31,9 @@ class DaysSinceOnsetOfSymptomsVectorDeterminator @Inject constructor(
     @Suppress("MagicNumber")
     private fun determinePositiveIndication(symptoms: Symptoms): DaysSinceOnsetOfSymptomsVector {
         return when (symptoms.startOfSymptoms) {
-            is Symptoms.StartOf.Date ->
-                createDaysSinceOnsetOfSymptomsVectorWith(
-                    symptoms.startOfSymptoms.date.ageInDays(timeStamper.nowUTC.toLocalDate())
-                )
+            is Symptoms.StartOf.Date -> createDaysSinceOnsetOfSymptomsVectorWith(
+                symptoms.startOfSymptoms.date.ageInDays(timeStamper.nowUTC.toLocalDate())
+            )
             is Symptoms.StartOf.LastSevenDays ->
                 createDaysSinceOnsetOfSymptomsVectorWith(701)
             is Symptoms.StartOf.OneToTwoWeeksAgo ->
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/Symptoms.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/Symptoms.kt
index 13e80e20a5f75dd4014b4f7597914124338ed71f..769df6264d8d24e3abd186fd2acfd57e110e30e4 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/Symptoms.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/Symptoms.kt
@@ -1,23 +1,43 @@
 package de.rki.coronawarnapp.submission
 
+import android.os.Parcelable
+import kotlinx.android.parcel.Parcelize
 import org.joda.time.LocalDate
 
+@Parcelize
 data class Symptoms(
     val startOfSymptoms: StartOf?,
     val symptomIndication: Indication
-) {
-    sealed class StartOf {
+) : Parcelable {
 
+    sealed class StartOf : Parcelable {
+        @Parcelize
         data class Date(val date: LocalDate) : StartOf()
+
+        @Parcelize
         object LastSevenDays : StartOf()
+
+        @Parcelize
         object OneToTwoWeeksAgo : StartOf()
+
+        @Parcelize
         object MoreThanTwoWeeks : StartOf()
+
+        @Parcelize
         object NoInformation : StartOf()
     }
 
-    enum class Indication {
+    @Parcelize
+    enum class Indication : Parcelable {
         POSITIVE,
         NEGATIVE,
         NO_INFORMATION
     }
+
+    companion object {
+        val NO_INFO_GIVEN = Symptoms(
+            startOfSymptoms = null, // FIXME  should this be null?
+            symptomIndication = Indication.NO_INFORMATION
+        )
+    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt
index af1a59dfd887c171a66c9e901a55a21e049d5e61..647f333c86bf16cc897fd14aad17495fe12ebe38 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt
@@ -9,7 +9,7 @@ import de.rki.coronawarnapp.ui.onboarding.OnboardingDeltaInteroperabilityModule
 import de.rki.coronawarnapp.ui.settings.SettingFragmentsModule
 import de.rki.coronawarnapp.ui.settings.SettingsResetFragment
 import de.rki.coronawarnapp.ui.settings.SettingsResetModule
-import de.rki.coronawarnapp.ui.submission.SubmissionFragmentModule
+import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionFragmentModule
 import de.rki.coronawarnapp.ui.tracing.details.RiskDetailsFragmentModule
 
 @Module(
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/TanConstants.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/TanConstants.kt
deleted file mode 100644
index 644cf1ee0f3aca6bb52e87b0fcca899b24065220..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/TanConstants.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package de.rki.coronawarnapp.ui.submission
-
-object TanConstants {
-    const val MAX_LENGTH = 10
-    val ALPHA_NUMERIC_CHARS = ('a'..'z').plus('A'..'Z').plus('0'..'9')
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionSymptomCalendarFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionSymptomCalendarFragment.kt
deleted file mode 100644
index 1c3184ab813f7b2bb8a2cf3537b55ca4b05605a4..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionSymptomCalendarFragment.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-package de.rki.coronawarnapp.ui.submission.fragment
-
-import android.content.res.ColorStateList
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
-import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.databinding.FragmentSubmissionSymptomCalendarBinding
-import de.rki.coronawarnapp.submission.Symptoms
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionSymptomCalendarViewModel
-import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
-import de.rki.coronawarnapp.util.di.AutoInject
-import de.rki.coronawarnapp.util.formatter.formatCalendarBackgroundButtonStyleByState
-import de.rki.coronawarnapp.util.formatter.formatCalendarButtonStyleByState
-import de.rki.coronawarnapp.util.formatter.isEnableSymptomCalendarButtonByState
-import de.rki.coronawarnapp.util.ui.doNavigate
-import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
-import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
-import javax.inject.Inject
-
-class SubmissionSymptomCalendarFragment : Fragment(), AutoInject {
-
-    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
-    private val viewModel: SubmissionSymptomCalendarViewModel by cwaViewModels { viewModelFactory }
-    private var _binding: FragmentSubmissionSymptomCalendarBinding? = null
-    private val binding: FragmentSubmissionSymptomCalendarBinding get() = _binding!!
-    private val submissionViewModel: SubmissionViewModel by activityViewModels()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        _binding = FragmentSubmissionSymptomCalendarBinding.inflate(inflater)
-        binding.submissionViewModel = submissionViewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
-
-    override fun onDestroyView() {
-        super.onDestroyView()
-        _binding = null
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        setButtonOnClickListener()
-
-        binding.symptomCalendarContainer.setDateSelectedListener(submissionViewModel::onDateSelected)
-
-        viewModel.routeToScreen.observe(viewLifecycleOwner, Observer {
-            when (it) {
-                is SubmissionNavigationEvents.NavigateToResultPositiveOtherWarning -> navigateToSymptomFinish()
-                is SubmissionNavigationEvents.NavigateToSymptomIntroduction -> navigateToPreviousScreen()
-            }
-        })
-
-        submissionViewModel.symptomStart.observe(viewLifecycleOwner, Observer {
-            updateButtons(it)
-            if (it !is Symptoms.StartOf.Date) {
-                binding.symptomCalendarContainer.unsetSelection()
-            }
-        })
-
-        submissionViewModel.initSymptomStart()
-    }
-
-    private fun updateButtons(symptomStart: Symptoms.StartOf?) {
-        binding.symptomCalendarChoiceSelection.calendarButtonSevenDays
-            .findViewById<Button>(R.id.calendar_button_seven_days)
-            .setTextColor(formatCalendarButtonStyleByState(symptomStart, Symptoms.StartOf.LastSevenDays))
-        binding.symptomCalendarChoiceSelection.targetLayout
-            .findViewById<Button>(R.id.calendar_button_seven_days).backgroundTintList =
-            ColorStateList.valueOf(
-                formatCalendarBackgroundButtonStyleByState(
-                    symptomStart, Symptoms.StartOf.LastSevenDays
-                )
-            )
-
-        binding.symptomCalendarChoiceSelection.targetLayout
-            .findViewById<Button>(R.id.calendar_button_one_two_weeks)
-            .setTextColor(formatCalendarButtonStyleByState(symptomStart, Symptoms.StartOf.OneToTwoWeeksAgo))
-        binding.symptomCalendarChoiceSelection.targetLayout
-            .findViewById<Button>(R.id.calendar_button_one_two_weeks).backgroundTintList =
-            ColorStateList.valueOf(
-                formatCalendarBackgroundButtonStyleByState(
-                    symptomStart, Symptoms.StartOf.OneToTwoWeeksAgo
-                )
-            )
-
-        binding.symptomCalendarChoiceSelection.targetLayout
-            .findViewById<Button>(R.id.calendar_button_more_than_two_weeks)
-            .setTextColor(formatCalendarButtonStyleByState(symptomStart, Symptoms.StartOf.MoreThanTwoWeeks))
-        binding.symptomCalendarChoiceSelection.targetLayout
-            .findViewById<Button>(R.id.calendar_button_more_than_two_weeks).backgroundTintList =
-            ColorStateList.valueOf(
-                formatCalendarBackgroundButtonStyleByState(
-                    symptomStart, Symptoms.StartOf.MoreThanTwoWeeks
-                )
-            )
-
-        binding.symptomCalendarChoiceSelection.targetLayout
-            .findViewById<Button>(R.id.target_button_verify)
-            .setTextColor(formatCalendarButtonStyleByState(symptomStart, Symptoms.StartOf.NoInformation))
-        binding.symptomCalendarChoiceSelection.targetLayout
-            .findViewById<Button>(R.id.target_button_verify).backgroundTintList =
-            ColorStateList.valueOf(
-                formatCalendarBackgroundButtonStyleByState(
-                    symptomStart, Symptoms.StartOf.NoInformation
-                )
-            )
-
-        binding
-            .symptomButtonNext.findViewById<Button>(R.id.symptom_button_next).isEnabled =
-            isEnableSymptomCalendarButtonByState(
-                symptomStart
-            )
-    }
-
-    private fun navigateToSymptomFinish() {
-        doNavigate(SubmissionSymptomCalendarFragmentDirections
-            .actionSubmissionSymptomCalendarFragmentToSubmissionResultPositiveOtherWarningFragment())
-    }
-
-    private fun navigateToPreviousScreen() {
-        doNavigate(SubmissionSymptomCalendarFragmentDirections
-            .actionSubmissionCalendarFragmentToSubmissionSymptomIntroductionFragment())
-    }
-
-    private fun setButtonOnClickListener() {
-        binding
-            .submissionSymptomCalendarHeader.headerButtonBack.buttonIcon
-            .setOnClickListener { viewModel.onCalendarPreviousClicked() }
-
-        binding
-            .symptomButtonNext
-            .setOnClickListener { viewModel.onCalendarNextClicked() }
-
-        binding.symptomCalendarChoiceSelection
-            .calendarButtonSevenDays
-            .setOnClickListener { submissionViewModel.onLastSevenDaysStart() }
-
-        binding.symptomCalendarChoiceSelection
-            .calendarButtonOneTwoWeeks
-            .setOnClickListener { submissionViewModel.onOneToTwoWeeksAgoStart() }
-
-        binding.symptomCalendarChoiceSelection
-            .calendarButtonMoreThanTwoWeeks
-            .setOnClickListener { submissionViewModel.onMoreThanTwoWeeksStart() }
-
-        binding.symptomCalendarChoiceSelection
-            .targetButtonVerify
-            .setOnClickListener { submissionViewModel.onNoInformationStart() }
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeInfoFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoFragment.kt
similarity index 78%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeInfoFragment.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoFragment.kt
index 1f37ca3e03ea3d3829a9492919609c4ae2864333..c98d506a032d742cec2cc6f8a1749d03717f10b1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeInfoFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoFragment.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission.fragment
+package de.rki.coronawarnapp.ui.submission.qrcode.info
 
 import android.os.Bundle
 import android.view.View
@@ -6,7 +6,6 @@ import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionQrCodeInfoBinding
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionQRCodeInfoFragmentViewModel
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.ui.doNavigate
 import de.rki.coronawarnapp.util.ui.observe2
@@ -24,11 +23,11 @@ class SubmissionQRCodeInfoFragment : Fragment(R.layout.fragment_submission_qr_co
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        binding.submissionQrCodeInfoHeader.headerButtonBack.buttonIcon.setOnClickListener() {
+        binding.submissionQrCodeInfoHeader.headerButtonBack.buttonIcon.setOnClickListener {
             viewModel.onBackPressed()
         }
 
-        binding.submissionQrInfoButtonNext.setOnClickListener() {
+        binding.submissionQrInfoButtonNext.setOnClickListener {
             viewModel.onNextPressed()
         }
 
@@ -38,8 +37,9 @@ class SubmissionQRCodeInfoFragment : Fragment(R.layout.fragment_submission_qr_co
 
         viewModel.navigateToQRScan.observe2(this) {
             doNavigate(
-                    SubmissionQRCodeInfoFragmentDirections
-                        .actionSubmissionQRCodeInfoFragmentToSubmissionQRCodeScanFragment())
+                SubmissionQRCodeInfoFragmentDirections
+                    .actionSubmissionQRCodeInfoFragmentToSubmissionQRCodeScanFragment()
+            )
         }
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeInfoFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoFragmentViewModel.kt
similarity index 92%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeInfoFragmentViewModel.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoFragmentViewModel.kt
index cde1a6246b98b1f4ed7321526b2f4b1f86bb6312..f90824a82de035cbc09cc81fec9986e3e05ff305 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeInfoFragmentViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoFragmentViewModel.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
+package de.rki.coronawarnapp.ui.submission.qrcode.info
 
 import com.squareup.inject.assisted.AssistedInject
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeInfoModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoModule.kt
similarity index 79%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeInfoModule.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoModule.kt
index fe135026e5823adaf0be18a9283913b193ff3410..437fc64b2353848b749819925a9187cf4fd956cb 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeInfoModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoModule.kt
@@ -1,9 +1,8 @@
-package de.rki.coronawarnapp.ui.submission.fragment
+package de.rki.coronawarnapp.ui.submission.qrcode.info
 
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoMap
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionQRCodeInfoFragmentViewModel
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt
similarity index 89%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeScanFragment.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt
index c09f7a54bcd6321ea83d2d00f1ff39eb3d52485e..1ecb64ba822eb0f531f8cf37d4fc769e464f1925 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionQRCodeScanFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission.fragment
+package de.rki.coronawarnapp.ui.submission.qrcode.scan
 
 import android.Manifest
 import android.content.pm.PackageManager
@@ -6,9 +6,7 @@ import android.os.Bundle
 import android.view.View
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
 import com.google.zxing.BarcodeFormat
-import com.journeyapps.barcodescanner.BarcodeResult
 import com.journeyapps.barcodescanner.DefaultDecoderFactory
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionQrCodeScanBinding
@@ -20,8 +18,6 @@ import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.submission.ApiRequestState
 import de.rki.coronawarnapp.ui.submission.ScanStatus
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionQRCodeScanViewModel
-import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.util.CameraPermissionHelper
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.di.AutoInject
@@ -40,54 +36,10 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co
 
     @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
     private val viewModel: SubmissionQRCodeScanViewModel by cwaViewModels { viewModelFactory }
-    private val submissionViewModel: SubmissionViewModel by activityViewModels()
+
     private val binding: FragmentSubmissionQrCodeScanBinding by viewBindingLazy()
     private var showsPermissionDialog = false
 
-    private fun decodeCallback(result: BarcodeResult) {
-        submissionViewModel.validateAndStoreTestGUID(result.text)
-    }
-
-    private fun startDecode() {
-        binding.submissionQrCodeScanPreview.decodeSingle { decodeCallback(it) }
-    }
-
-    private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
-        return when (exception) {
-            is BadRequestException -> DialogHelper.DialogInstance(
-                requireActivity(),
-                R.string.submission_qr_code_scan_invalid_dialog_headline,
-                R.string.submission_qr_code_scan_invalid_dialog_body,
-                R.string.submission_qr_code_scan_invalid_dialog_button_positive,
-                R.string.submission_qr_code_scan_invalid_dialog_button_negative,
-                true,
-                { startDecode() },
-                ::navigateToDispatchScreen
-            )
-            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,
-                ::navigateToDispatchScreen
-            )
-            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,
-                ::navigateToDispatchScreen
-            )
-        }
-    }
-
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
@@ -106,9 +58,9 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co
 
         binding.submissionQrCodeScanViewfinderView.setCameraPreview(binding.submissionQrCodeScanPreview)
 
-        submissionViewModel.scanStatus.observeEvent(viewLifecycleOwner) {
+        viewModel.scanStatus.observeEvent(viewLifecycleOwner) {
             if (ScanStatus.SUCCESS == it) {
-                submissionViewModel.doDeviceRegistration()
+                viewModel.doDeviceRegistration()
             }
 
             if (ScanStatus.INVALID == it) {
@@ -116,7 +68,7 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co
             }
         }
 
-        submissionViewModel.registrationState.observeEvent(viewLifecycleOwner) {
+        viewModel.registrationState.observe2(this) {
             binding.submissionQrCodeScanSpinner.visibility = when (it) {
                 ApiRequestState.STARTED -> View.VISIBLE
                 else -> View.GONE
@@ -130,7 +82,7 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co
             }
         }
 
-        submissionViewModel.registrationError.observeEvent(viewLifecycleOwner) {
+        viewModel.registrationError.observe2(this) {
             DialogHelper.showDialog(buildErrorDialog(it))
         }
 
@@ -144,10 +96,51 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co
         }
     }
 
+    private fun startDecode() {
+        binding.submissionQrCodeScanPreview.decodeSingle {
+            viewModel.validateAndStoreTestGUID(it.text)
+        }
+    }
+
+    private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
+        return when (exception) {
+            is BadRequestException -> DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_qr_code_scan_invalid_dialog_headline,
+                R.string.submission_qr_code_scan_invalid_dialog_body,
+                R.string.submission_qr_code_scan_invalid_dialog_button_positive,
+                R.string.submission_qr_code_scan_invalid_dialog_button_negative,
+                true,
+                { startDecode() },
+                ::navigateToDispatchScreen
+            )
+            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,
+                ::navigateToDispatchScreen
+            )
+            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,
+                ::navigateToDispatchScreen
+            )
+        }
+    }
+
     private fun navigateToDispatchScreen() =
         doNavigate(
-            SubmissionQRCodeScanFragmentDirections
-                .actionSubmissionQRCodeScanFragmentToSubmissionDispatcherFragment()
+            SubmissionQRCodeScanFragmentDirections.actionSubmissionQRCodeScanFragmentToSubmissionDispatcherFragment()
         )
 
     private fun showInvalidScanDialog() {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeScanModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanModule.kt
similarity index 90%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeScanModule.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanModule.kt
index 55c737607dd9de8d75bf7054e5137b53daaad770..7a435572f5371e5e76a6a49482e6bc80386e803b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeScanModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanModule.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
+package de.rki.coronawarnapp.ui.submission.qrcode.scan
 
 import dagger.Binds
 import dagger.Module
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b4330c73b3d8596cfcd44f708d814e6e9e786ede
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt
@@ -0,0 +1,71 @@
+package de.rki.coronawarnapp.ui.submission.qrcode.scan
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.TransactionException
+import de.rki.coronawarnapp.exception.http.CwaWebException
+import de.rki.coronawarnapp.exception.reporting.report
+import de.rki.coronawarnapp.service.submission.QRScanResult
+import de.rki.coronawarnapp.service.submission.SubmissionService
+import de.rki.coronawarnapp.ui.submission.ApiRequestState
+import de.rki.coronawarnapp.ui.submission.ScanStatus
+import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
+import de.rki.coronawarnapp.util.Event
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+
+class SubmissionQRCodeScanViewModel @AssistedInject constructor() : CWAViewModel() {
+
+    val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
+    private val _scanStatus = MutableLiveData(Event(ScanStatus.STARTED))
+
+    val scanStatus: LiveData<Event<ScanStatus>> = _scanStatus
+
+    fun validateAndStoreTestGUID(rawResult: String) {
+        val scanResult = QRScanResult(rawResult)
+        if (scanResult.isValid) {
+            SubmissionService.storeTestGUID(scanResult.guid!!)
+            _scanStatus.value = Event(ScanStatus.SUCCESS)
+        } else {
+            _scanStatus.value = Event(ScanStatus.INVALID)
+        }
+    }
+
+    val registrationState = MutableLiveData(ApiRequestState.IDLE)
+    val registrationError = SingleLiveEvent<CwaWebException>()
+
+    fun doDeviceRegistration() = launch {
+        try {
+            registrationState.postValue(ApiRequestState.STARTED)
+            SubmissionService.asyncRegisterDevice()
+            registrationState.postValue(ApiRequestState.SUCCESS)
+        } catch (err: CwaWebException) {
+            registrationState.postValue(ApiRequestState.FAILED)
+            registrationError.postValue(err)
+        } catch (err: TransactionException) {
+            if (err.cause is CwaWebException) {
+                registrationError.postValue(err.cause)
+            } else {
+                err.report(ExceptionCategory.INTERNAL)
+            }
+            registrationState.postValue(ApiRequestState.FAILED)
+        } catch (err: Exception) {
+            registrationState.postValue(ApiRequestState.FAILED)
+            err.report(ExceptionCategory.INTERNAL)
+        }
+    }
+
+    fun onBackPressed() {
+        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToQRInfo)
+    }
+
+    fun onClosePressed() {
+        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToDispatcher)
+    }
+
+    @AssistedInject.Factory
+    interface Factory : SimpleCWAViewModelFactory<SubmissionQRCodeScanViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2c1653eedd26f0d6df583642e4edd1fbb6eb3263
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt
@@ -0,0 +1,148 @@
+package de.rki.coronawarnapp.ui.submission.symptoms.calendar
+
+import android.content.res.ColorStateList
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.navArgs
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentSubmissionSymptomCalendarBinding
+import de.rki.coronawarnapp.submission.Symptoms
+import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.formatter.formatCalendarBackgroundButtonStyleByState
+import de.rki.coronawarnapp.util.formatter.formatCalendarButtonStyleByState
+import de.rki.coronawarnapp.util.formatter.isEnableSymptomCalendarButtonByState
+import de.rki.coronawarnapp.util.ui.doNavigate
+import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted
+import javax.inject.Inject
+
+class SubmissionSymptomCalendarFragment : Fragment(R.layout.fragment_submission_symptom_calendar),
+    AutoInject {
+
+    private val navArgs by navArgs<SubmissionSymptomCalendarFragmentArgs>()
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val viewModel: SubmissionSymptomCalendarViewModel by cwaViewModelsAssisted(
+        factoryProducer = { viewModelFactory },
+        constructorCall = { factory, _ ->
+            factory as SubmissionSymptomCalendarViewModel.Factory
+            factory.create(navArgs.symptomIndication)
+        }
+    )
+
+    private val binding: FragmentSubmissionSymptomCalendarBinding by viewBindingLazy()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        binding.symptomCalendarContainer.setDateSelectedListener {
+            viewModel.onDateSelected(it)
+        }
+
+        viewModel.routeToScreen.observe2(this) {
+            when (it) {
+                is SubmissionNavigationEvents.NavigateToResultPositiveOtherWarning -> doNavigate(
+                    SubmissionSymptomCalendarFragmentDirections
+                        .actionSubmissionSymptomCalendarFragmentToSubmissionResultPositiveOtherWarningFragment(
+                            it.symptoms
+                        )
+                )
+                is SubmissionNavigationEvents.NavigateToSymptomIntroduction -> doNavigate(
+                    SubmissionSymptomCalendarFragmentDirections
+                        .actionSubmissionCalendarFragmentToSubmissionSymptomIntroductionFragment()
+                )
+            }
+        }
+
+        viewModel.symptomStart.observe2(this) {
+            updateButtons(it)
+            if (it !is Symptoms.StartOf.Date) {
+                binding.symptomCalendarContainer.unsetSelection()
+            }
+        }
+
+        binding.apply {
+            submissionSymptomCalendarHeader.headerButtonBack.buttonIcon
+                .setOnClickListener { viewModel.onCalendarPreviousClicked() }
+
+            symptomButtonNext
+                .setOnClickListener { viewModel.onCalendarNextClicked() }
+
+            symptomCalendarChoiceSelection
+                .calendarButtonSevenDays
+                .setOnClickListener { viewModel.onLastSevenDaysStart() }
+
+            symptomCalendarChoiceSelection
+                .calendarButtonOneTwoWeeks
+                .setOnClickListener { viewModel.onOneToTwoWeeksAgoStart() }
+
+            symptomCalendarChoiceSelection
+                .calendarButtonMoreThanTwoWeeks
+                .setOnClickListener { viewModel.onMoreThanTwoWeeksStart() }
+
+            symptomCalendarChoiceSelection
+                .targetButtonVerify
+                .setOnClickListener { viewModel.onNoInformationStart() }
+        }
+    }
+
+    private fun updateButtons(symptomStart: Symptoms.StartOf?) {
+        binding.symptomCalendarChoiceSelection.apply {
+            calendarButtonSevenDays.apply {
+                setTextColor(
+                    formatCalendarButtonStyleByState(symptomStart, Symptoms.StartOf.LastSevenDays)
+                )
+                backgroundTintList = ColorStateList.valueOf(
+                    formatCalendarBackgroundButtonStyleByState(
+                        symptomStart, Symptoms.StartOf.LastSevenDays
+                    )
+                )
+            }
+
+            calendarButtonOneTwoWeeks.apply {
+                setTextColor(
+                    formatCalendarButtonStyleByState(
+                        symptomStart,
+                        Symptoms.StartOf.OneToTwoWeeksAgo
+                    )
+                )
+                backgroundTintList = ColorStateList.valueOf(
+                    formatCalendarBackgroundButtonStyleByState(
+                        symptomStart, Symptoms.StartOf.OneToTwoWeeksAgo
+                    )
+                )
+            }
+
+            calendarButtonMoreThanTwoWeeks.apply {
+                setTextColor(
+                    formatCalendarButtonStyleByState(
+                        symptomStart,
+                        Symptoms.StartOf.MoreThanTwoWeeks
+                    )
+                )
+                backgroundTintList = ColorStateList.valueOf(
+                    formatCalendarBackgroundButtonStyleByState(
+                        symptomStart, Symptoms.StartOf.MoreThanTwoWeeks
+                    )
+                )
+            }
+            targetButtonVerify.apply {
+                setTextColor(
+                    formatCalendarButtonStyleByState(symptomStart, Symptoms.StartOf.NoInformation)
+                )
+                backgroundTintList = ColorStateList.valueOf(
+                    formatCalendarBackgroundButtonStyleByState(
+                        symptomStart, Symptoms.StartOf.NoInformation
+                    )
+                )
+            }
+        }
+
+        binding.symptomButtonNext.isEnabled = isEnableSymptomCalendarButtonByState(
+            symptomStart
+        )
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomCalendarModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarModule.kt
similarity index 90%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomCalendarModule.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarModule.kt
index d359621f48f15cce986628fcea9dec82e2eb64f3..82edb4598b871084eb556494657d61a2e6e0f505 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomCalendarModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarModule.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
+package de.rki.coronawarnapp.ui.submission.symptoms.calendar
 
 import dagger.Binds
 import dagger.Module
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e79975890a20c3d5572801fd7ecf5481f246e367
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt
@@ -0,0 +1,68 @@
+package de.rki.coronawarnapp.ui.submission.symptoms.calendar
+
+import androidx.lifecycle.asLiveData
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.submission.Symptoms
+import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.first
+import org.joda.time.LocalDate
+
+class SubmissionSymptomCalendarViewModel @AssistedInject constructor(
+    @Assisted private val symptomIndication: Symptoms.Indication,
+    dispatcherProvider: DispatcherProvider
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
+    private val symptomStartInternal = MutableStateFlow<Symptoms.StartOf?>(null)
+    val symptomStart = symptomStartInternal
+        .asLiveData(context = dispatcherProvider.Default)
+
+    val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
+
+    fun onCalendarNextClicked() {
+        launch {
+            val symptoms = Symptoms(
+                startOfSymptoms = symptomStartInternal.first(),
+                symptomIndication = symptomIndication
+            )
+            routeToScreen.postValue(
+                SubmissionNavigationEvents.NavigateToResultPositiveOtherWarning(symptoms)
+            )
+        }
+    }
+
+    fun onCalendarPreviousClicked() {
+        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSymptomIntroduction)
+    }
+
+    fun onLastSevenDaysStart() {
+        symptomStartInternal.value = Symptoms.StartOf.LastSevenDays
+    }
+
+    fun onOneToTwoWeeksAgoStart() {
+        symptomStartInternal.value = Symptoms.StartOf.OneToTwoWeeksAgo
+    }
+
+    fun onMoreThanTwoWeeksStart() {
+        symptomStartInternal.value = Symptoms.StartOf.MoreThanTwoWeeks
+    }
+
+    fun onNoInformationStart() {
+        symptomStartInternal.value = Symptoms.StartOf.NoInformation
+    }
+
+    fun onDateSelected(localDate: LocalDate?) {
+        symptomStartInternal.value = localDate?.let { Symptoms.StartOf.Date(it) }
+    }
+
+    @AssistedInject.Factory
+    interface Factory : CWAViewModelFactory<SubmissionSymptomCalendarViewModel> {
+
+        fun create(symptomIndication: Symptoms.Indication): SubmissionSymptomCalendarViewModel
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionSymptomIntroductionFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionFragment.kt
similarity index 60%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionSymptomIntroductionFragment.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionFragment.kt
index a7895306fc34a66757f801c3b52c7cce97f77ec7..c2af32534fb433369929cc714279aed588a3a4f1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionSymptomIntroductionFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionFragment.kt
@@ -1,20 +1,15 @@
-package de.rki.coronawarnapp.ui.submission.fragment
+package de.rki.coronawarnapp.ui.submission.symptoms.introduction
 
 import android.content.res.ColorStateList
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewGroup
 import android.widget.Button
 import androidx.activity.OnBackPressedCallback
 import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionSymptomIntroBinding
 import de.rki.coronawarnapp.submission.Symptoms
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionSymptomIntroductionViewModel
-import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.formatter.formatBackgroundButtonStyleByState
@@ -22,52 +17,62 @@ import de.rki.coronawarnapp.util.formatter.formatButtonStyleByState
 import de.rki.coronawarnapp.util.formatter.isEnableSymptomIntroButtonByState
 import de.rki.coronawarnapp.util.ui.doNavigate
 import de.rki.coronawarnapp.util.ui.observe2
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
 import javax.inject.Inject
 
-class SubmissionSymptomIntroductionFragment : Fragment(), AutoInject {
+class SubmissionSymptomIntroductionFragment : Fragment(R.layout.fragment_submission_symptom_intro),
+    AutoInject {
 
     @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
     private val viewModel: SubmissionSymptomIntroductionViewModel by cwaViewModels { viewModelFactory }
-    private var _binding: FragmentSubmissionSymptomIntroBinding? = null
-    private val binding: FragmentSubmissionSymptomIntroBinding get() = _binding!!
-    private val submissionViewModel: SubmissionViewModel by activityViewModels()
-
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        _binding = FragmentSubmissionSymptomIntroBinding.inflate(inflater)
-        binding.submissionViewModel = submissionViewModel
-        binding.lifecycleOwner = this
-        return binding.root
-    }
 
-    override fun onDestroyView() {
-        super.onDestroyView()
-        _binding = null
-    }
+    private val binding: FragmentSubmissionSymptomIntroBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        setButtonOnClickListener()
 
         viewModel.routeToScreen.observe2(this) {
             when (it) {
-                is SubmissionNavigationEvents.NavigateToSymptomCalendar -> navigateToNext()
+                is SubmissionNavigationEvents.NavigateToSymptomCalendar -> doNavigate(
+                    SubmissionSymptomIntroductionFragmentDirections
+                        .actionSubmissionSymptomIntroductionFragmentToSubmissionSymptomCalendarFragment(
+                            symptomIndication = it.symptomIndication
+                        )
+                )
+                is SubmissionNavigationEvents.NavigateToResultPositiveOtherWarning -> doNavigate(
+                    SubmissionSymptomIntroductionFragmentDirections
+                        .actionSubmissionSymptomIntroductionFragmentToSubmissionResultPositiveOtherWarningFragment(
+                            it.symptoms
+                        )
+                )
                 is SubmissionNavigationEvents.NavigateToTestResult -> handleSubmissionCancellation()
             }
         }
 
-        submissionViewModel.symptomIndication.observe(viewLifecycleOwner, {
+        viewModel.symptomIndication.observe2(this) {
             updateButtons(it)
-        })
+        }
 
         requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backCallback)
 
-        submissionViewModel.initSymptoms()
+        binding.apply {
+            submissionSymptomHeader.headerButtonBack.buttonIcon
+                .setOnClickListener { viewModel.onPreviousClicked() }
+
+            symptomButtonNext
+                .setOnClickListener { viewModel.onNextClicked() }
+
+            symptomChoiceSelection.targetButtonApply
+                .setOnClickListener { viewModel.onPositiveSymptomIndication() }
+
+            symptomChoiceSelection.targetButtonReject
+                .setOnClickListener { viewModel.onNegativeSymptomIndication() }
+
+            symptomChoiceSelection.targetButtonVerify
+                .setOnClickListener { viewModel.onNoInformationSymptomIndication() }
+        }
     }
 
     private val backCallback: OnBackPressedCallback =
@@ -117,26 +122,9 @@ class SubmissionSymptomIntroductionFragment : Fragment(), AutoInject {
             )
     }
 
-    private fun navigateToNext() {
-
-        if (submissionViewModel.symptomIndication.value!! == Symptoms.Indication.POSITIVE) {
-            doNavigate(
-                SubmissionSymptomIntroductionFragmentDirections
-                    .actionSubmissionSymptomIntroductionFragmentToSubmissionSymptomCalendarFragment()
-            )
-        } else {
-            doNavigate(
-                SubmissionSymptomIntroductionFragmentDirections
-                    .actionSubmissionSymptomIntroductionFragmentToSubmissionResultPositiveOtherWarningFragment()
-            )
-        }
-    }
-
     /**
      * Opens a Dialog that warns user
      * when they're about to cancel the submission flow
-     * @see DialogHelper
-     * @see navigateToPreviousScreen
      */
     private fun handleSubmissionCancellation() {
         DialogHelper.showDialog(
@@ -147,37 +135,13 @@ class SubmissionSymptomIntroductionFragment : Fragment(), AutoInject {
                 R.string.submission_error_dialog_confirm_cancellation_button_positive,
                 R.string.submission_error_dialog_confirm_cancellation_button_negative,
                 true,
-                ::navigateToPreviousScreen
+                {
+                    doNavigate(
+                        SubmissionSymptomIntroductionFragmentDirections
+                            .actionSubmissionSymptomIntroductionFragmentToSubmissionResultFragment()
+                    )
+                }
             )
         )
     }
-
-    private fun navigateToPreviousScreen() {
-        doNavigate(
-            SubmissionSymptomIntroductionFragmentDirections
-                .actionSubmissionSymptomIntroductionFragmentToSubmissionResultFragment()
-        )
-    }
-
-    private fun setButtonOnClickListener() {
-        binding
-            .submissionSymptomHeader.headerButtonBack.buttonIcon
-            .setOnClickListener { viewModel.onPreviousClicked() }
-
-        binding
-            .symptomButtonNext
-            .setOnClickListener { viewModel.onNextClicked() }
-
-        binding
-            .symptomChoiceSelection.targetButtonApply
-            .setOnClickListener { submissionViewModel.onPositiveSymptomIndication() }
-
-        binding
-            .symptomChoiceSelection.targetButtonReject
-            .setOnClickListener { submissionViewModel.onNegativeSymptomIndication() }
-
-        binding
-            .symptomChoiceSelection.targetButtonVerify
-            .setOnClickListener { submissionViewModel.onNoInformationSymptomIndication() }
-    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomIntroductionModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionModule.kt
similarity index 89%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomIntroductionModule.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionModule.kt
index 53792300528498a2ff95abcfe6d4d2fa69c10628..40b49ba8e839ffc6d3cc687d375632e93678ec79 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomIntroductionModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionModule.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
+package de.rki.coronawarnapp.ui.submission.symptoms.introduction
 
 import dagger.Binds
 import dagger.Module
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..43f7550b426a28cfee0e10c8360906c685c4aaf2
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt
@@ -0,0 +1,61 @@
+package de.rki.coronawarnapp.ui.submission.symptoms.introduction
+
+import androidx.lifecycle.asLiveData
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.submission.Symptoms
+import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+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.MutableStateFlow
+import kotlinx.coroutines.flow.first
+
+class SubmissionSymptomIntroductionViewModel @AssistedInject constructor(
+    dispatcherProvider: DispatcherProvider
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
+    private val symptomIndicationInternal = MutableStateFlow<Symptoms.Indication?>(null)
+    val symptomIndication = symptomIndicationInternal
+        .asLiveData(context = dispatcherProvider.Default)
+
+    val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
+
+    fun onNextClicked() {
+        launch {
+            when (symptomIndicationInternal.first()) {
+                Symptoms.Indication.POSITIVE -> SubmissionNavigationEvents.NavigateToSymptomCalendar(
+                    Symptoms.Indication.POSITIVE
+                )
+                Symptoms.Indication.NEGATIVE -> SubmissionNavigationEvents.NavigateToResultPositiveOtherWarning(
+                    symptoms = Symptoms(
+                        startOfSymptoms = null,
+                        symptomIndication = Symptoms.Indication.NEGATIVE
+                    )
+                )
+                else -> SubmissionNavigationEvents.NavigateToResultPositiveOtherWarning(
+                    symptoms = Symptoms.NO_INFO_GIVEN
+                )
+            }.let { routeToScreen.postValue(it) }
+        }
+    }
+
+    fun onPreviousClicked() {
+        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToTestResult)
+    }
+
+    fun onPositiveSymptomIndication() {
+        symptomIndicationInternal.value = Symptoms.Indication.POSITIVE
+    }
+
+    fun onNegativeSymptomIndication() {
+        symptomIndicationInternal.value = Symptoms.Indication.NEGATIVE
+    }
+
+    fun onNoInformationSymptomIndication() {
+        symptomIndicationInternal.value = Symptoms.Indication.NO_INFORMATION
+    }
+
+    @AssistedInject.Factory
+    interface Factory : SimpleCWAViewModelFactory<SubmissionSymptomIntroductionViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanFragment.kt
similarity index 69%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTanFragment.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanFragment.kt
index f021bdc4d7b675c3c40cff9513773999cab8d873..a9c953118a6983ab44db4e83d669d41185e25c00 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTanFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanFragment.kt
@@ -1,10 +1,9 @@
-package de.rki.coronawarnapp.ui.submission.fragment
+package de.rki.coronawarnapp.ui.submission.tan
 
 import android.os.Bundle
 import android.view.View
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionTanBinding
 import de.rki.coronawarnapp.exception.http.BadRequestException
@@ -13,14 +12,11 @@ import de.rki.coronawarnapp.exception.http.CwaServerError
 import de.rki.coronawarnapp.exception.http.CwaWebException
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.submission.ApiRequestState
-import de.rki.coronawarnapp.ui.submission.TanConstants
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionTanViewModel
-import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.util.DialogHelper
-import de.rki.coronawarnapp.util.TanHelper
 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.setGone
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
@@ -33,67 +29,33 @@ import javax.inject.Inject
 class SubmissionTanFragment : Fragment(R.layout.fragment_submission_tan), AutoInject {
 
     @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
-    private val submissionViewModel: SubmissionViewModel by viewModels()
     private val viewModel: SubmissionTanViewModel by cwaViewModels { viewModelFactory }
-    private val binding: FragmentSubmissionTanBinding by viewBindingLazy()
 
-    private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
-        return when (exception) {
-            is BadRequestException -> DialogHelper.DialogInstance(
-                requireActivity(),
-                R.string.submission_error_dialog_web_test_paired_title_tan,
-                R.string.submission_error_dialog_web_test_paired_body_tan,
-                R.string.submission_error_dialog_web_test_paired_button_positive,
-                null,
-                true,
-                ::goBack
-            )
-            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,
-                ::goBack
-            )
-            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,
-                ::goBack
-            )
-        }
-    }
+    private val binding: FragmentSubmissionTanBinding by viewBindingLazy()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        binding.viewmodel = viewModel
 
-        binding.submissionTanContent.submissionTanInput.listener = { tan ->
-            resetError()
+        viewModel.state.observe2(this) {
+            binding.uiState = it
 
-            viewModel.tan.value = tan
+            submission_tan_character_error.setGone(it.areCharactersCorrect)
+            submission_tan_error.setGone(it.isTanValidFormat)
+        }
 
-            if (tan != null) {
-                if (!TanHelper.allCharactersValid(tan))
-                    showCharacterError()
+        binding.submissionTanContent.submissionTanInput.listener = { tan ->
+            submission_tan_character_error.visibility = View.GONE
+            submission_tan_error.visibility = View.GONE
 
-                if (tan.length == TanConstants.MAX_LENGTH && !TanHelper.isChecksumValid(tan))
-                    showTanError()
-            }
+            viewModel.onTanChanged(tan)
         }
 
-        binding.submissionTanButtonEnter.setOnClickListener { storeTanAndContinue() }
+        binding.submissionTanButtonEnter.setOnClickListener {
+            viewModel.onTanSubmit()
+        }
         binding.submissionTanHeader.headerButtonBack.buttonIcon.setOnClickListener { goBack() }
 
-        submissionViewModel.registrationState.observeEvent(viewLifecycleOwner) {
+        viewModel.registrationState.observe2(this) {
             binding.submissionTanSpinner.visibility = when (it) {
                 ApiRequestState.STARTED -> View.VISIBLE
                 else -> View.GONE
@@ -106,24 +68,11 @@ class SubmissionTanFragment : Fragment(R.layout.fragment_submission_tan), AutoIn
             }
         }
 
-        submissionViewModel.registrationError.observeEvent(viewLifecycleOwner) {
+        viewModel.registrationError.observe2(this) {
             DialogHelper.showDialog(buildErrorDialog(it))
         }
     }
 
-    private fun resetError() {
-        submission_tan_character_error.visibility = View.GONE
-        submission_tan_error.visibility = View.GONE
-    }
-
-    private fun showCharacterError() {
-        submission_tan_character_error.visibility = View.VISIBLE
-    }
-
-    private fun showTanError() {
-        submission_tan_error.visibility = View.VISIBLE
-    }
-
     override fun onResume() {
         super.onResume()
         binding.submissionTanRoot.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
@@ -131,14 +80,38 @@ class SubmissionTanFragment : Fragment(R.layout.fragment_submission_tan), AutoIn
 
     private fun goBack() = (activity as MainActivity).goBack()
 
-    private fun storeTanAndContinue() {
-        // verify input format
-        if (viewModel.isValidTanFormat.value != true)
-            return
-
-        // store locally
-        viewModel.storeTeletan()
-
-        submissionViewModel.doDeviceRegistration()
+    private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
+        return when (exception) {
+            is BadRequestException -> DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_error_dialog_web_test_paired_title_tan,
+                R.string.submission_error_dialog_web_test_paired_body_tan,
+                R.string.submission_error_dialog_web_test_paired_button_positive,
+                null,
+                true,
+                ::goBack
+            )
+            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,
+                ::goBack
+            )
+            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,
+                ::goBack
+            )
+        }
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTanModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanModule.kt
similarity index 90%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTanModule.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanModule.kt
index 5a05697d71229959b5716f7ba14c7c24ab095391..a33e0f4ca9d23dd81adf95d3eb6a3719a187d5a6 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTanModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanModule.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
+package de.rki.coronawarnapp.ui.submission.tan
 
 import dagger.Binds
 import dagger.Module
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b2fcfe37df70cdd766efa70050c84b350cba7336
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt
@@ -0,0 +1,81 @@
+package de.rki.coronawarnapp.ui.submission.tan
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.asLiveData
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.TransactionException
+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.storage.SubmissionRepository
+import de.rki.coronawarnapp.ui.submission.ApiRequestState
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+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.MutableStateFlow
+import kotlinx.coroutines.flow.map
+import timber.log.Timber
+
+class SubmissionTanViewModel @AssistedInject constructor(
+    dispatcherProvider: DispatcherProvider
+) : CWAViewModel() {
+
+    private val currentTan = MutableStateFlow(Tan(""))
+
+    val state = currentTan.map { currentTan ->
+        UIState(
+            isTanValid = currentTan.isTanValid,
+            isTanValidFormat = currentTan.isTanValidFormat,
+            areCharactersCorrect = currentTan.areCharactersValid
+        )
+    }.asLiveData(context = dispatcherProvider.Default)
+
+    val registrationState = MutableLiveData(ApiRequestState.IDLE)
+    val registrationError = SingleLiveEvent<CwaWebException>()
+
+    fun onTanChanged(tan: String) {
+        currentTan.value = Tan(tan)
+    }
+
+    fun onTanSubmit() {
+        val teletan = currentTan.value
+        if (!teletan.isTanValid) {
+            Timber.w("Tried to set invalid teletan: %s", teletan)
+            return
+        }
+        Timber.d("Storing teletan $teletan")
+        SubmissionRepository.setTeletan(teletan.value)
+
+        launch {
+            try {
+                registrationState.postValue(ApiRequestState.STARTED)
+                SubmissionService.asyncRegisterDevice()
+                registrationState.postValue(ApiRequestState.SUCCESS)
+            } catch (err: CwaWebException) {
+                registrationState.postValue(ApiRequestState.FAILED)
+                registrationError.postValue(err)
+            } catch (err: TransactionException) {
+                if (err.cause is CwaWebException) {
+                    registrationError.postValue(err.cause)
+                } else {
+                    err.report(ExceptionCategory.INTERNAL)
+                }
+                registrationState.postValue(ApiRequestState.FAILED)
+            } catch (err: Exception) {
+                registrationState.postValue(ApiRequestState.FAILED)
+                err.report(ExceptionCategory.INTERNAL)
+            }
+        }
+    }
+
+    data class UIState(
+        val isTanValid: Boolean = false,
+        val areCharactersCorrect: Boolean = false,
+        val isTanValidFormat: Boolean = false
+    )
+
+    @AssistedInject.Factory
+    interface Factory : SimpleCWAViewModelFactory<SubmissionTanViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/Tan.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/Tan.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a4d883605477fbc1295047d10155bb8bb5305b22
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/Tan.kt
@@ -0,0 +1,46 @@
+package de.rki.coronawarnapp.ui.submission.tan
+
+import java.nio.charset.StandardCharsets
+import java.security.MessageDigest
+import java.util.Locale
+
+data class Tan(
+    val value: String
+) {
+
+    val areCharactersValid = allCharactersValid(value)
+    val isTanValidFormat = value.length == MAX_LENGTH && isChecksumValid(value)
+    val isTanValid = areCharactersValid && isTanValidFormat
+
+    companion object {
+        const val MAX_LENGTH = 10
+        internal val ALPHA_NUMERIC_CHARS = ('a'..'z').plus('A'..'Z').plus('0'..'9')
+
+        private const val VALID_CHARACTERS = "23456789ABCDEFGHJKMNPQRSTUVWXYZ"
+
+        fun isChecksumValid(tan: String): Boolean {
+            if (tan.trim().length != MAX_LENGTH)
+                return false
+            val subTan = tan.substring(0, MAX_LENGTH - 1).toUpperCase(Locale.ROOT)
+            val tanDigest = MessageDigest.getInstance("SHA-256")
+                .digest(subTan.toByteArray(StandardCharsets.US_ASCII))
+            var checkChar = "%02x".format(tanDigest[0])[0]
+            if (checkChar == '0') checkChar = 'G'
+            if (checkChar == '1') checkChar = 'H'
+
+            return checkChar.toUpperCase() == tan.last().toUpperCase()
+        }
+
+        fun allCharactersValid(tan: String): Boolean {
+            for (character in tan) {
+                if (!isTanCharacterValid(character.toString()))
+                    return false
+            }
+            return true
+        }
+
+        fun isTanCharacterValid(character: String): Boolean {
+            return VALID_CHARACTERS.contains(character)
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/TanInput.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/TanInput.kt
similarity index 90%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/TanInput.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/TanInput.kt
index 91a3e227e730b652d2a98bdb9cf2b9f915358106..04c1e7a8f0040f8bff6199ad7d1e752fc46751e6 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/TanInput.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/TanInput.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.view
+package de.rki.coronawarnapp.ui.submission.tan
 
 import android.content.Context
 import android.os.Handler
@@ -13,9 +13,7 @@ import androidx.annotation.DimenRes
 import androidx.core.view.children
 import androidx.core.widget.doOnTextChanged
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.ui.submission.TanConstants
-import de.rki.coronawarnapp.util.TanHelper
-import kotlinx.android.synthetic.main.view_tan_input_edittext.view.tan_input_edittext
+import kotlinx.android.synthetic.main.view_tan_input_edittext.view.*
 import java.util.Locale
 import kotlin.math.max
 
@@ -36,14 +34,14 @@ class TanInput(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs
         InputFilter { source, _, _, _, _, _ -> source.filter { !it.isWhitespace() } }
     private val alphaNumericFilter = InputFilter { source, _, _, _, _, _ ->
         source.filter {
-            TanConstants.ALPHA_NUMERIC_CHARS.contains(it)
+            Tan.ALPHA_NUMERIC_CHARS.contains(it)
         }
     }
-    private var lengthFilter = InputFilter.LengthFilter(TanConstants.MAX_LENGTH)
+    private var lengthFilter = InputFilter.LengthFilter(Tan.MAX_LENGTH)
 
-    var listener: ((String?) -> Unit)? = null
+    var listener: ((String) -> Unit)? = null
 
-    private var tan: String? = null
+    private var tan: String = ""
 
     private val lineSpacing: Int
 
@@ -61,11 +59,12 @@ class TanInput(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs
         tan_input_edittext.filters = arrayOf(whitespaceFilter, alphaNumericFilter, lengthFilter)
 
         // register listener
-        tan_input_edittext.doOnTextChanged { text, _, _, _ -> updateTan(text) }
+        tan_input_edittext.doOnTextChanged { text, _, _, _ -> updateTan(text ?: "") }
         setOnClickListener { showKeyboard() }
 
         // initially show the keyboard
-        Handler().postDelayed({ showKeyboard() },
+        Handler().postDelayed(
+            { showKeyboard() },
             KEYBOARD_TRIGGER_DELAY
         )
     }
@@ -77,8 +76,8 @@ class TanInput(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs
         }
     }
 
-    private fun updateTan(text: CharSequence?) {
-        this.tan = text?.toString()?.toUpperCase(Locale.ROOT)
+    private fun updateTan(text: CharSequence) {
+        this.tan = text.toString().toUpperCase(Locale.ROOT)
         updateDigits()
         notifyListener()
     }
@@ -98,7 +97,7 @@ class TanInput(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs
         tanDigit.text = text
         tanDigit.background = when {
             text == EMPTY_STRING -> resources.getDrawable(R.drawable.tan_input_digit, null)
-            TanHelper.isTanCharacterValid(text) -> resources.getDrawable(
+            Tan.isTanCharacterValid(text) -> resources.getDrawable(
                 R.drawable.tan_input_digit_entered,
                 null
             )
@@ -106,14 +105,14 @@ class TanInput(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs
         }
 
         tanDigit.setTextColor(
-            if (TanHelper.isTanCharacterValid(text))
+            if (Tan.isTanCharacterValid(text))
                 resources.getColor(R.color.colorTextPrimary1, null)
             else
                 resources.getColor(R.color.colorTextSemanticRed, null)
         )
     }
 
-    private fun digitAtIndex(index: Int): String = tan?.getOrNull(index)?.toString() ?: EMPTY_STRING
+    private fun digitAtIndex(index: Int): String = tan.getOrNull(index)?.toString() ?: EMPTY_STRING
 
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
@@ -206,7 +205,7 @@ class TanInput(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs
     private fun calculateDigitDimension(availableWith: Int, textSize: Int): Pair<Int, Int> {
         val widthRequiredForSpacing =
             (DIGIT_SPACING_COUNT * getDimension(R.dimen.submission_tan_total_digit_spacing)) +
-                    (GROUP_SPACING_COUNT * getDimension(R.dimen.submission_tan_total_group_spacing))
+                (GROUP_SPACING_COUNT * getDimension(R.dimen.submission_tan_total_group_spacing))
 
         val remainingWidthForDigits = availableWith - widthRequiredForSpacing
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTestResultFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt
similarity index 70%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTestResultFragment.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt
index c0da337c77a6c1373d2090684ec976173f218194..0655e3e5893e889f563f5b0b7361e3f9e4efcf0d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionTestResultFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission.fragment
+package de.rki.coronawarnapp.ui.submission.testresult
 
 import android.app.AlertDialog
 import android.os.Bundle
@@ -6,7 +6,6 @@ import android.view.View
 import android.view.accessibility.AccessibilityEvent
 import androidx.activity.OnBackPressedCallback
 import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultBinding
 import de.rki.coronawarnapp.exception.http.CwaClientError
@@ -14,11 +13,7 @@ import de.rki.coronawarnapp.exception.http.CwaServerError
 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.ui.submission.viewmodel.SubmissionTestResultViewModel
-import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
-import de.rki.coronawarnapp.util.DeviceUIState
 import de.rki.coronawarnapp.util.DialogHelper
-import de.rki.coronawarnapp.util.di.AppInjector
 import de.rki.coronawarnapp.util.di.AutoInject
 import de.rki.coronawarnapp.util.observeEvent
 import de.rki.coronawarnapp.util.ui.doNavigate
@@ -26,20 +21,13 @@ import de.rki.coronawarnapp.util.ui.observe2
 import de.rki.coronawarnapp.util.ui.viewBindingLazy
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
 import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.withContext
 import javax.inject.Inject
 
-/**
- * A simple [Fragment] subclass.
- */
 class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_result),
     AutoInject {
 
     @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
     private val viewModel: SubmissionTestResultViewModel by cwaViewModels { viewModelFactory }
-    private val submissionViewModel: SubmissionViewModel by activityViewModels()
 
     private val binding: FragmentSubmissionTestResultBinding by viewBindingLazy()
 
@@ -86,7 +74,11 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        binding.submissionViewModel = submissionViewModel
+
+        viewModel.uiState.observe2(this) {
+            binding.uiState = it
+        }
+
         // registers callback when the os level back is pressed
         requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backCallback)
 
@@ -95,14 +87,29 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_
 
         setButtonOnClickListener()
 
-        submissionViewModel.uiStateError.observeEvent(viewLifecycleOwner) {
+        viewModel.showTracingRequiredScreen.observe2(this) {
+            val tracingRequiredDialog = DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_test_result_dialog_tracing_required_title,
+                R.string.submission_test_result_dialog_tracing_required_message,
+                R.string.submission_test_result_dialog_tracing_required_button
+            )
+            DialogHelper.showDialog(tracingRequiredDialog)
+        }
+
+        viewModel.uiStateError.observeEvent(viewLifecycleOwner) {
             DialogHelper.showDialog(buildErrorDialog(it))
         }
 
-        submissionViewModel.deviceUiState.observe2(this) { uiState ->
-            if (uiState == DeviceUIState.PAIRED_REDEEMED) {
-                showRedeemedTokenWarningDialog()
-            }
+        viewModel.showRedeemedTokenWarning.observe2(this) {
+            val dialog = DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_error_dialog_web_tan_redeemed_title,
+                R.string.submission_error_dialog_web_tan_redeemed_body,
+                R.string.submission_error_dialog_web_tan_redeemed_button_positive
+            )
+
+            DialogHelper.showDialog(dialog)
         }
 
         viewModel.routeToScreen.observe2(this) {
@@ -115,7 +122,9 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_
                 is SubmissionNavigationEvents.NavigateToResultPositiveOtherWarning ->
                     doNavigate(
                         SubmissionTestResultFragmentDirections
-                            .actionSubmissionResultFragmentToSubmissionResultPositiveOtherWarningFragment()
+                            .actionSubmissionResultFragmentToSubmissionResultPositiveOtherWarningFragment(
+                                it.symptoms
+                            )
                     )
                 is SubmissionNavigationEvents.NavigateToMainActivity ->
                     doNavigate(
@@ -125,17 +134,6 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_
         }
     }
 
-    private fun showRedeemedTokenWarningDialog() {
-        val dialog = DialogHelper.DialogInstance(
-            requireActivity(),
-            R.string.submission_error_dialog_web_tan_redeemed_title,
-            R.string.submission_error_dialog_web_tan_redeemed_body,
-            R.string.submission_error_dialog_web_tan_redeemed_button_positive
-        )
-
-        DialogHelper.showDialog(dialog)
-    }
-
     override fun onResume() {
         super.onResume()
         binding.submissionTestResultContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
@@ -160,12 +158,11 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_
         }
 
         binding.submissionTestResultButtonPositiveContinue.setOnClickListener {
-            continueIfTracingEnabled(false)
+            viewModel.onContinuePressed()
         }
 
         binding.submissionTestResultButtonPositiveContinueWithoutSymptoms.setOnClickListener {
-            submissionViewModel.onNoInformationSymptomIndication()
-            continueIfTracingEnabled(true)
+            viewModel.onContinueWithoutSymptoms()
         }
 
         binding.submissionTestResultButtonInvalidRemoveTest.setOnClickListener {
@@ -177,31 +174,6 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_
         }
     }
 
-    private fun continueIfTracingEnabled(skipSymptomSubmission: Boolean) {
-        // TODO Workaround until we have a VM injected that can handle this
-        submissionViewModel.launch {
-            val isTracingEnabled = AppInjector.component.enfClient.isTracingEnabled.first()
-            withContext(Dispatchers.Main) {
-                if (!isTracingEnabled) {
-                    val tracingRequiredDialog = DialogHelper.DialogInstance(
-                        requireActivity(),
-                        R.string.submission_test_result_dialog_tracing_required_title,
-                        R.string.submission_test_result_dialog_tracing_required_message,
-                        R.string.submission_test_result_dialog_tracing_required_button
-                    )
-                    DialogHelper.showDialog(tracingRequiredDialog)
-                    return@withContext
-                }
-
-                if (skipSymptomSubmission) {
-                    viewModel.onContinueNoSymptomsPressed()
-                } else {
-                    viewModel.onContinuePressed()
-                }
-            }
-        }
-    }
-
     private fun removeTestAfterConfirmation() {
         val removeTestDialog = DialogHelper.DialogInstance(
             requireActivity(),
@@ -210,8 +182,7 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_
             R.string.submission_test_result_dialog_remove_test_button_positive,
             R.string.submission_test_result_dialog_remove_test_button_negative,
             positiveButtonFunction = {
-                submissionViewModel.deregisterTestFromDevice()
-                viewModel.onNavigateTestRemoved()
+                viewModel.deregisterTestFromDevice()
             }
         )
         DialogHelper.showDialog(removeTestDialog).apply {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTestResultModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultModule.kt
similarity index 90%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTestResultModule.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultModule.kt
index a10ad1086c01cfb63f2c70f7566f94db5e95f854..a81b3bf8e6f3c2eab36f14ed34f9807f902222a7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTestResultModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultModule.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
+package de.rki.coronawarnapp.ui.submission.testresult
 
 import dagger.Binds
 import dagger.Module
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
new file mode 100644
index 0000000000000000000000000000000000000000..96766c26904d12ab16c85b2809fe68477248cb0d
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt
@@ -0,0 +1,101 @@
+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.service.submission.SubmissionService
+import de.rki.coronawarnapp.storage.LocalData
+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.coroutine.DispatcherProvider
+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.combineTransform
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import timber.log.Timber
+
+class SubmissionTestResultViewModel @AssistedInject constructor(
+    dispatcherProvider: DispatcherProvider,
+    private val enfClient: ENFClient
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
+    val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
+    val showTracingRequiredScreen = SingleLiveEvent<Unit>()
+    val showRedeemedTokenWarning = SingleLiveEvent<Unit>()
+
+    private var wasRedeemedTokenErrorShown = false
+    private val tokenErrorMutex = Mutex()
+
+    val uiState: LiveData<TestResultUIState> = combineTransform(
+        SubmissionRepository.uiStateStateFlow,
+        SubmissionRepository.deviceUIStateFlow,
+        SubmissionRepository.testResultReceivedDateFlow
+    ) { apiRequestState, deviceUiState, resultDate ->
+
+        tokenErrorMutex.withLock {
+            if (!wasRedeemedTokenErrorShown && deviceUiState == DeviceUIState.PAIRED_REDEEMED) {
+                wasRedeemedTokenErrorShown = true
+                showRedeemedTokenWarning.postValue(Unit)
+            }
+        }
+
+        TestResultUIState(
+            apiRequestState = apiRequestState,
+            deviceUiState = deviceUiState,
+            testResultReceivedDate = resultDate
+        ).let { emit(it) }
+    }.asLiveData(context = dispatcherProvider.Default)
+
+    val uiStateError: LiveData<Event<CwaWebException>> = SubmissionRepository.uiStateError
+
+    fun onBackPressed() {
+        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity)
+    }
+
+    fun onContinuePressed() {
+        Timber.d("onContinuePressed()")
+        requireTracingOrShowError {
+            routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSymptomIntroduction)
+        }
+    }
+
+    fun onContinueWithoutSymptoms() {
+        Timber.d("onContinueWithoutSymptoms()")
+        requireTracingOrShowError {
+            Symptoms.NO_INFO_GIVEN
+                .let { SubmissionNavigationEvents.NavigateToResultPositiveOtherWarning(it) }
+                .let { routeToScreen.postValue(it) }
+        }
+    }
+
+    private fun requireTracingOrShowError(action: () -> Unit) = launch {
+        if (enfClient.isTracingEnabled.first()) {
+            action()
+        } else {
+            showTracingRequiredScreen.postValue(Unit)
+        }
+    }
+
+    fun deregisterTestFromDevice() {
+        launch {
+            Timber.d("deregisterTestFromDevice()")
+            SubmissionService.deleteTestGUID()
+            SubmissionService.deleteRegistrationToken()
+            LocalData.isAllowedToSubmitDiagnosisKeys(false)
+            LocalData.initialTestResultReceivedTimestamp(0L)
+
+            routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity)
+        }
+    }
+
+    @AssistedInject.Factory
+    interface Factory : SimpleCWAViewModelFactory<SubmissionTestResultViewModel>
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..b1909eb805d6cbaa923b41336f14d60cb30aa45b
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/TestResultUIState.kt
@@ -0,0 +1,11 @@
+package de.rki.coronawarnapp.ui.submission.testresult
+
+import de.rki.coronawarnapp.ui.submission.ApiRequestState
+import de.rki.coronawarnapp.util.DeviceUIState
+import java.util.Date
+
+data class TestResultUIState(
+    val apiRequestState: ApiRequestState,
+    val deviceUiState: DeviceUIState,
+    val testResultReceivedDate: Date?
+)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt
similarity index 61%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionFragmentModule.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt
index 0378f1666f6cad28665bc0026c3f48174860b0f1..07cdfe2f9d8fc00919ab4de58fb2b4d8c4af0592 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionFragmentModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission
+package de.rki.coronawarnapp.ui.submission.viewmodel
 
 import dagger.Module
 import dagger.android.ContributesAndroidInjector
@@ -6,24 +6,20 @@ import de.rki.coronawarnapp.ui.submission.fragment.SubmissionContactFragment
 import de.rki.coronawarnapp.ui.submission.fragment.SubmissionDispatcherFragment
 import de.rki.coronawarnapp.ui.submission.fragment.SubmissionDoneFragment
 import de.rki.coronawarnapp.ui.submission.fragment.SubmissionIntroFragment
-import de.rki.coronawarnapp.ui.submission.fragment.SubmissionQRCodeInfoFragment
-import de.rki.coronawarnapp.ui.submission.fragment.SubmissionQRCodeInfoModule
-import de.rki.coronawarnapp.ui.submission.fragment.SubmissionQRCodeScanFragment
-import de.rki.coronawarnapp.ui.submission.fragment.SubmissionResultPositiveOtherWarningFragment
-import de.rki.coronawarnapp.ui.submission.fragment.SubmissionSymptomCalendarFragment
-import de.rki.coronawarnapp.ui.submission.fragment.SubmissionSymptomIntroductionFragment
-import de.rki.coronawarnapp.ui.submission.fragment.SubmissionTanFragment
-import de.rki.coronawarnapp.ui.submission.fragment.SubmissionTestResultFragment
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionContactModule
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionDispatcherModule
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionDoneModule
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionIntroModule
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionQRCodeScanModule
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionResultPositiveOtherWarningModule
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionSymptomCalendarModule
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionSymptomIntroductionModule
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionTanModule
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionTestResultModule
+import de.rki.coronawarnapp.ui.submission.qrcode.info.SubmissionQRCodeInfoFragment
+import de.rki.coronawarnapp.ui.submission.qrcode.info.SubmissionQRCodeInfoModule
+import de.rki.coronawarnapp.ui.submission.qrcode.scan.SubmissionQRCodeScanFragment
+import de.rki.coronawarnapp.ui.submission.qrcode.scan.SubmissionQRCodeScanModule
+import de.rki.coronawarnapp.ui.submission.symptoms.calendar.SubmissionSymptomCalendarFragment
+import de.rki.coronawarnapp.ui.submission.symptoms.calendar.SubmissionSymptomCalendarModule
+import de.rki.coronawarnapp.ui.submission.symptoms.introduction.SubmissionSymptomIntroductionFragment
+import de.rki.coronawarnapp.ui.submission.symptoms.introduction.SubmissionSymptomIntroductionModule
+import de.rki.coronawarnapp.ui.submission.tan.SubmissionTanFragment
+import de.rki.coronawarnapp.ui.submission.tan.SubmissionTanModule
+import de.rki.coronawarnapp.ui.submission.testresult.SubmissionTestResultFragment
+import de.rki.coronawarnapp.ui.submission.testresult.SubmissionTestResultModule
+import de.rki.coronawarnapp.ui.submission.warnothers.SubmissionResultPositiveOtherWarningFragment
+import de.rki.coronawarnapp.ui.submission.warnothers.SubmissionResultPositiveOtherWarningModule
 
 @Module
 internal abstract class SubmissionFragmentModule {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt
index 347cb1c18a335a8a9d72926b04e4721c36bf23be..5a0fe235fefec008d7d167cfe47144d5fd755f95 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt
@@ -1,14 +1,23 @@
 package de.rki.coronawarnapp.ui.submission.viewmodel
 
+import de.rki.coronawarnapp.submission.Symptoms
+
 sealed class SubmissionNavigationEvents {
     object NavigateToContact : SubmissionNavigationEvents()
     object NavigateToDispatcher : SubmissionNavigationEvents()
     object NavigateToSubmissionDone : SubmissionNavigationEvents()
     object NavigateToSubmissionIntro : SubmissionNavigationEvents()
     object NavigateToQRCodeScan : SubmissionNavigationEvents()
-    object NavigateToResultPositiveOtherWarning : SubmissionNavigationEvents()
+
+    data class NavigateToResultPositiveOtherWarning(
+        val symptoms: Symptoms
+    ) : SubmissionNavigationEvents()
+
     object NavigateToSymptomSubmission : SubmissionNavigationEvents()
-    object NavigateToSymptomCalendar : SubmissionNavigationEvents()
+    data class NavigateToSymptomCalendar(
+        val symptomIndication: Symptoms.Indication
+    ) : SubmissionNavigationEvents()
+
     object NavigateToSymptomIntroduction : SubmissionNavigationEvents()
     object NavigateToTAN : SubmissionNavigationEvents()
     object NavigateToTestResult : SubmissionNavigationEvents()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeScanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeScanViewModel.kt
deleted file mode 100644
index ae575ce54fc6d2cc6cd8ab3c3d2a7018f955b345..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeScanViewModel.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
-
-import com.squareup.inject.assisted.AssistedInject
-import de.rki.coronawarnapp.util.ui.SingleLiveEvent
-import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
-import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
-
-class SubmissionQRCodeScanViewModel @AssistedInject constructor() : CWAViewModel() {
-
-    val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
-
-    fun onBackPressed() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToQRInfo)
-    }
-
-    fun onClosePressed() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToDispatcher)
-    }
-
-    @AssistedInject.Factory
-    interface Factory : SimpleCWAViewModelFactory<SubmissionQRCodeScanViewModel>
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionResultPositiveOtherWarningViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionResultPositiveOtherWarningViewModel.kt
deleted file mode 100644
index a2b242ceaca511028fdbfadfdff686813b416f4c..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionResultPositiveOtherWarningViewModel.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
-
-import com.squareup.inject.assisted.AssistedInject
-import de.rki.coronawarnapp.util.ui.SingleLiveEvent
-import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
-import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
-
-class SubmissionResultPositiveOtherWarningViewModel @AssistedInject constructor() : CWAViewModel() {
-
-    val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
-
-    fun onBackPressed() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToTestResult)
-    }
-
-    fun onWarnOthersPressed() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSymptomIntroduction)
-    }
-
-    fun onSubmissionComplete() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSubmissionDone)
-    }
-
-    @AssistedInject.Factory
-    interface Factory : SimpleCWAViewModelFactory<SubmissionResultPositiveOtherWarningViewModel>
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomCalendarViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomCalendarViewModel.kt
deleted file mode 100644
index 38bd530b33e363ebaebad3d0c1e11eb3ee2af652..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomCalendarViewModel.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
-
-import com.squareup.inject.assisted.AssistedInject
-import de.rki.coronawarnapp.util.ui.SingleLiveEvent
-import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
-import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
-
-class SubmissionSymptomCalendarViewModel @AssistedInject constructor() : CWAViewModel() {
-
-    val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
-
-    fun onCalendarNextClicked() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToResultPositiveOtherWarning)
-    }
-
-    fun onCalendarPreviousClicked() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSymptomIntroduction)
-    }
-
-    @AssistedInject.Factory
-    interface Factory : SimpleCWAViewModelFactory<SubmissionSymptomCalendarViewModel>
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomIntroductionViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomIntroductionViewModel.kt
deleted file mode 100644
index a79f4a1b3204e61c841e0b1419f9a59d03acefc3..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomIntroductionViewModel.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
-
-import com.squareup.inject.assisted.AssistedInject
-import de.rki.coronawarnapp.util.ui.SingleLiveEvent
-import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
-import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
-
-class SubmissionSymptomIntroductionViewModel @AssistedInject constructor() : CWAViewModel() {
-
-    val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
-
-    fun onNextClicked() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSymptomCalendar)
-    }
-
-    fun onPreviousClicked() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToTestResult)
-    }
-
-    @AssistedInject.Factory
-    interface Factory : SimpleCWAViewModelFactory<SubmissionSymptomIntroductionViewModel>
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTanViewModel.kt
deleted file mode 100644
index 2a33f7239397b9bb01df36b8608c8b23516c07bc..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTanViewModel.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
-
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.Transformations
-import com.squareup.inject.assisted.AssistedInject
-import de.rki.coronawarnapp.storage.SubmissionRepository
-import de.rki.coronawarnapp.ui.submission.TanConstants
-import de.rki.coronawarnapp.util.TanHelper
-import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
-import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
-import timber.log.Timber
-
-class SubmissionTanViewModel @AssistedInject constructor() : CWAViewModel() {
-
-    companion object {
-        private val TAG: String? = SubmissionTanViewModel::class.simpleName
-    }
-
-    val tan = MutableLiveData<String?>(null)
-
-    val isValidTanFormat =
-        Transformations.map(tan) {
-            it != null &&
-                    it.length == TanConstants.MAX_LENGTH &&
-                    TanHelper.isChecksumValid(it) &&
-                    TanHelper.allCharactersValid(it)
-        }
-
-    fun storeTeletan() {
-        val teletan = tan.value!!
-        Timber.d("Storing teletan $teletan")
-        SubmissionRepository.setTeletan(teletan)
-    }
-
-    @AssistedInject.Factory
-    interface Factory : SimpleCWAViewModelFactory<SubmissionTanViewModel>
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTestResultViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTestResultViewModel.kt
deleted file mode 100644
index 5c96c86ecfc27250f9499765a8e9a58a96536cf8..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTestResultViewModel.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
-
-import com.squareup.inject.assisted.AssistedInject
-import de.rki.coronawarnapp.util.ui.SingleLiveEvent
-import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
-import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
-
-class SubmissionTestResultViewModel @AssistedInject constructor() : CWAViewModel() {
-
-    val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
-
-    fun onBackPressed() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity)
-    }
-
-    fun onNavigateTestRemoved() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity)
-    }
-
-    fun onContinuePressed() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSymptomIntroduction)
-    }
-
-    fun onContinueNoSymptomsPressed() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToResultPositiveOtherWarning)
-    }
-
-    @AssistedInject.Factory
-    interface Factory : SimpleCWAViewModelFactory<SubmissionTestResultViewModel>
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionResultPositiveOtherWarningFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningFragment.kt
similarity index 62%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionResultPositiveOtherWarningFragment.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningFragment.kt
index 1d96f4804519e3e36d919cecfd46aa072190729f..65ad86643463f269fa71867d72bc81fc98e638c8 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionResultPositiveOtherWarningFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningFragment.kt
@@ -1,12 +1,12 @@
-package de.rki.coronawarnapp.ui.submission.fragment
+package de.rki.coronawarnapp.ui.submission.warnothers
 
 import android.content.Intent
 import android.os.Bundle
 import android.view.View
 import android.view.accessibility.AccessibilityEvent
 import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
 import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
 import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.FragmentSubmissionPositiveOtherWarningBinding
@@ -16,41 +16,111 @@ import de.rki.coronawarnapp.exception.http.CwaServerError
 import de.rki.coronawarnapp.exception.http.CwaWebException
 import de.rki.coronawarnapp.exception.http.ForbiddenException
 import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
 import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionResultPositiveOtherWarningViewModel
-import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel
 import de.rki.coronawarnapp.util.DialogHelper
-import de.rki.coronawarnapp.util.di.AppInjector
 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
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
-import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.withContext
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted
 import javax.inject.Inject
 
 class SubmissionResultPositiveOtherWarningFragment :
     Fragment(R.layout.fragment_submission_positive_other_warning),
     InternalExposureNotificationPermissionHelper.Callback, AutoInject {
 
+    private val navArgs by navArgs<SubmissionResultPositiveOtherWarningFragmentArgs>()
+
     @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
-    private val viewModel: SubmissionResultPositiveOtherWarningViewModel by cwaViewModels { viewModelFactory }
-    private val submissionViewModel: SubmissionViewModel by activityViewModels()
+    private val viewModel: SubmissionResultPositiveOtherWarningViewModel by cwaViewModelsAssisted(
+        factoryProducer = { viewModelFactory },
+        constructorCall = { factory, _ ->
+            factory as SubmissionResultPositiveOtherWarningViewModel.Factory
+            factory.create(navArgs.symptoms)
+        }
+    )
 
     private val binding: FragmentSubmissionPositiveOtherWarningBinding by viewBindingLazy()
-    private lateinit var internalExposureNotificationPermissionHelper:
-        InternalExposureNotificationPermissionHelper
+    private lateinit var internalExposureNotificationPermissionHelper: InternalExposureNotificationPermissionHelper
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        viewModel.uiState.observe2(this) {
+            binding.uiState = it
+        }
+
+        internalExposureNotificationPermissionHelper =
+            InternalExposureNotificationPermissionHelper(this, this)
+
+        binding.submissionPositiveOtherWarningButtonNext.setOnClickListener {
+            viewModel.onWarnOthersPressed()
+        }
+        binding.submissionPositiveOtherWarningHeader.headerButtonBack.buttonIcon.setOnClickListener {
+            viewModel.onBackPressed()
+        }
+
+        viewModel.routeToScreen.observe2(this) {
+            when (it) {
+                is SubmissionNavigationEvents.NavigateToSubmissionIntro -> doNavigate(
+                    SubmissionResultPositiveOtherWarningFragmentDirections
+                        .actionSubmissionResultPositiveOtherWarningFragmentToSubmissionDoneFragment()
+                )
+                is SubmissionNavigationEvents.NavigateToSubmissionDone -> doNavigate(
+                    SubmissionResultPositiveOtherWarningFragmentDirections
+                        .actionSubmissionResultPositiveOtherWarningFragmentToSubmissionDoneFragment()
+                )
+                is SubmissionNavigationEvents.NavigateToTestResult -> findNavController().popBackStack()
+            }
+        }
+
+        viewModel.submissionError.observe2(this) {
+            DialogHelper.showDialog(buildErrorDialog(it))
+        }
+
+        viewModel.requestKeySharing.observe2(this) {
+            internalExposureNotificationPermissionHelper.requestPermissionToShareKeys()
+        }
+
+        viewModel.showEnableTracingEvent.observe2(this) {
+            val tracingRequiredDialog = DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_test_result_dialog_tracing_required_title,
+                R.string.submission_test_result_dialog_tracing_required_message,
+                R.string.submission_test_result_dialog_tracing_required_button
+            )
+            DialogHelper.showDialog(tracingRequiredDialog)
+        }
+    }
 
     override fun onResume() {
         super.onResume()
         binding.submissionPositiveOtherPrivacyContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
     }
 
+    private fun navigateToSubmissionResultFragment() = doNavigate(
+        SubmissionResultPositiveOtherWarningFragmentDirections
+            .actionSubmissionResultPositiveOtherWarningFragmentToSubmissionResultFragment()
+    )
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        this.internalExposureNotificationPermissionHelper.onResolutionComplete(
+            requestCode,
+            resultCode
+        )
+    }
+
+    // InternalExposureNotificationPermissionHelper - callbacks
+    override fun onKeySharePermissionGranted(keys: List<TemporaryExposureKey>) {
+        super.onKeySharePermissionGranted(keys)
+        viewModel.onKeysShared(keys)
+    }
+
+    override fun onFailure(exception: Exception?) {
+        // NOOP
+    }
+
     private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
         return when (exception) {
             is BadRequestException -> DialogHelper.DialogInstance(
@@ -94,104 +164,4 @@ class SubmissionResultPositiveOtherWarningFragment :
             )
         }
     }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        binding.submissionViewModel = submissionViewModel
-
-        internalExposureNotificationPermissionHelper =
-            InternalExposureNotificationPermissionHelper(this, this)
-
-        setButtonOnClickListener()
-
-        submissionViewModel.submissionError.observeEvent(viewLifecycleOwner) {
-            DialogHelper.showDialog(buildErrorDialog(it))
-        }
-
-        submissionViewModel.submissionState.observe2(this) {
-            if (it == ApiRequestState.SUCCESS) {
-                viewModel.onSubmissionComplete()
-            }
-        }
-    }
-
-    private fun setButtonOnClickListener() {
-        binding.submissionPositiveOtherWarningButtonNext.setOnClickListener {
-            initiateWarningOthers()
-            viewModel.onWarnOthersPressed()
-        }
-        binding.submissionPositiveOtherWarningHeader.headerButtonBack.buttonIcon.setOnClickListener {
-            findNavController().popBackStack()
-            viewModel.onBackPressed()
-        }
-
-        viewModel.routeToScreen.observe2(this) {
-            when (it) {
-                is SubmissionNavigationEvents.NavigateToSubmissionIntro ->
-                    initiateWarningOthers()
-                is SubmissionNavigationEvents.NavigateToSubmissionDone ->
-                    navigateToSubmissionDoneFragment()
-                is SubmissionNavigationEvents.NavigateToTestResult ->
-                    findNavController().popBackStack()
-            }
-        }
-    }
-
-    private fun navigateToSubmissionResultFragment() =
-        doNavigate(
-            SubmissionResultPositiveOtherWarningFragmentDirections
-                .actionSubmissionResultPositiveOtherWarningFragmentToSubmissionResultFragment()
-        )
-
-    /**
-     * Navigate to submission done Fragment
-     * @see SubmissionDoneFragment
-     */
-    private fun navigateToSubmissionDoneFragment() =
-        doNavigate(
-            SubmissionResultPositiveOtherWarningFragmentDirections
-                .actionSubmissionResultPositiveOtherWarningFragmentToSubmissionDoneFragment()
-        )
-
-    private fun initiateWarningOthers() {
-        // TODO remove after VM Injection, workaround, should not happen in the fragment
-        submissionViewModel.launch {
-            val isTracingEnabled = AppInjector.component.enfClient.isTracingEnabled.first()
-            withContext(Dispatchers.Main) {
-                if (!isTracingEnabled) {
-                    val tracingRequiredDialog = DialogHelper.DialogInstance(
-                        requireActivity(),
-                        R.string.submission_test_result_dialog_tracing_required_title,
-                        R.string.submission_test_result_dialog_tracing_required_message,
-                        R.string.submission_test_result_dialog_tracing_required_button
-                    )
-                    DialogHelper.showDialog(tracingRequiredDialog)
-                } else {
-                    internalExposureNotificationPermissionHelper.requestPermissionToShareKeys()
-                }
-            }
-        }
-    }
-
-    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        this.internalExposureNotificationPermissionHelper.onResolutionComplete(
-            requestCode,
-            resultCode
-        )
-    }
-
-    // InternalExposureNotificationPermissionHelper - callbacks
-    override fun onKeySharePermissionGranted(keys: List<TemporaryExposureKey>) {
-        super.onKeySharePermissionGranted(keys)
-        if (keys.isNotEmpty()) {
-            submissionViewModel.submitDiagnosisKeys(keys)
-        } else {
-            submissionViewModel.submitWithNoDiagnosisKeys()
-            viewModel.onSubmissionComplete()
-        }
-    }
-
-    override fun onFailure(exception: Exception?) {
-        // NOOP
-    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionResultPositiveOtherWarningModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningModule.kt
similarity index 91%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionResultPositiveOtherWarningModule.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningModule.kt
index 41a0322ceeb2edc24cbd7d66d4eec75fec795688..3ff8fc4cf5f20ecee8060a6f925b5f16cb43bf13 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionResultPositiveOtherWarningModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningModule.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
+package de.rki.coronawarnapp.ui.submission.warnothers
 
 import dagger.Binds
 import dagger.Module
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e4e381a7e08386c46f6d43c9faaf1fb5ed4a850e
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningViewModel.kt
@@ -0,0 +1,110 @@
+package de.rki.coronawarnapp.ui.submission.warnothers
+
+import androidx.lifecycle.asLiveData
+import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.exception.ExceptionCategory
+import de.rki.coronawarnapp.exception.TransactionException
+import de.rki.coronawarnapp.exception.http.CwaWebException
+import de.rki.coronawarnapp.exception.reporting.report
+import de.rki.coronawarnapp.nearby.ENFClient
+import de.rki.coronawarnapp.service.submission.SubmissionService
+import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
+import de.rki.coronawarnapp.submission.Symptoms
+import de.rki.coronawarnapp.ui.submission.ApiRequestState
+import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.first
+import timber.log.Timber
+
+class SubmissionResultPositiveOtherWarningViewModel @AssistedInject constructor(
+    @Assisted private val symptoms: Symptoms,
+    dispatcherProvider: DispatcherProvider,
+    private val enfClient: ENFClient,
+    interoperabilityRepository: InteroperabilityRepository
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
+    private val submissionState = MutableStateFlow(ApiRequestState.IDLE)
+    val submissionError = SingleLiveEvent<CwaWebException>()
+
+    val uiState = combineTransform(
+        submissionState,
+        interoperabilityRepository.countryListFlow
+    ) { state, countries ->
+        WarnOthersState(
+            apiRequestState = state,
+            countryList = countries
+        ).also { emit(it) }
+    }.asLiveData(context = dispatcherProvider.Default)
+
+    val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent()
+
+    val requestKeySharing = SingleLiveEvent<Unit>()
+    val showEnableTracingEvent = SingleLiveEvent<Unit>()
+
+    fun onBackPressed() {
+        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToTestResult)
+    }
+
+    fun onWarnOthersPressed() {
+        launch {
+            if (enfClient.isTracingEnabled.first()) {
+                requestKeySharing.postValue(Unit)
+            } else {
+                showEnableTracingEvent.postValue(Unit)
+            }
+        }
+    }
+
+    fun onKeysShared(keys: List<TemporaryExposureKey>) {
+        if (keys.isNotEmpty()) {
+            submitDiagnosisKeys(keys)
+        } else {
+            submitWithNoDiagnosisKeys()
+            routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSubmissionDone)
+        }
+    }
+
+    private fun submitDiagnosisKeys(keys: List<TemporaryExposureKey>) {
+        Timber.d("submitDiagnosisKeys(keys=%s, symptoms=%s)", keys, symptoms)
+
+        submissionState.value = ApiRequestState.STARTED
+        launch {
+            try {
+                SubmissionService.asyncSubmitExposureKeys(keys, symptoms)
+                routeToScreen.postValue(SubmissionNavigationEvents.NavigateToSubmissionDone)
+
+                submissionState.value = ApiRequestState.SUCCESS
+            } catch (err: CwaWebException) {
+                submissionError.postValue(err)
+                submissionState.value = ApiRequestState.FAILED
+            } catch (err: TransactionException) {
+                if (err.cause is CwaWebException) {
+                    submissionError.postValue(err.cause)
+                } else {
+                    err.report(ExceptionCategory.INTERNAL)
+                }
+                submissionState.value = ApiRequestState.FAILED
+            } catch (err: Exception) {
+                submissionState.value = ApiRequestState.FAILED
+                err.report(ExceptionCategory.INTERNAL)
+            }
+        }
+    }
+
+    private fun submitWithNoDiagnosisKeys() {
+        Timber.d("submitWithNoDiagnosisKeys()")
+        SubmissionService.submissionSuccessful()
+    }
+
+    @AssistedInject.Factory
+    interface Factory : CWAViewModelFactory<SubmissionResultPositiveOtherWarningViewModel> {
+        fun create(symptoms: Symptoms): SubmissionResultPositiveOtherWarningViewModel
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/WarnOthersState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/WarnOthersState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..33809f6b7780c42c014d3c0bfb0698deee4aab2b
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/WarnOthersState.kt
@@ -0,0 +1,17 @@
+package de.rki.coronawarnapp.ui.submission.warnothers
+
+import de.rki.coronawarnapp.ui.Country
+import de.rki.coronawarnapp.ui.submission.ApiRequestState
+
+data class WarnOthersState(
+    val apiRequestState: ApiRequestState,
+    val countryList: List<Country>
+) {
+
+    fun isSubmitButtonEnabled(): Boolean =
+        apiRequestState == ApiRequestState.IDLE || apiRequestState == ApiRequestState.FAILED
+
+    fun isSubmitSpinnerVisible(): Boolean {
+        return apiRequestState == ApiRequestState.STARTED
+    }
+}
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
index c1079bbdd87eef234f6a31dc0a3cac320cd83265..6b668e6c420a52ee06796f5a4365794fd96452b6 100644
--- 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
@@ -1,175 +1,12 @@
 package de.rki.coronawarnapp.ui.viewmodel
 
 import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.viewModelScope
-import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
-import de.rki.coronawarnapp.exception.ExceptionCategory
-import de.rki.coronawarnapp.exception.TransactionException
-import de.rki.coronawarnapp.exception.http.CwaWebException
-import de.rki.coronawarnapp.exception.reporting.report
-import de.rki.coronawarnapp.service.submission.QRScanResult
-import de.rki.coronawarnapp.service.submission.SubmissionService
-import de.rki.coronawarnapp.storage.LocalData
+import androidx.lifecycle.asLiveData
 import de.rki.coronawarnapp.storage.SubmissionRepository
-import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
-import de.rki.coronawarnapp.submission.Symptoms
-import de.rki.coronawarnapp.ui.submission.ApiRequestState
-import de.rki.coronawarnapp.ui.submission.ScanStatus
 import de.rki.coronawarnapp.util.DeviceUIState
-import de.rki.coronawarnapp.util.Event
-import de.rki.coronawarnapp.util.di.AppInjector
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
-import kotlinx.coroutines.launch
-import org.joda.time.LocalDate
-import timber.log.Timber
-import java.util.Date
 
 class SubmissionViewModel : CWAViewModel() {
-    private val _scanStatus = MutableLiveData(Event(ScanStatus.STARTED))
 
-    private val _registrationState = MutableLiveData(Event(ApiRequestState.IDLE))
-    private val _registrationError = MutableLiveData<Event<CwaWebException>>(null)
-
-    private val _submissionState = MutableLiveData(ApiRequestState.IDLE)
-    private val _submissionError = MutableLiveData<Event<CwaWebException>>(null)
-    private val interoperabilityRepository: InteroperabilityRepository
-        get() = AppInjector.component.interoperabilityRepository
-
-    val scanStatus: LiveData<Event<ScanStatus>> = _scanStatus
-
-    val registrationState: LiveData<Event<ApiRequestState>> = _registrationState
-    val registrationError: LiveData<Event<CwaWebException>> = _registrationError
-
-    val uiStateState: LiveData<ApiRequestState> = SubmissionRepository.uiStateState
-    val uiStateError: LiveData<Event<CwaWebException>> = SubmissionRepository.uiStateError
-
-    val submissionState: LiveData<ApiRequestState> = _submissionState
-    val submissionError: LiveData<Event<CwaWebException>> = _submissionError
-
-    val testResultReceivedDate: LiveData<Date> = SubmissionRepository.testResultReceivedDate
-    val deviceUiState: LiveData<DeviceUIState> = SubmissionRepository.deviceUIState
-
-    val symptomIndication = MutableLiveData<Symptoms.Indication?>()
-    val symptomStart = MutableLiveData<Symptoms.StartOf?>()
-
-    val countryList by lazy {
-        MutableLiveData(interoperabilityRepository.countryList)
-    }
-
-    fun initSymptoms() {
-        symptomIndication.postValue(null)
-    }
-
-    fun initSymptomStart() {
-        symptomStart.postValue(null)
-    }
-
-    fun submitDiagnosisKeys(keys: List<TemporaryExposureKey>) {
-        val indication = symptomIndication.value
-        if (indication == null) {
-            Timber.w("symptoms indicator is null")
-            return
-        }
-        Symptoms(symptomStart.value, indication).also {
-            viewModelScope.launch {
-                try {
-                    _submissionState.value = ApiRequestState.STARTED
-                    SubmissionService.asyncSubmitExposureKeys(keys, it)
-                    _submissionState.value = ApiRequestState.SUCCESS
-                } catch (err: CwaWebException) {
-                    _submissionError.value = Event(err)
-                    _submissionState.value = ApiRequestState.FAILED
-                } catch (err: TransactionException) {
-                    if (err.cause is CwaWebException) {
-                        _submissionError.value = Event(err.cause)
-                    } else {
-                        err.report(ExceptionCategory.INTERNAL)
-                    }
-                    _submissionState.value = ApiRequestState.FAILED
-                } catch (err: Exception) {
-                    _submissionState.value = ApiRequestState.FAILED
-                    err.report(ExceptionCategory.INTERNAL)
-                }
-            }
-        }
-    }
-
-    fun doDeviceRegistration() = viewModelScope.launch {
-        try {
-            _registrationState.value = Event(ApiRequestState.STARTED)
-            SubmissionService.asyncRegisterDevice()
-            _registrationState.value = Event(ApiRequestState.SUCCESS)
-        } catch (err: CwaWebException) {
-            _registrationError.value = Event(err)
-            _registrationState.value = Event(ApiRequestState.FAILED)
-        } catch (err: TransactionException) {
-            if (err.cause is CwaWebException) {
-                _registrationError.value = Event(err.cause)
-            } else {
-                err.report(ExceptionCategory.INTERNAL)
-            }
-            _registrationState.value = Event(ApiRequestState.FAILED)
-        } catch (err: Exception) {
-            _registrationState.value = Event(ApiRequestState.FAILED)
-            err.report(ExceptionCategory.INTERNAL)
-        }
-    }
-
-    fun validateAndStoreTestGUID(rawResult: String) {
-        val scanResult = QRScanResult(rawResult)
-        if (scanResult.isValid) {
-            SubmissionService.storeTestGUID(scanResult.guid!!)
-            _scanStatus.value = Event(ScanStatus.SUCCESS)
-        } else {
-            _scanStatus.value = Event(ScanStatus.INVALID)
-        }
-    }
-
-    fun deleteTestGUID() {
-        SubmissionService.deleteTestGUID()
-    }
-
-    fun submitWithNoDiagnosisKeys() {
-        SubmissionService.submissionSuccessful()
-    }
-
-    fun deregisterTestFromDevice() {
-        deleteTestGUID()
-        SubmissionService.deleteRegistrationToken()
-        LocalData.isAllowedToSubmitDiagnosisKeys(false)
-        LocalData.initialTestResultReceivedTimestamp(0L)
-    }
-
-    fun onPositiveSymptomIndication() {
-        symptomIndication.postValue(Symptoms.Indication.POSITIVE)
-    }
-
-    fun onNegativeSymptomIndication() {
-        symptomIndication.postValue(Symptoms.Indication.NEGATIVE)
-    }
-
-    fun onNoInformationSymptomIndication() {
-        symptomIndication.postValue(Symptoms.Indication.NO_INFORMATION)
-    }
-
-    fun onLastSevenDaysStart() {
-        symptomStart.postValue(Symptoms.StartOf.LastSevenDays)
-    }
-
-    fun onOneToTwoWeeksAgoStart() {
-        symptomStart.postValue(Symptoms.StartOf.OneToTwoWeeksAgo)
-    }
-
-    fun onMoreThanTwoWeeksStart() {
-        symptomStart.postValue(Symptoms.StartOf.MoreThanTwoWeeks)
-    }
-
-    fun onNoInformationStart() {
-        symptomStart.postValue(Symptoms.StartOf.NoInformation)
-    }
-
-    fun onDateSelected(localDate: LocalDate?) {
-        symptomStart.postValue(if (localDate == null) null else Symptoms.StartOf.Date(localDate))
-    }
+    val deviceUiState: LiveData<DeviceUIState> = SubmissionRepository.deviceUIStateFlow.asLiveData()
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TanHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TanHelper.kt
deleted file mode 100644
index 5327b0f0073f1ced096a9a3ac329b6f750ec2981..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TanHelper.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package de.rki.coronawarnapp.util
-
-import de.rki.coronawarnapp.ui.submission.TanConstants.MAX_LENGTH
-import java.nio.charset.StandardCharsets
-import java.security.MessageDigest
-import java.util.Locale
-
-object TanHelper {
-    private const val VALID_CHARACTERS = "23456789ABCDEFGHJKMNPQRSTUVWXYZ"
-
-    fun isChecksumValid(tan: String): Boolean {
-        if (tan.trim().length != MAX_LENGTH)
-            return false
-        val subTan = tan.substring(0, MAX_LENGTH - 1).toUpperCase(Locale.ROOT)
-        val tanDigest = MessageDigest.getInstance("SHA-256")
-            .digest(subTan.toByteArray(StandardCharsets.US_ASCII))
-        var checkChar = "%02x".format(tanDigest[0])[0]
-        if (checkChar == '0') checkChar = 'G'
-        if (checkChar == '1') checkChar = 'H'
-
-        return checkChar.toUpperCase() == tan.last().toUpperCase()
-    }
-
-    fun allCharactersValid(tan: String): Boolean {
-        for (character in tan) {
-            if (!isTanCharacterValid(character.toString()))
-                return false
-        }
-        return true
-    }
-
-    fun isTanCharacterValid(character: String): Boolean {
-        return VALID_CHARACTERS.contains(character)
-    }
-}
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 df3c1283f4104a8323e413dd185f55b76abe23fb..156f8efee0289c727be6c85d0aa6e4a96b116674 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
@@ -55,12 +55,6 @@ fun formatTestResultSpinnerVisible(uiStateState: ApiRequestState?): Int =
 fun formatTestResultVisible(uiStateState: ApiRequestState?): Int =
     formatVisibility(uiStateState == ApiRequestState.SUCCESS)
 
-fun formatSubmitButtonEnabled(apiRequestState: ApiRequestState) =
-    apiRequestState == ApiRequestState.IDLE || apiRequestState == ApiRequestState.FAILED
-
-fun formatSubmitSpinnerVisible(apiRequestState: ApiRequestState) =
-    formatVisibility(apiRequestState == ApiRequestState.STARTED)
-
 fun formatTestResultStatusText(uiState: DeviceUIState?): String {
     val appContext = CoronaWarnApplication.getAppContext()
     return when (uiState) {
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_positive_other_warning.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_positive_other_warning.xml
index 555709a0ab052805a88e12fc87c3421347548227..fcf4c4a8300f153e0b8e3adc5bf5ee8544990ede 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_positive_other_warning.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_positive_other_warning.xml
@@ -4,13 +4,9 @@
     xmlns:tools="http://schemas.android.com/tools">
 
     <data>
-
-        <import type="de.rki.coronawarnapp.util.formatter.FormatterSubmissionHelper" />
-
         <variable
-            name="submissionViewModel"
-            type="de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel" />
-
+            name="uiState"
+            type="de.rki.coronawarnapp.ui.submission.warnothers.WarnOthersState" />
     </data>
 
 
@@ -20,7 +16,7 @@
         android:layout_height="match_parent"
         android:contentDescription="@string/submission_positive_other_warning_title"
         android:fillViewport="true"
-        tools:context=".ui.submission.fragment.SubmissionResultPositiveOtherWarningFragment">
+        tools:context=".ui.submission.warnothers.SubmissionResultPositiveOtherWarningFragment">
 
         <include
             android:id="@+id/submission_positive_other_warning_header"
@@ -37,7 +33,7 @@
             layout="@layout/include_submission_positive_other_warning"
             android:layout_width="@dimen/match_constraint"
             android:layout_height="@dimen/match_constraint"
-            app:countryData="@{submissionViewModel.countryList}"
+            app:countryData="@{uiState.countryList}"
             app:layout_constraintBottom_toTopOf="@+id/guideline_action"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintHorizontal_bias="0.0"
@@ -50,7 +46,7 @@
             style="@style/buttonPrimary"
             android:layout_width="@dimen/match_constraint"
             android:layout_height="wrap_content"
-            android:enabled="@{FormatterSubmissionHelper.formatSubmitButtonEnabled(submissionViewModel.submissionState)}"
+            android:enabled="@{uiState != null &amp;&amp; uiState.isSubmitButtonEnabled()}"
             android:text="@string/submission_positive_other_warning_button"
             android:textAllCaps="true"
             app:layout_constraintBottom_toBottomOf="parent"
@@ -65,7 +61,7 @@
             android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/spacing_large"
             android:indeterminate="true"
-            android:visibility="@{FormatterSubmissionHelper.formatSubmitSpinnerVisible(submissionViewModel.submissionState)}"
+            gone="@{uiState == null || !uiState.isSubmitSpinnerVisible()}"
             app:layout_constraintBottom_toBottomOf="@+id/submission_positive_other_warning_button_next"
             app:layout_constraintEnd_toEndOf="@+id/submission_positive_other_warning_button_next"
             app:layout_constraintStart_toStartOf="@+id/submission_positive_other_warning_button_next"
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 eb19ee84f777439eb95616fd29cd758dfc369bc5..a34166dd59df62849ed4df4572518c4ef5ea3e24 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
@@ -24,7 +24,7 @@
             android:layout_height="wrap_content"
             android:fillViewport="true"
             android:focusable="true"
-            tools:context=".ui.submission.fragment.SubmissionSymptomCalendarFragment">
+            tools:context=".ui.submission.symptoms.calendar.SubmissionSymptomCalendarFragment">
 
             <include
                 android:id="@+id/submission_symptom_calendar_header"
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_tan.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_tan.xml
index 3ef4b9f79ef0c89881003cd9e7f1ec963a4416c4..e499b7cef4937c65ecc526611b20d3eddeef6d98 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_submission_tan.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_tan.xml
@@ -3,11 +3,11 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools">
 
-    <data>
 
+    <data>
         <variable
-            name="viewmodel"
-            type="de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionTanViewModel" />
+            name="uiState"
+            type="de.rki.coronawarnapp.ui.submission.tan.SubmissionTanViewModel.UIState" />
     </data>
 
     <androidx.constraintlayout.widget.ConstraintLayout
@@ -16,7 +16,7 @@
         android:layout_height="match_parent"
         android:contentDescription="@string/submission_tan_accessibility_title"
         android:fillViewport="true"
-        tools:context=".ui.submission.fragment.SubmissionTanFragment">
+        tools:context=".ui.submission.tan.SubmissionTanFragment">
 
         <include
             android:id="@+id/submission_tan_header"
@@ -38,8 +38,7 @@
             app:layout_constraintBottom_toTopOf="@id/guideline_action"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/submission_tan_header"
-            app:viewmodel="@{viewmodel}" />
+            app:layout_constraintTop_toBottomOf="@+id/submission_tan_header" />
 
         <ProgressBar
             android:id="@+id/submission_tan_spinner"
@@ -57,7 +56,7 @@
             style="@style/buttonPrimary"
             android:layout_width="@dimen/match_constraint"
             android:layout_height="wrap_content"
-            android:enabled="@{viewmodel.isValidTanFormat}"
+            android:enabled="@{uiState.tanValid}"
             android:text="@string/submission_tan_button_text"
             android:textAllCaps="true"
             app:layout_constraintBottom_toBottomOf="parent"
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 2f7825389e55c461017cc670b808d92fd318c09f..3521faa0f64126875ffb9317c6782159fa998fff 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
@@ -7,8 +7,8 @@
         <import type="de.rki.coronawarnapp.util.formatter.FormatterSubmissionHelper" />
 
         <variable
-            name="submissionViewModel"
-            type="de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel" />
+            name="uiState"
+            type="de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState" />
     </data>
 
     <androidx.constraintlayout.widget.ConstraintLayout
@@ -34,7 +34,7 @@
             style="?android:attr/progressBarStyle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:visibility="@{FormatterSubmissionHelper.formatTestResultSpinnerVisible(submissionViewModel.uiStateState)}"
+            android:visibility="@{FormatterSubmissionHelper.formatTestResultSpinnerVisible(uiState.apiRequestState)}"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
@@ -48,18 +48,17 @@
             android:layout_width="@dimen/match_constraint"
             android:layout_height="@dimen/match_constraint"
             android:layout_marginBottom="@dimen/button_padding_top_bottom"
-            android:visibility="@{FormatterSubmissionHelper.formatTestResultVisible(submissionViewModel.uiStateState)}"
+            android:visibility="@{FormatterSubmissionHelper.formatTestResultVisible(uiState.apiRequestState)}"
             app:layout_constraintBottom_toTopOf="@+id/include_submission_test_result_buttons"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@+id/submission_test_result_header"
-            app:submissionViewModel="@{submissionViewModel}" />
+            app:uiState="@{uiState}" />
 
         <androidx.constraintlayout.widget.Barrier
             android:id="@+id/include_submission_test_result_buttons"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-
             app:barrierAllowsGoneWidgets="false"
             app:barrierDirection="top"
             app:constraint_referenced_ids="submission_test_result_button_pending_refresh,submission_test_result_button_invalid_remove_test,submission_test_result_button_positive_continue,submission_test_result_button_negative_remove_test" />
@@ -70,7 +69,7 @@
             android:layout_width="@dimen/match_constraint"
             android:layout_height="wrap_content"
             android:text="@string/submission_test_result_pending_refresh_button"
-            android:visibility="@{FormatterSubmissionHelper.formatTestResultPendingStepsVisible(submissionViewModel.deviceUiState)}"
+            android:visibility="@{FormatterSubmissionHelper.formatTestResultPendingStepsVisible(uiState.deviceUiState)}"
             app:layout_constraintBottom_toTopOf="@+id/submission_test_result_button_pending_remove_test"
             app:layout_constraintEnd_toStartOf="@+id/guideline_end"
             app:layout_constraintStart_toStartOf="@id/guideline_start"
@@ -82,7 +81,7 @@
             android:layout_width="@dimen/match_constraint"
             android:layout_height="wrap_content"
             android:text="@string/submission_test_result_pending_remove_test_button"
-            android:visibility="@{FormatterSubmissionHelper.formatTestResultPendingStepsVisible(submissionViewModel.deviceUiState)}"
+            android:visibility="@{FormatterSubmissionHelper.formatTestResultPendingStepsVisible(uiState.deviceUiState)}"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@+id/guideline_end"
             app:layout_constraintStart_toStartOf="@id/guideline_start"
@@ -94,7 +93,7 @@
             android:layout_width="@dimen/match_constraint"
             android:layout_height="wrap_content"
             android:text="@string/submission_test_result_invalid_remove_test_button"
-            android:visibility="@{FormatterSubmissionHelper.formatTestResultInvalidStepsVisible(submissionViewModel.deviceUiState)}"
+            android:visibility="@{FormatterSubmissionHelper.formatTestResultInvalidStepsVisible(uiState.deviceUiState)}"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@+id/guideline_end"
             app:layout_constraintStart_toStartOf="@id/guideline_start"
@@ -106,7 +105,7 @@
             android:layout_width="@dimen/match_constraint"
             android:layout_height="wrap_content"
             android:text="@string/submission_test_result_positive_continue_button_with_symptoms"
-            android:visibility="@{FormatterSubmissionHelper.formatTestResultPositiveStepsVisible(submissionViewModel.deviceUiState)}"
+            android:visibility="@{FormatterSubmissionHelper.formatTestResultPositiveStepsVisible(uiState.deviceUiState)}"
             app:layout_constraintBottom_toTopOf="@+id/submission_test_result_button_positive_continue_without_symptoms"
             app:layout_constraintEnd_toStartOf="@+id/guideline_end"
             app:layout_constraintStart_toStartOf="@id/guideline_start"
@@ -118,7 +117,7 @@
             android:layout_width="@dimen/match_constraint"
             android:layout_height="wrap_content"
             android:text="@string/submission_test_result_positive_continue_button_wo_symptoms"
-            android:visibility="@{FormatterSubmissionHelper.formatTestResultPositiveStepsVisible(submissionViewModel.deviceUiState)}"
+            android:visibility="@{FormatterSubmissionHelper.formatTestResultPositiveStepsVisible(uiState.deviceUiState)}"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@+id/guideline_end"
             app:layout_constraintStart_toStartOf="@id/guideline_start"
@@ -130,7 +129,7 @@
             android:layout_width="@dimen/match_constraint"
             android:layout_height="wrap_content"
             android:text="@string/submission_test_result_negative_remove_test_button"
-            android:visibility="@{FormatterSubmissionHelper.formatTestResultNegativeStepsVisible(submissionViewModel.deviceUiState)}"
+            android:visibility="@{FormatterSubmissionHelper.formatTestResultNegativeStepsVisible(uiState.deviceUiState)}"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@+id/guideline_end"
             app:layout_constraintStart_toStartOf="@id/guideline_start"
diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_tan.xml b/Corona-Warn-App/src/main/res/layout/include_submission_tan.xml
index 795e2414432a623b48ec39c9887c2a07db4e690d..5d31152d6d36f837475f713187a17be0acc85746 100644
--- a/Corona-Warn-App/src/main/res/layout/include_submission_tan.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_submission_tan.xml
@@ -2,18 +2,6 @@
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools">
-
-    <data>
-
-        <import type="de.rki.coronawarnapp.util.formatter.FormatterHelper" />
-
-        <import type="de.rki.coronawarnapp.util.formatter.FormatterSubmissionHelper" />
-
-        <variable
-            name="viewmodel"
-            type="de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionTanViewModel" />
-    </data>
-
     <ScrollView
         android:layout_width="match_parent"
         android:layout_height="match_parent">
@@ -35,7 +23,7 @@
                 app:layout_constraintStart_toStartOf="@+id/guideline_start"
                 app:layout_constraintTop_toTopOf="parent" />
 
-            <de.rki.coronawarnapp.ui.view.TanInput
+            <de.rki.coronawarnapp.ui.submission.tan.TanInput
                 android:id="@+id/submission_tan_input"
                 android:layout_width="@dimen/match_constraint"
                 android:layout_height="wrap_content"
diff --git a/Corona-Warn-App/src/main/res/layout/include_submission_test_result.xml b/Corona-Warn-App/src/main/res/layout/include_submission_test_result.xml
index b6b15d563c317d0d90058b10c1f10e64fd260c5b..a564e5c9632c893c096db4b4829bc72603ab5550 100644
--- a/Corona-Warn-App/src/main/res/layout/include_submission_test_result.xml
+++ b/Corona-Warn-App/src/main/res/layout/include_submission_test_result.xml
@@ -7,8 +7,8 @@
         <import type="de.rki.coronawarnapp.util.formatter.FormatterSubmissionHelper" />
 
         <variable
-            name="submissionViewModel"
-            type="de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel" />
+            name="uiState"
+            type="de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState" />
     </data>
 
 
@@ -28,11 +28,11 @@
                 android:layout_marginTop="@dimen/spacing_small"
                 android:focusable="true"
                 android:importantForAccessibility="yes"
-                app:deviceUIState="@{submissionViewModel.deviceUiState}"
+                app:deviceUIState="@{uiState.deviceUiState}"
                 app:layout_constraintEnd_toEndOf="@+id/guideline_card_end"
                 app:layout_constraintStart_toStartOf="@+id/guideline_card_start"
                 app:layout_constraintTop_toTopOf="parent"
-                app:registerDate="@{submissionViewModel.testResultReceivedDate}" />
+                app:registerDate="@{uiState.testResultReceivedDate}" />
 
             <TextView
                 android:id="@+id/submission_test_result_subtitle"
@@ -52,7 +52,7 @@
                 android:layout_width="@dimen/match_constraint"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_normal"
-                android:visibility="@{FormatterSubmissionHelper.formatTestResultPendingStepsVisible(submissionViewModel.deviceUiState)}"
+                android:visibility="@{FormatterSubmissionHelper.formatTestResultPendingStepsVisible(uiState.deviceUiState)}"
                 app:layout_constraintEnd_toEndOf="@+id/guideline_end"
                 app:layout_constraintStart_toStartOf="@+id/guideline_start"
                 app:layout_constraintTop_toBottomOf="@+id/submission_test_result_subtitle" />
@@ -63,7 +63,7 @@
                 android:layout_width="@dimen/match_constraint"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_normal"
-                android:visibility="@{FormatterSubmissionHelper.formatTestResultNegativeStepsVisible(submissionViewModel.deviceUiState)}"
+                android:visibility="@{FormatterSubmissionHelper.formatTestResultNegativeStepsVisible(uiState.deviceUiState)}"
                 app:layout_constraintEnd_toEndOf="@+id/guideline_end"
                 app:layout_constraintStart_toStartOf="@+id/guideline_start"
                 app:layout_constraintTop_toBottomOf="@+id/submission_test_result_subtitle" />
@@ -74,7 +74,7 @@
                 android:layout_width="@dimen/match_constraint"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_normal"
-                android:visibility="@{FormatterSubmissionHelper.formatTestResultNegativeStepsVisible(submissionViewModel.deviceUiState)}"
+                android:visibility="@{FormatterSubmissionHelper.formatTestResultNegativeStepsVisible(uiState.deviceUiState)}"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/submission_test_result_negative_steps" />
@@ -85,7 +85,7 @@
                 android:layout_width="@dimen/match_constraint"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_normal"
-                android:visibility="@{FormatterSubmissionHelper.formatTestResultPositiveStepsVisible(submissionViewModel.deviceUiState)}"
+                android:visibility="@{FormatterSubmissionHelper.formatTestResultPositiveStepsVisible(uiState.deviceUiState)}"
                 app:layout_constraintEnd_toEndOf="@+id/guideline_end"
                 app:layout_constraintStart_toStartOf="@+id/guideline_start"
                 app:layout_constraintTop_toBottomOf="@+id/submission_test_result_subtitle" />
@@ -96,7 +96,7 @@
                 android:layout_width="@dimen/match_constraint"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="@dimen/spacing_normal"
-                android:visibility="@{FormatterSubmissionHelper.formatTestResultInvalidStepsVisible(submissionViewModel.deviceUiState)}"
+                android:visibility="@{FormatterSubmissionHelper.formatTestResultInvalidStepsVisible(uiState.deviceUiState)}"
                 app:layout_constraintEnd_toEndOf="@+id/guideline_end"
                 app:layout_constraintStart_toStartOf="@+id/guideline_start"
                 app:layout_constraintTop_toBottomOf="@+id/submission_test_result_subtitle" />
diff --git a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
index 6d734cbc2904ca373f763df1ffa89578a6648666..44aea7e8c24b13c4aedcd91e535e6309c8f828b1 100644
--- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
+++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
@@ -224,7 +224,7 @@
     </fragment>
     <fragment
         android:id="@+id/submissionResultPositiveOtherWarningFragment"
-        android:name="de.rki.coronawarnapp.ui.submission.fragment.SubmissionResultPositiveOtherWarningFragment"
+        android:name="de.rki.coronawarnapp.ui.submission.warnothers.SubmissionResultPositiveOtherWarningFragment"
         android:label="fragment_submission_result_positive_other_warning"
         tools:layout="@layout/fragment_submission_positive_other_warning">
         <action
@@ -243,10 +243,13 @@
         <action
             android:id="@+id/action_submissionResultPositiveOtherWarningFragment_to_submissionSymptomIntroductionFragment"
             app:destination="@id/submissionSymptomIntroductionFragment" />
+        <argument
+            android:name="symptoms"
+            app:argType="de.rki.coronawarnapp.submission.Symptoms" />
     </fragment>
     <fragment
         android:id="@+id/submissionResultFragment"
-        android:name="de.rki.coronawarnapp.ui.submission.fragment.SubmissionTestResultFragment"
+        android:name="de.rki.coronawarnapp.ui.submission.testresult.SubmissionTestResultFragment"
         android:label="fragment_submission_result"
         tools:layout="@layout/fragment_submission_test_result">
         <argument
@@ -268,7 +271,7 @@
 
     <fragment
         android:id="@+id/submissionTanFragment"
-        android:name="de.rki.coronawarnapp.ui.submission.fragment.SubmissionTanFragment"
+        android:name="de.rki.coronawarnapp.ui.submission.tan.SubmissionTanFragment"
         android:label="fragment_submission_tan"
         tools:layout="@layout/fragment_submission_tan">
         <action
@@ -311,7 +314,7 @@
     </activity>
     <fragment
         android:id="@+id/submissionQRCodeInfoFragment"
-        android:name="de.rki.coronawarnapp.ui.submission.fragment.SubmissionQRCodeInfoFragment"
+        android:name="de.rki.coronawarnapp.ui.submission.qrcode.info.SubmissionQRCodeInfoFragment"
         android:label="SubmissionQRCodeInfoFragment">
         <action
             android:id="@+id/action_submissionQRCodeInfoFragment_to_submissionQRCodeScanFragment"
@@ -320,7 +323,7 @@
 
     <fragment
         android:id="@+id/submissionQRCodeScanFragment"
-        android:name="de.rki.coronawarnapp.ui.submission.fragment.SubmissionQRCodeScanFragment"
+        android:name="de.rki.coronawarnapp.ui.submission.qrcode.scan.SubmissionQRCodeScanFragment"
         android:label="SubmissionQRCodeScanFragment">
         <action
             android:id="@+id/action_submissionQRCodeScanFragment_to_submissionDispatcherFragment"
@@ -357,7 +360,7 @@
     </fragment>
     <fragment
         android:id="@+id/submissionSymptomIntroductionFragment"
-        android:name="de.rki.coronawarnapp.ui.submission.fragment.SubmissionSymptomIntroductionFragment"
+        android:name="de.rki.coronawarnapp.ui.submission.symptoms.introduction.SubmissionSymptomIntroductionFragment"
         android:label="SubmissionSymptomIntroductionFragment" >
 
         <action
@@ -372,14 +375,17 @@
     </fragment>
     <fragment
         android:id="@+id/submissionSymptomCalendarFragment"
-        android:name="de.rki.coronawarnapp.ui.submission.fragment.SubmissionSymptomCalendarFragment"
-        android:label="SubmissionSymptomCalendarFragment" >
+        android:name="de.rki.coronawarnapp.ui.submission.symptoms.calendar.SubmissionSymptomCalendarFragment"
+        android:label="SubmissionSymptomCalendarFragment">
         <action
             android:id="@+id/action_submissionCalendarFragment_to_submissionSymptomIntroductionFragment"
             app:destination="@id/submissionSymptomIntroductionFragment" />
         <action
             android:id="@+id/action_submissionSymptomCalendarFragment_to_submissionResultPositiveOtherWarningFragment"
             app:destination="@id/submissionResultPositiveOtherWarningFragment" />
+        <argument
+            android:name="symptomIndication"
+            app:argType="de.rki.coronawarnapp.submission.Symptoms$Indication" />
     </fragment>
 
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/AppConfigApiTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/AppConfigApiTest.kt
index 2e2d2cb5a31ecd7bfd98cc95dc61793c22341a88..671fd3ab13b4be98186c78f0e730204372e60efa 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/AppConfigApiTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/AppConfigApiTest.kt
@@ -8,7 +8,6 @@ import io.mockk.MockKAnnotations
 import io.mockk.clearAllMocks
 import io.mockk.every
 import io.mockk.impl.annotations.MockK
-import io.mockk.mockk
 import kotlinx.coroutines.runBlocking
 import okhttp3.ConnectionSpec
 import okhttp3.mockwebserver.MockResponse
@@ -32,7 +31,6 @@ class AppConfigApiTest : BaseIOTest() {
     private val cacheFiles = File(testDir, "cache")
     private val cacheDir = File(cacheFiles, "http_app-config")
 
-
     @BeforeEach
     fun setup() {
         MockKAnnotations.init(this)
@@ -69,7 +67,7 @@ class AppConfigApiTest : BaseIOTest() {
             cache = cache
         )
     }
-    
+
     @Test
     fun `application config download`() {
         val api = createAPI()
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt
index c9ecb45bccb3930a85efa6bdd454be17d4459bfd..541eedf627670fbf71a60a819a15cf71e661771d 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt
@@ -158,7 +158,7 @@ class ENFClientTest : BaseTest() {
                 mapOf(
                     "1" to Calculation(
                         identifier = "1",
-                        startedAt = Instant.EPOCH.plus(5),
+                        startedAt = Instant.EPOCH.plus(5)
                     ),
                     "2" to Calculation(
                         identifier = "2",
@@ -176,7 +176,7 @@ class ENFClientTest : BaseTest() {
                 mapOf(
                     "1" to Calculation(
                         identifier = "1",
-                        startedAt = Instant.EPOCH,
+                        startedAt = Instant.EPOCH
                     ),
                     "2" to Calculation(
                         identifier = "2",
@@ -251,7 +251,7 @@ class ENFClientTest : BaseTest() {
                     "2" to Calculation(
                         identifier = "2",
                         result = Calculation.Result.UPDATED_STATE,
-                        startedAt = Instant.EPOCH,
+                        startedAt = Instant.EPOCH
                     ),
                     "3" to Calculation(
                         identifier = "3",
@@ -266,4 +266,3 @@ class ENFClientTest : BaseTest() {
         }
     }
 }
-
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/calculationtracker/DefaultCalculationTrackerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/calculationtracker/DefaultCalculationTrackerTest.kt
index 5ae6cad6afb8e39c1389f8f668d1556c34f29ed1..067680cac59871576df6c49a6acb9eec6426e300 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/calculationtracker/DefaultCalculationTrackerTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/calculationtracker/DefaultCalculationTrackerTest.kt
@@ -116,7 +116,6 @@ class DefaultCalculationTrackerTest : BaseTest() {
             )
         }
 
-
         every { timeStamper.nowUTC } returns Instant.EPOCH.plus(1)
 
         createInstance(scope = this).apply {
@@ -233,7 +232,7 @@ class DefaultCalculationTrackerTest : BaseTest() {
             timeoutIgnoresFinishedCalcs.identifier to timeoutIgnoresFinishedCalcs,
             timeoutRunningOnEdge.identifier to timeoutRunningOnEdge,
             noTimeoutCalcRunning.identifier to noTimeoutCalcRunning,
-            noTimeOutCalcFinished.identifier to noTimeOutCalcFinished,
+            noTimeOutCalcFinished.identifier to noTimeOutCalcFinished
         )
 
         coEvery { storage.load() } returns calcData
@@ -256,7 +255,6 @@ class DefaultCalculationTrackerTest : BaseTest() {
 
                 this["3"] shouldBe timeoutRunningOnEdge
 
-
                 this["4"] shouldBe noTimeoutCalcRunning
                 this["5"] shouldBe noTimeOutCalcFinished
             }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/task/testtasks/SkippingTask.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/task/testtasks/SkippingTask.kt
index 3c832ad293951d1072960f4ffbefaee9fe1b0333..f04714e342280f267dfd7a55d7f34365b6b43c90 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/task/testtasks/SkippingTask.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/task/testtasks/SkippingTask.kt
@@ -18,7 +18,7 @@ class SkippingTask : QueueingTask() {
     }
 
     class Factory @Inject constructor(
-        private val taskByDagger: Provider<QueueingTask>,
+        private val taskByDagger: Provider<QueueingTask>
     ) : TaskFactory<DefaultProgress, Result> {
 
         override val config: TaskFactory.Config =
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RiskLevelTransactionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RiskLevelTransactionTest.kt
index ea8c5d2192fd484c46e862fea33580b70bf2d66b..5a6d942050d2262b7dd676d8a77b74ee07077b7b 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RiskLevelTransactionTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RiskLevelTransactionTest.kt
@@ -17,7 +17,6 @@ import de.rki.coronawarnapp.risk.RiskScoreAnalysis
 import de.rki.coronawarnapp.risk.TimeVariables
 import de.rki.coronawarnapp.server.protocols.internal.AppConfig
 import de.rki.coronawarnapp.server.protocols.internal.AttenuationDurationOuterClass
-import de.rki.coronawarnapp.server.protocols.internal.RiskScoreClassificationOuterClass
 import de.rki.coronawarnapp.server.protocols.internal.RiskScoreClassificationOuterClass.RiskScoreClass
 import de.rki.coronawarnapp.server.protocols.internal.RiskScoreClassificationOuterClass.RiskScoreClassification
 import de.rki.coronawarnapp.service.applicationconfiguration.ApplicationConfigurationService
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/SubmissionTanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/SubmissionTanViewModelTest.kt
deleted file mode 100644
index 8f49bcb3f001e37e3e3012895d795f3217c9485b..0000000000000000000000000000000000000000
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/SubmissionTanViewModelTest.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package de.rki.coronawarnapp.ui.submission
-
-import de.rki.coronawarnapp.storage.SubmissionRepository
-import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionTanViewModel
-import io.mockk.Runs
-import io.mockk.every
-import io.mockk.just
-import io.mockk.mockk
-import io.mockk.verify
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Test
-
-class SubmissionTanViewModelTest {
-    private var viewModel: SubmissionTanViewModel = SubmissionTanViewModel()
-
-    @Test
-    fun tanFormatValid() {
-        viewModel.tan.postValue("ZWFPC7NG47")
-        viewModel.isValidTanFormat.value?.let { assertTrue(it) }
-
-        viewModel.tan.postValue("ABC")
-        viewModel.isValidTanFormat.value?.let { assertFalse(it) }
-
-        viewModel.tan.postValue("ZWFPC7NG48")
-        viewModel.isValidTanFormat.value?.let { assertFalse(it) }
-
-        viewModel.tan.postValue("ZWFPC7NG4A")
-        viewModel.isValidTanFormat.value?.let { assertFalse(it) }
-    }
-
-    @Test
-    fun testTanStorage() {
-        val sr = mockk<SubmissionRepository> {
-            every { setTeletan(any()) } just Runs
-        }
-        val tan = "ZWFPC7NG47"
-        sr.setTeletan(tan)
-
-        verify(exactly = 1) {
-            sr.setTeletan(
-                withArg {
-                    assertEquals(it, tan)
-                })
-        }
-    }
-}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeInfoFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoFragmentViewModelTest.kt
similarity index 93%
rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeInfoFragmentViewModelTest.kt
rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoFragmentViewModelTest.kt
index 44023d6cd1cc8c3e8b8efe8b3846df0cbd45e1ea..d42ae7c1d4307f20c66c0c795901e657fb8a97a9 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeInfoFragmentViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/info/SubmissionQRCodeInfoFragmentViewModelTest.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.submission.viewmodel
+package de.rki.coronawarnapp.ui.submission.qrcode.info
 
 import io.kotest.matchers.shouldBe
 import org.junit.jupiter.api.Test
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt
similarity index 88%
rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModelTest.kt
rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt
index cbc54ad5d3a78b2dddbe89f2a525c0576ca03672..b518ae31a0ba845187e3889b415605b4507da462 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt
@@ -1,4 +1,4 @@
-package de.rki.coronawarnapp.ui.viewmodel
+package de.rki.coronawarnapp.ui.submission.qrcode.scan
 
 import de.rki.coronawarnapp.playbook.BackgroundNoise
 import de.rki.coronawarnapp.storage.LocalData
@@ -14,10 +14,11 @@ import org.junit.Assert
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
+import testhelpers.BaseTest
 import testhelpers.extensions.InstantExecutorExtension
 
 @ExtendWith(InstantExecutorExtension::class)
-class SubmissionViewModelTest {
+class SubmissionQRCodeScanViewModelTest : BaseTest() {
 
     @MockK lateinit var backgroundNoise: BackgroundNoise
 
@@ -32,7 +33,7 @@ class SubmissionViewModelTest {
         every { BackgroundNoise.getInstance() } returns backgroundNoise
     }
 
-    private fun createViewModel() = SubmissionViewModel()
+    private fun createViewModel() = SubmissionQRCodeScanViewModel()
 
     @Test
     fun scanStatusValid() {
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fe2bd9043d442472d3a2b8739ffa07a583545f85
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt
@@ -0,0 +1,58 @@
+package de.rki.coronawarnapp.ui.submission.tan
+
+import de.rki.coronawarnapp.storage.SubmissionRepository
+import io.kotest.matchers.shouldBe
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import testhelpers.BaseTest
+import testhelpers.TestDispatcherProvider
+import testhelpers.extensions.CoroutinesTestExtension
+import testhelpers.extensions.InstantExecutorExtension
+
+@ExtendWith(InstantExecutorExtension::class, CoroutinesTestExtension::class)
+class SubmissionTanViewModelTest : BaseTest() {
+
+    private fun createInstance() = SubmissionTanViewModel(
+        dispatcherProvider = TestDispatcherProvider
+    )
+
+    @Test
+    fun tanFormatValid() {
+        val viewModel = createInstance()
+        viewModel.state.observeForever { }
+
+        viewModel.onTanChanged("ZWFPC7NG47")
+        viewModel.state.value!!.isTanValid shouldBe true
+
+        viewModel.onTanChanged("ABC")
+        viewModel.state.value!!.isTanValid shouldBe false
+
+        viewModel.onTanChanged("ZWFPC7NG48")
+        viewModel.state.value!!.isTanValid shouldBe false
+
+        viewModel.onTanChanged("ZWFPC7NG4A")
+        viewModel.state.value!!.isTanValid shouldBe false
+    }
+
+    @Test
+    fun testTanStorage() {
+        val sr = mockk<SubmissionRepository> {
+            every { setTeletan(any()) } just Runs
+        }
+        val tan = "ZWFPC7NG47"
+        sr.setTeletan(tan)
+
+        verify(exactly = 1) {
+            sr.setTeletan(
+                withArg {
+                    it shouldBe tan
+                }
+            )
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/TanHelperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/TanTest.kt
similarity index 63%
rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/TanHelperTest.kt
rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/TanTest.kt
index 7a70f8c5493057354b1953815cd5869051bb252d..4a011b972587c5bc14a74e2b0bf002a1e70259ce 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/TanHelperTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/TanTest.kt
@@ -1,10 +1,10 @@
-package de.rki.coronawarnapp.util
+package de.rki.coronawarnapp.ui.submission.tan
 
-import org.hamcrest.CoreMatchers
-import org.hamcrest.MatcherAssert
-import org.junit.Test
+import io.kotest.matchers.shouldBe
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
 
-class TanHelperTest {
+class TanTest : BaseTest() {
 
     @Test
     fun isValidCharacter() {
@@ -14,10 +14,7 @@ class TanHelperTest {
             "J", "K", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
         )
         for (character in validCharacters) {
-            MatcherAssert.assertThat(
-                TanHelper.isTanCharacterValid(character),
-                CoreMatchers.equalTo(true)
-            )
+            Tan.isTanCharacterValid(character) shouldBe true
         }
 
         // invalid
@@ -26,10 +23,7 @@ class TanHelperTest {
             "c", "ö", "ß", "é", ".", " ", "€", "(", ")", ";", ","
         )
         for (character in invalidCharacters) {
-            MatcherAssert.assertThat(
-                TanHelper.isTanCharacterValid(character),
-                CoreMatchers.equalTo(false)
-            )
+            Tan.isTanCharacterValid(character) shouldBe false
         }
     }
 
@@ -40,10 +34,7 @@ class TanHelperTest {
             "ABCD", "2345", "PTPHM35RP4", "AAAAAAAAAA", "BBBBB"
         )
         for (text in validStrings) {
-            MatcherAssert.assertThat(
-                TanHelper.allCharactersValid(text),
-                CoreMatchers.equalTo(true)
-            )
+            Tan.allCharactersValid(text) shouldBe true
         }
 
         // invalid input strings
@@ -51,10 +42,7 @@ class TanHelperTest {
             "ABCDÖ", "01234", "PTPHM15RP4", "AAAAAA AAA", "BB.BBB"
         )
         for (text in invalidStrings) {
-            MatcherAssert.assertThat(
-                TanHelper.allCharactersValid(text),
-                CoreMatchers.equalTo(false)
-            )
+            Tan.allCharactersValid(text) shouldBe false
         }
     }
 
@@ -65,10 +53,7 @@ class TanHelperTest {
             "9A3B578UMG", "DEU7TKSV3H", "PTPHM35RP4", "V923D59AT8", "H9NC5CQ34E"
         )
         for (tan in validTans) {
-            MatcherAssert.assertThat(
-                TanHelper.isChecksumValid(tan),
-                CoreMatchers.equalTo(true)
-            )
+            Tan.isChecksumValid(tan) shouldBe true
         }
 
         // invalid
@@ -81,10 +66,7 @@ class TanHelperTest {
             "9A3B578UM0", "DEU7TKSV31", "Q4XBJCB43", "929B96CA8"
         )
         for (tan in invalidTans) {
-            MatcherAssert.assertThat(
-                TanHelper.isChecksumValid(tan),
-                CoreMatchers.equalTo(false)
-            )
+            Tan.isChecksumValid(tan) shouldBe false
         }
     }
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/tracing/details/TracingDetailsStateTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/tracing/details/TracingDetailsStateTest.kt
index 9a22c516dbff73ac424f0d72bb83f95d219ab459..93154d75926eaf91c65b80950faeac0735a74072 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/tracing/details/TracingDetailsStateTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/tracing/details/TracingDetailsStateTest.kt
@@ -249,61 +249,61 @@ class TracingDetailsStateTest : BaseTest() {
     fun `risk details buttons visibility`() {
         createInstance(
             riskLevelScore = RiskLevelConstants.INCREASED_RISK,
-            isBackgroundJobEnabled = true,
+            isBackgroundJobEnabled = true
         ).apply {
             areRiskDetailsButtonsVisible() shouldBe false
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS,
-            isBackgroundJobEnabled = true,
+            isBackgroundJobEnabled = true
         ).apply {
             areRiskDetailsButtonsVisible() shouldBe true
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF,
-            isBackgroundJobEnabled = true,
+            isBackgroundJobEnabled = true
         ).apply {
             areRiskDetailsButtonsVisible() shouldBe true
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.LOW_LEVEL_RISK,
-            isBackgroundJobEnabled = true,
+            isBackgroundJobEnabled = true
         ).apply {
             areRiskDetailsButtonsVisible() shouldBe false
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.UNKNOWN_RISK_INITIAL,
-            isBackgroundJobEnabled = true,
+            isBackgroundJobEnabled = true
         ).apply {
             areRiskDetailsButtonsVisible() shouldBe false
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.INCREASED_RISK,
-            isBackgroundJobEnabled = false,
+            isBackgroundJobEnabled = false
         ).apply {
             areRiskDetailsButtonsVisible() shouldBe true
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS,
-            isBackgroundJobEnabled = false,
+            isBackgroundJobEnabled = false
         ).apply {
             areRiskDetailsButtonsVisible() shouldBe true
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF,
-            isBackgroundJobEnabled = false,
+            isBackgroundJobEnabled = false
         ).apply {
             areRiskDetailsButtonsVisible() shouldBe true
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.LOW_LEVEL_RISK,
-            isBackgroundJobEnabled = false,
+            isBackgroundJobEnabled = false
         ).apply {
             areRiskDetailsButtonsVisible() shouldBe true
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.UNKNOWN_RISK_INITIAL,
-            isBackgroundJobEnabled = false,
+            isBackgroundJobEnabled = false
         ).apply {
             areRiskDetailsButtonsVisible() shouldBe true
         }
@@ -338,62 +338,62 @@ class TracingDetailsStateTest : BaseTest() {
     fun `risk details update button visibility`() {
         createInstance(
             riskLevelScore = RiskLevelConstants.INCREASED_RISK,
-            isBackgroundJobEnabled = true,
+            isBackgroundJobEnabled = true
         ).apply {
             isRiskDetailsUpdateButtonVisible() shouldBe false
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS,
-            isBackgroundJobEnabled = true,
+            isBackgroundJobEnabled = true
         ).apply {
             isRiskDetailsUpdateButtonVisible() shouldBe false
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF,
-            isBackgroundJobEnabled = true,
+            isBackgroundJobEnabled = true
         ).apply {
             isRiskDetailsUpdateButtonVisible() shouldBe false
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.LOW_LEVEL_RISK,
-            isBackgroundJobEnabled = true,
+            isBackgroundJobEnabled = true
         ).apply {
             isRiskDetailsUpdateButtonVisible() shouldBe false
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.UNKNOWN_RISK_INITIAL,
-            isBackgroundJobEnabled = true,
+            isBackgroundJobEnabled = true
         ).apply {
             isRiskDetailsUpdateButtonVisible() shouldBe false
         }
 
         createInstance(
             riskLevelScore = RiskLevelConstants.INCREASED_RISK,
-            isBackgroundJobEnabled = false,
+            isBackgroundJobEnabled = false
         ).apply {
             isRiskDetailsUpdateButtonVisible() shouldBe true
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS,
-            isBackgroundJobEnabled = false,
+            isBackgroundJobEnabled = false
         ).apply {
             isRiskDetailsUpdateButtonVisible() shouldBe false
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF,
-            isBackgroundJobEnabled = false,
+            isBackgroundJobEnabled = false
         ).apply {
             isRiskDetailsUpdateButtonVisible() shouldBe false
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.LOW_LEVEL_RISK,
-            isBackgroundJobEnabled = false,
+            isBackgroundJobEnabled = false
         ).apply {
             isRiskDetailsUpdateButtonVisible() shouldBe true
         }
         createInstance(
             riskLevelScore = RiskLevelConstants.UNKNOWN_RISK_INITIAL,
-            isBackgroundJobEnabled = false,
+            isBackgroundJobEnabled = false
         ).apply {
             isRiskDetailsUpdateButtonVisible() shouldBe true
         }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/flow/HotDataFlowTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/flow/HotDataFlowTest.kt
index 0a5702aa451a11d0064f51a1d9f97fa5e54ab376..e5bbbc32bf1b36461320d765152b9771a9a65607 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/flow/HotDataFlowTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/flow/HotDataFlowTest.kt
@@ -141,7 +141,6 @@ class HotDataFlowTest : BaseTest() {
         hotData.updateSafely { "2" }
         hotData.updateSafely { "1" }
 
-
         runBlocking {
             testCollector.await { list, l -> list.size == 3 }
             testCollector.latestValues shouldBe listOf("1", "2", "1")
@@ -161,7 +160,6 @@ class HotDataFlowTest : BaseTest() {
             sharingBehavior = SharingStarted.Lazily
         )
 
-
         testScope.runBlockingTest2(permanentJobs = true) {
             val sub1 = hotData.data.test().start(scope = this)
             val sub2 = hotData.data.test().start(scope = this)
diff --git a/Corona-Warn-App/src/test/java/testhelpers/coroutines/TestExtensions.kt b/Corona-Warn-App/src/test/java/testhelpers/coroutines/TestExtensions.kt
index bcf45781d2a11ea590622c2f157acee224bd5e02..0fc5027bee9cc61a1545b2cab0ad52bdde4b1488 100644
--- a/Corona-Warn-App/src/test/java/testhelpers/coroutines/TestExtensions.kt
+++ b/Corona-Warn-App/src/test/java/testhelpers/coroutines/TestExtensions.kt
@@ -40,5 +40,3 @@ fun runBlockingTest2(
         }
     }
 }
-
-