From bc38a847a29c632fa6f65af63d84d0e0e96e5701 Mon Sep 17 00:00:00 2001 From: Matthias Urhahn <matthias.urhahn@sap.com> Date: Fri, 30 Oct 2020 14:48:58 +0100 Subject: [PATCH] Introduce skeleton classes for UI tests and screenshots (EXPOSUREAPP-2997) (#1496) * Initial refactoring on the Submission flow. Trimming down SubmissionViewModel.kt * Refactored submission tan, and began instrumentation tests. Intro, Dispatcher and Tan complete * Adapted instrumentation tests after merge - added ToDos * Completed boilerplate submission instrumentation tests. Verification and screenshots to follow * Fix lints * More lint issues. * Fix instrumentation test dependency missmatch. * Move screenshot related permissions into a debug AndroidManifest.xml, such that they don't get included in production builds. * Added missing new line to manifest Co-authored-by: Oliver Zimmerman <oezimmerman@gmail.com> Co-authored-by: Kolya Opahle <k.opahle@sap.com> Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> --- Corona-Warn-App/build.gradle | 8 + .../ui/main/home/HomeFragmentTest.kt | 4 +- .../SubmissionContactFragmentTest.kt | 66 ++++++ .../SubmissionDispatcherFragmentTest.kt | 78 +++++++ .../submission/SubmissionDoneFragmentTest.kt | 58 +++++ .../submission/SubmissionIntroFragmentTest.kt | 69 ++++++ .../SubmissionOtherWarningFragmentTest.kt | 83 ++++++++ .../SubmissionQrCodeInfoFragmentTest.kt | 58 +++++ .../SubmissionQrCodeScanFragmentTest.kt | 45 ++++ .../SubmissionSymptomCalendarFragmentTest.kt | 68 ++++++ .../SubmissionSymptomIntroFragmentTest.kt | 60 ++++++ .../submission/SubmissionTanFragmentTest.kt | 63 ++++++ .../SubmissionTestResultFragmentTest.kt | 117 ++++++++++ .../FragmentTestModuleRegistrar.kt | 26 ++- Corona-Warn-App/src/debug/AndroidManifest.xml | 15 ++ .../storage/SubmissionRepository.kt | 2 - .../InteroperabilityRepository.kt | 18 +- ...sSinceOnsetOfSymptomsVectorDeterminator.kt | 7 +- .../rki/coronawarnapp/submission/Symptoms.kt | 26 ++- .../ui/main/MainActivityModule.kt | 2 +- .../ui/submission/TanConstants.kt | 6 - .../SubmissionSymptomCalendarFragment.kt | 161 -------------- .../info}/SubmissionQRCodeInfoFragment.kt | 12 +- .../SubmissionQRCodeInfoFragmentViewModel.kt | 2 +- .../info}/SubmissionQRCodeInfoModule.kt | 3 +- .../scan}/SubmissionQRCodeScanFragment.kt | 105 +++++---- .../scan}/SubmissionQRCodeScanModule.kt | 2 +- .../scan/SubmissionQRCodeScanViewModel.kt | 71 +++++++ .../SubmissionSymptomCalendarFragment.kt | 148 +++++++++++++ .../SubmissionSymptomCalendarModule.kt | 2 +- .../SubmissionSymptomCalendarViewModel.kt | 68 ++++++ .../SubmissionSymptomIntroductionFragment.kt | 118 ++++------- .../SubmissionSymptomIntroductionModule.kt | 2 +- .../SubmissionSymptomIntroductionViewModel.kt | 61 ++++++ .../SubmissionTanFragment.kt | 129 +++++------ .../{viewmodel => tan}/SubmissionTanModule.kt | 2 +- .../submission/tan/SubmissionTanViewModel.kt | 81 +++++++ .../coronawarnapp/ui/submission/tan/Tan.kt | 46 ++++ .../ui/{view => submission/tan}/TanInput.kt | 31 ++- .../SubmissionTestResultFragment.kt | 93 +++----- .../SubmissionTestResultModule.kt | 2 +- .../SubmissionTestResultViewModel.kt | 101 +++++++++ .../testresult/TestResultUIState.kt | 11 + .../SubmissionFragmentModule.kt | 34 ++- .../viewmodel/SubmissionNavigationEvents.kt | 13 +- .../SubmissionQRCodeScanViewModel.kt | 22 -- ...sionResultPositiveOtherWarningViewModel.kt | 26 --- .../SubmissionSymptomCalendarViewModel.kt | 22 -- .../SubmissionSymptomIntroductionViewModel.kt | 22 -- .../viewmodel/SubmissionTanViewModel.kt | 37 ---- .../SubmissionTestResultViewModel.kt | 30 --- ...ssionResultPositiveOtherWarningFragment.kt | 200 ++++++++---------- ...missionResultPositiveOtherWarningModule.kt | 2 +- ...sionResultPositiveOtherWarningViewModel.kt | 110 ++++++++++ .../submission/warnothers/WarnOthersState.kt | 17 ++ .../ui/viewmodel/SubmissionViewModel.kt | 167 +-------------- .../de/rki/coronawarnapp/util/TanHelper.kt | 35 --- .../formatter/FormatterSubmissionHelper.kt | 6 - ...ment_submission_positive_other_warning.xml | 16 +- .../fragment_submission_symptom_calendar.xml | 2 +- .../res/layout/fragment_submission_tan.xml | 13 +- .../fragment_submission_test_result.xml | 23 +- .../res/layout/include_submission_tan.xml | 14 +- .../layout/include_submission_test_result.xml | 18 +- .../src/main/res/navigation/nav_graph.xml | 22 +- .../appconfig/AppConfigApiTest.kt | 4 +- .../rki/coronawarnapp/nearby/ENFClientTest.kt | 7 +- .../DefaultCalculationTrackerTest.kt | 4 +- .../task/testtasks/SkippingTask.kt | 2 +- .../transaction/RiskLevelTransactionTest.kt | 1 - .../submission/SubmissionTanViewModelTest.kt | 48 ----- ...bmissionQRCodeInfoFragmentViewModelTest.kt | 2 +- .../SubmissionQRCodeScanViewModelTest.kt} | 7 +- .../tan/SubmissionTanViewModelTest.kt | 58 +++++ .../submission/tan/TanTest.kt} | 40 +--- .../details/TracingDetailsStateTest.kt | 40 ++-- .../util/flow/HotDataFlowTest.kt | 2 - .../testhelpers/coroutines/TestExtensions.kt | 2 - 78 files changed, 2030 insertions(+), 1168 deletions(-) create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDoneFragmentTest.kt create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionIntroFragmentTest.kt create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionOtherWarningFragmentTest.kt create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeInfoFragmentTest.kt create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeScanFragmentTest.kt create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragmentTest.kt create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt create mode 100644 Corona-Warn-App/src/debug/AndroidManifest.xml delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/TanConstants.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionSymptomCalendarFragment.kt rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{fragment => qrcode/info}/SubmissionQRCodeInfoFragment.kt (78%) rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{viewmodel => qrcode/info}/SubmissionQRCodeInfoFragmentViewModel.kt (92%) rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{fragment => qrcode/info}/SubmissionQRCodeInfoModule.kt (79%) rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{fragment => qrcode/scan}/SubmissionQRCodeScanFragment.kt (89%) rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{viewmodel => qrcode/scan}/SubmissionQRCodeScanModule.kt (90%) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{viewmodel => symptoms/calendar}/SubmissionSymptomCalendarModule.kt (90%) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{fragment => symptoms/introduction}/SubmissionSymptomIntroductionFragment.kt (60%) rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{viewmodel => symptoms/introduction}/SubmissionSymptomIntroductionModule.kt (89%) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{fragment => tan}/SubmissionTanFragment.kt (69%) rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{viewmodel => tan}/SubmissionTanModule.kt (90%) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/Tan.kt rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/{view => submission/tan}/TanInput.kt (90%) rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{fragment => testresult}/SubmissionTestResultFragment.kt (70%) rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{viewmodel => testresult}/SubmissionTestResultModule.kt (90%) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/TestResultUIState.kt rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{ => viewmodel}/SubmissionFragmentModule.kt (61%) delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionQRCodeScanViewModel.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionResultPositiveOtherWarningViewModel.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomCalendarViewModel.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionSymptomIntroductionViewModel.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTanViewModel.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionTestResultViewModel.kt rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{fragment => warnothers}/SubmissionResultPositiveOtherWarningFragment.kt (62%) rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{viewmodel => warnothers}/SubmissionResultPositiveOtherWarningModule.kt (91%) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningViewModel.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/WarnOthersState.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TanHelper.kt delete mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/SubmissionTanViewModelTest.kt rename Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/{viewmodel => qrcode/info}/SubmissionQRCodeInfoFragmentViewModelTest.kt (93%) rename Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/{viewmodel/SubmissionViewModelTest.kt => submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt} (88%) create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt rename Corona-Warn-App/src/test/java/de/rki/coronawarnapp/{util/TanHelperTest.kt => ui/submission/tan/TanTest.kt} (63%) diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index 5ed563edf..b27fde297 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 289a23b9b..d109adabf 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 000000000..5e262d08e --- /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 000000000..fc417a990 --- /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 000000000..989bbcdcb --- /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 000000000..5f3cd0566 --- /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 000000000..eb4a3dba7 --- /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 000000000..5e2b2ceaf --- /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 000000000..245374a2a --- /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 000000000..b592443c8 --- /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 000000000..ed959a210 --- /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 000000000..b2dc3033d --- /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 000000000..ba62622e5 --- /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 da725b563..e50180f6c 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 000000000..ba24510e2 --- /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 eff4ebae8..ed9100cda 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 81327c8e1..b29b24698 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 e7a6c4179..037dfc690 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 13e80e20a..769df6264 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 af1a59dfd..647f333c8 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 644cf1ee0..000000000 --- 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 1c3184ab8..000000000 --- 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 1f37ca3e0..c98d506a0 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 cde1a6246..f90824a82 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 fe135026e..437fc64b2 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 c09f7a54b..1ecb64ba8 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 55c737607..7a435572f 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 000000000..b4330c73b --- /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 000000000..2c1653eed --- /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 d359621f4..82edb4598 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 000000000..e79975890 --- /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 a7895306f..c2af32534 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 537923005..40b49ba8e 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 000000000..43f7550b4 --- /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 f021bdc4d..a9c953118 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 5a05697d7..a33e0f4ca 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 000000000..b2fcfe37d --- /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 000000000..a4d883605 --- /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 91a3e227e..04c1e7a8f 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 c0da337c7..0655e3e58 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 a10ad1086..a81b3bf8e 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 000000000..96766c269 --- /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 000000000..b1909eb80 --- /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 0378f1666..07cdfe2f9 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 347cb1c18..5a0fe235f 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 ae575ce54..000000000 --- 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 a2b242cea..000000000 --- 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 38bd530b3..000000000 --- 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 a79f4a1b3..000000000 --- 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 2a33f7239..000000000 --- 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 5c96c86ec..000000000 --- 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 1d96f4804..65ad86643 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 41a0322ce..3ff8fc4cf 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 000000000..e4e381a7e --- /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 000000000..33809f6b7 --- /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 c1079bbdd..6b668e6c4 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 5327b0f00..000000000 --- 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 df3c1283f..156f8efee 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 555709a0a..fcf4c4a83 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 && 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 eb19ee84f..a34166dd5 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 3ef4b9f79..e499b7cef 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 2f7825389..3521faa0f 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 795e24144..5d31152d6 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 b6b15d563..a564e5c96 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 6d734cbc2..44aea7e8c 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 2e2d2cb5a..671fd3ab1 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 c9ecb45bc..541eedf62 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 5ae6cad6a..067680cac 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 3c832ad29..f04714e34 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 ea8c5d219..5a6d94205 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 8f49bcb3f..000000000 --- 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 44023d6cd..d42ae7c1d 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 cbc54ad5d..b518ae31a 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 000000000..fe2bd9043 --- /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 7a70f8c54..4a011b972 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 9a22c516d..93154d759 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 0a5702aa4..e5bbbc32b 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 bcf45781d..0fc5027be 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( } } } - - -- GitLab