From e5da9ee875050dee2fee61dd0a95142e418902c1 Mon Sep 17 00:00:00 2001 From: Mohamed Metwalli <mohamed.metwalli@sap.com> Date: Wed, 2 Jun 2021 15:18:46 +0200 Subject: [PATCH] Request DCC Screen (EXPOSUREAPP-7549, 7553) (#3343) * Add dgc * Parse value * Refactoring * Prase Dgc * Refactoring * Register in DCC * Open privacy * Analytics * Lint * Fix test * Wire Screen * Refactoring * Connect Deletion warning path * Fix assisted inject * Create RequestCovidCertificateViewModelTest.kt * Update RequestCovidCertificateViewModelTest.kt --- .../GreenCertificateTestFragment.kt | 10 - .../fragment_test_green_certificate.xml | 12 - .../coronatest/TestRegistrationRequest.kt | 2 +- .../coronatest/qrcode/CoronaTestQRCode.kt | 10 +- .../qrcode/RapidAntigenQrCodeExtractor.kt | 19 +- .../coronatest/tan/CoronaTestTAN.kt | 2 +- .../coronatest/type/CoronaTest.kt | 2 + .../coronatest/type/pcr/PCRCoronaTest.kt | 3 + .../type/rapidantigen/RACoronaTest.kt | 3 + .../type/rapidantigen/RAProcessor.kt | 2 +- .../SubmissionDeletionWarningViewModel.kt | 63 ++-- .../RequestCovidCertificateFragment.kt | 189 +++++++++++ ... RequestCovidCertificateFragmentModule.kt} | 6 +- .../RequestCovidCertificateViewModel.kt | 97 ++++++ .../greencertificate/RequestDccNavEvent.kt | 6 + .../RequestGreenCertificateFragment.kt | 74 ----- .../RequestGreenCertificateViewModel.kt | 31 -- .../QrCodeRegistrationStateProcessor.kt | 9 +- .../scan/SubmissionQRCodeScanFragment.kt | 7 +- .../scan/SubmissionQRCodeScanViewModel.kt | 44 ++- .../viewmodel/SubmissionFragmentModule.kt | 8 +- .../viewmodel/SubmissionNavigationEvents.kt | 6 +- ...=> fragment_request_covid_certificate.xml} | 15 +- .../src/main/res/navigation/nav_graph.xml | 41 ++- .../coronatest/qrcode/CoronaTestQRCodeTest.kt | 6 +- .../coronatest/tan/CoronaTestTANTest.kt | 2 +- .../RequestCovidCertificateViewModelTest.kt | 304 ++++++++++++++++++ .../scan/SubmissionQRCodeScanViewModelTest.kt | 96 ++++-- 28 files changed, 816 insertions(+), 253 deletions(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragment.kt rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/{RequestGreenCertificateFragmentModule.kt => RequestCovidCertificateFragmentModule.kt} (71%) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModel.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestDccNavEvent.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragment.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateViewModel.kt rename Corona-Warn-App/src/main/res/layout/{fragment_request_green_certificate.xml => fragment_request_covid_certificate.xml} (93%) create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModelTest.kt diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/greencertificate/GreenCertificateTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/greencertificate/GreenCertificateTestFragment.kt index 5c702e1e9..c7d115bc5 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/greencertificate/GreenCertificateTestFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/greencertificate/GreenCertificateTestFragment.kt @@ -4,10 +4,7 @@ import android.annotation.SuppressLint import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment -import de.rki.coronawarnapp.NavGraphDirections import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.PCR -import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.RAPID_ANTIGEN import de.rki.coronawarnapp.databinding.FragmentTestGreenCertificateBinding import de.rki.coronawarnapp.test.menu.ui.TestMenuItem import de.rki.coronawarnapp.util.di.AutoInject @@ -28,13 +25,6 @@ class GreenCertificateTestFragment : Fragment(R.layout.fragment_test_green_certi super.onViewCreated(view, savedInstanceState) binding.apply { - pcrScreen.setOnClickListener { - doNavigate(NavGraphDirections.actionSubmissionTestResultGreenCertificateFragment(PCR)) - } - ratScreen.setOnClickListener { - doNavigate(NavGraphDirections.actionSubmissionTestResultGreenCertificateFragment(RAPID_ANTIGEN)) - } - detailsScreen.setOnClickListener { doNavigate( GreenCertificateTestFragmentDirections diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_green_certificate.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_green_certificate.xml index 4f5a4eee7..b35ae07c4 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_green_certificate.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_green_certificate.xml @@ -17,18 +17,6 @@ android:layout_height="wrap_content" android:text="Request DCC screen" /> - <com.google.android.material.button.MaterialButton - android:id="@+id/pcr_screen" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="CPR screen" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/rat_screen" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="RAT screen" /> - <com.google.android.material.button.MaterialButton android:id="@+id/details_screen" android:layout_width="match_parent" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestRegistrationRequest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestRegistrationRequest.kt index 766ca2e57..99ab06763 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestRegistrationRequest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestRegistrationRequest.kt @@ -6,7 +6,7 @@ import org.joda.time.LocalDate interface TestRegistrationRequest { val type: CoronaTest.Type val identifier: String - val isDccSupportedbyPoc: Boolean + val isDccSupportedByPoc: Boolean val isDccConsentGiven: Boolean val dateOfBirth: LocalDate? } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt index 2e9417b64..e7660b6d5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt @@ -21,7 +21,7 @@ sealed class CoronaTestQRCode : Parcelable, TestRegistrationRequest { ) : CoronaTestQRCode() { @IgnoredOnParcel - override val isDccSupportedbyPoc: Boolean = true + override val isDccSupportedByPoc: Boolean = true @IgnoredOnParcel override val type: CoronaTest.Type = CoronaTest.Type.PCR @@ -37,15 +37,15 @@ sealed class CoronaTestQRCode : Parcelable, TestRegistrationRequest { @Parcelize data class RapidAntigen( + override val dateOfBirth: LocalDate? = null, + override val isDccConsentGiven: Boolean = false, + override val isDccSupportedByPoc: Boolean = false, val hash: RapidAntigenHash, val createdAt: Instant, val firstName: String? = null, val lastName: String? = null, - override val dateOfBirth: LocalDate? = null, - val testid: String? = null, + val testId: String? = null, val salt: String? = null, - override val isDccConsentGiven: Boolean = false, - override val isDccSupportedbyPoc: Boolean = false, ) : CoronaTestQRCode() { @IgnoredOnParcel diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt index f0bffc613..703886f8e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt @@ -39,9 +39,9 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona firstName = payload.firstName, lastName = payload.lastName, dateOfBirth = payload.dateOfBirth, - testid = payload.testId, + testId = payload.testId, salt = payload.salt, - isDccSupportedbyPoc = false, // TODO + isDccSupportedByPoc = payload.isDccSupportedByPoc ) } @@ -83,7 +83,8 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona @SerializedName("ln") val lastName: String?, @SerializedName("dob") val dateOfBirth: String?, @SerializedName("testid") val testid: String?, - @SerializedName("salt") val salt: String? + @SerializedName("salt") val salt: String?, + @SerializedName("dgc") val dgc: Boolean? ) private data class CleanPayload(val raw: RawPayload) { @@ -127,6 +128,8 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona if (raw.salt.isNullOrEmpty()) null else raw.salt } + val isDccSupportedByPoc: Boolean by lazy { raw.dgc == true } + fun requireValidData() { requireValidPersonalData() requireValidHash() @@ -144,9 +147,15 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona private fun requireValidHash() { val isQrCodeWithPersonalData = firstName != null && lastName != null && dateOfBirth != null - val generatedHash = + val rawBuilder = StringBuilder( "${raw.dateOfBirth}#${raw.firstName}#${raw.lastName}#${raw.timestamp}#${raw.testid}#${raw.salt}" - .toSHA256() + ) + if (raw.dgc != null) { + val asInt = if (raw.dgc == true) 1 else 0 + rawBuilder.append("#$asInt") + } + + val generatedHash = rawBuilder.toString().toSHA256() if (isQrCodeWithPersonalData && !generatedHash.equals(hash, true)) { throw InvalidQRCodeException("Generated hash doesn't match QRCode hash") } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTAN.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTAN.kt index 348576e79..46b9e79ac 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTAN.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTAN.kt @@ -25,7 +25,7 @@ sealed class CoronaTestTAN : Parcelable, TestRegistrationRequest { override val type: CoronaTest.Type = CoronaTest.Type.PCR @IgnoredOnParcel - override val isDccSupportedbyPoc: Boolean = false + override val isDccSupportedByPoc: Boolean = false @IgnoredOnParcel override val isDccConsentGiven: Boolean = false diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt index 464bb89b5..f516a6e5e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt @@ -28,6 +28,8 @@ interface CoronaTest { */ val isFinal: Boolean + val isRedeemed: Boolean + val testResultReceivedAt: Instant? val testResult: CoronaTestResult diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt index fc85fb4eb..fd28cb447 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt @@ -54,6 +54,9 @@ data class PCRCoronaTest( override val isFinal: Boolean get() = testResult == CoronaTestResult.PCR_REDEEMED + override val isRedeemed: Boolean + get() = testResult == CoronaTestResult.PCR_REDEEMED + override val isPositive: Boolean get() = testResult == CoronaTestResult.PCR_POSITIVE diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt index 13e0c4200..6dfa9ad11 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt @@ -99,6 +99,9 @@ data class RACoronaTest( override val isFinal: Boolean get() = testResult == RAT_REDEEMED + override val isRedeemed: Boolean + get() = testResult == RAT_REDEEMED + override val isPositive: Boolean get() = testResult == RAT_POSITIVE diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt index 463d38167..c2b3911c6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt @@ -101,7 +101,7 @@ class RAProcessor @Inject constructor( lastName = request.lastName, dateOfBirth = request.dateOfBirth, sampleCollectedAt = sampleCollectedAt, - isDccSupportedByPoc = request.isDccSupportedbyPoc, + isDccSupportedByPoc = request.isDccSupportedByPoc, ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt index 4315b5004..5db57987c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt @@ -49,36 +49,45 @@ class SubmissionDeletionWarningViewModel @AssistedInject constructor( private suspend fun deleteExistingAndRegisterNewTestWithQrCode() = try { requireNotNull(coronaTestQrCode) { "QR Code was unavailable" } + if (coronaTestQrCode.isDccSupportedByPoc) { + SubmissionDeletionWarningFragmentDirections + .actionSubmissionDeletionWarningFragmentToRequestCovidCertificateFragment( + coronaTestQrCode = coronaTestQrCode, + coronaTestConsent = isConsentGiven, + deleteOldTest = true + ).run { routeToScreen.postValue(this) } + } else { + removeAndRegisterNew(coronaTestQrCode) + } + } catch (e: Exception) { + Timber.e(e, "Error during test registration via QR code") + mutableRegistrationState.postValue(RegistrationState(isFetching = false)) + registrationError.postValue(e) + } + private suspend fun removeAndRegisterNew( + coronaTestQrCode: CoronaTestQRCode + ) { // Remove existing test and wait until that is done submissionRepository.testForType(coronaTestQrCode.type).first()?.let { coronaTestRepository.removeTest(it.identifier) } ?: Timber.w("Test we will replace with QR was already removed?") mutableRegistrationState.postValue(RegistrationState(isFetching = true)) - val coronaTest = submissionRepository.registerTest(coronaTestQrCode) - if (coronaTest.isFinal) { - Timber.d("New test was already final, removing it again: %s", coronaTest) + if (coronaTest.isRedeemed) { + Timber.d("New test was already redeemed, removing it again: %s", coronaTest) // This does not wait until the test is removed, // the exception handling should navigate the user to a new screen anyways submissionRepository.removeTestFromDevice(type = coronaTest.type) - - throw InvalidQRCodeException() + throw InvalidQRCodeException("Test is already redeemed") } - if (isConsentGiven) { - submissionRepository.giveConsentToSubmission(type = coronaTestQrCode.type) - } + if (isConsentGiven) submissionRepository.giveConsentToSubmission(type = coronaTestQrCode.type) continueWithNewTest(coronaTest) - mutableRegistrationState.postValue(RegistrationState(coronaTest = coronaTest)) - } catch (e: Exception) { - Timber.e(e, "Error during test registration via QR code") - mutableRegistrationState.postValue(RegistrationState(isFetching = false)) - registrationError.postValue(e) } private suspend fun deleteExistingAndRegisterNewTestWitTAN() = try { @@ -108,28 +117,20 @@ class SubmissionDeletionWarningViewModel @AssistedInject constructor( private fun continueWithNewTest(coronaTest: CoronaTest) { Timber.d("Continuing with our new CoronaTest: %s", coronaTest) + val testType = coronaTestQrCode!!.type when (getRegistrationType()) { - RegistrationType.QR -> { - if (coronaTest.isPositive) { - SubmissionDeletionWarningFragmentDirections - .actionSubmissionDeletionWarningFragmentToSubmissionTestResultAvailableFragment( - testType = coronaTestQrCode!!.type - ) - .run { routeToScreen.postValue(this) } - } else { - SubmissionDeletionWarningFragmentDirections - .actionSubmissionDeletionWarningFragmentToSubmissionTestResultPendingFragment( - testType = coronaTestQrCode!!.type - ) - .run { routeToScreen.postValue(this) } - } + RegistrationType.QR -> if (coronaTest.isPositive) { + SubmissionDeletionWarningFragmentDirections + .actionSubmissionDeletionWarningFragmentToSubmissionTestResultAvailableFragment(testType) + } else { + SubmissionDeletionWarningFragmentDirections + .actionSubmissionDeletionWarningFragmentToSubmissionTestResultPendingFragment(testType) } - RegistrationType.TAN -> { + + RegistrationType.TAN -> SubmissionDeletionWarningFragmentDirections .actionSubmissionDeletionFragmentToSubmissionTestResultNoConsentFragment(getTestType()) - .run { routeToScreen.postValue(this) } - } - } + }.run { routeToScreen.postValue(this) } } data class RegistrationState( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragment.kt new file mode 100644 index 000000000..6ada9a974 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragment.kt @@ -0,0 +1,189 @@ +package de.rki.coronawarnapp.ui.submission.greencertificate + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.View +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.core.widget.doOnTextChanged +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import com.google.android.material.datepicker.MaterialDatePicker +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import de.rki.coronawarnapp.NavGraphDirections +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.bugreporting.ui.toErrorDialogBuilder +import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.PCR +import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.RAPID_ANTIGEN +import de.rki.coronawarnapp.databinding.FragmentRequestCovidCertificateBinding +import de.rki.coronawarnapp.exception.http.BadRequestException +import de.rki.coronawarnapp.exception.http.CwaClientError +import de.rki.coronawarnapp.exception.http.CwaServerError +import de.rki.coronawarnapp.exception.http.CwaWebException +import de.rki.coronawarnapp.ui.submission.ApiRequestState +import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor +import de.rki.coronawarnapp.util.DialogHelper +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.doNavigate +import de.rki.coronawarnapp.util.ui.popBackStack +import de.rki.coronawarnapp.util.ui.viewBinding +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider +import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted +import org.joda.time.LocalDate +import timber.log.Timber +import javax.inject.Inject + +class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid_certificate), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val viewModel by cwaViewModelsAssisted<RequestCovidCertificateViewModel>( + factoryProducer = { viewModelFactory }, + constructorCall = { factory, _ -> + factory as RequestCovidCertificateViewModel.Factory + factory.create(args.coronaTestQrCode, args.coronaTestConsent, args.deleteOldTest) + } + ) + private val binding by viewBinding<FragmentRequestCovidCertificateBinding>() + private val args by navArgs<RequestCovidCertificateFragmentArgs>() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) = + with(binding) { + val isPCR = args.coronaTestQrCode is CoronaTestQRCode.PCR + birthDateGroup.isVisible = isPCR + privacyCard.pcrExtraBullet.isVisible = isPCR + + dateInputEdit.doOnTextChanged { text, _, _, _ -> + if (text.toString().isEmpty()) viewModel.birthDateChanged(null) + } + + toolbar.setNavigationOnClickListener { showCloseDialog() } + agreeButton.setOnClickListener { viewModel.onAgreeGC() } + disagreeButton.setOnClickListener { viewModel.onDisagreeGC() } + dateInputEdit.setOnClickListener { openDatePicker() } + privacyInformation.setOnClickListener { findNavController().navigate(R.id.informationPrivacyFragment) } + + viewModel.events.observe(viewLifecycleOwner) { event -> + when (event) { + Back -> popBackStack() + ToDispatcherScreen -> doNavigate( + RequestCovidCertificateFragmentDirections + .actionRequestCovidCertificateFragmentToDispatcherFragment() + ) + ToHomeScreen -> doNavigate( + RequestCovidCertificateFragmentDirections.actionRequestCovidCertificateFragmentToHomeFragment() + ) + } + } + viewModel.birthDate.observe(viewLifecycleOwner) { date -> agreeButton.isEnabled = !isPCR || date != null } + viewModel.registrationError.observe(viewLifecycleOwner) { DialogHelper.showDialog(buildErrorDialog(it)) } + viewModel.registrationState.observe(viewLifecycleOwner) { state -> handleRegistrationState(state) } + viewModel.showRedeemedTokenWarning.observe(viewLifecycleOwner) { DialogHelper.showDialog(redeemDialog()) } + viewModel.removalError.observe(viewLifecycleOwner) { it.toErrorDialogBuilder(requireContext()).show() } + } + + private fun handleRegistrationState(state: QrCodeRegistrationStateProcessor.RegistrationState) { + when (state.apiRequestState) { + ApiRequestState.STARTED -> binding.apply { + progressBar.show() + agreeButton.isInvisible = true + disagreeButton.isInvisible = true + } + else -> binding.apply { + progressBar.hide() + agreeButton.isInvisible = false + disagreeButton.isInvisible = false + } + } + + when (state.test?.testResult) { + CoronaTestResult.PCR_POSITIVE -> + NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = PCR) + + CoronaTestResult.PCR_OR_RAT_PENDING -> + NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type) + + CoronaTestResult.PCR_NEGATIVE, + CoronaTestResult.PCR_INVALID, + CoronaTestResult.PCR_REDEEMED -> + NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = PCR) + + CoronaTestResult.RAT_POSITIVE -> + NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = RAPID_ANTIGEN) + + CoronaTestResult.RAT_NEGATIVE, + CoronaTestResult.RAT_INVALID, + CoronaTestResult.RAT_PENDING, + CoronaTestResult.RAT_REDEEMED -> + NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = RAPID_ANTIGEN) + null -> { + Timber.w("Successful API request, but test was null?") + return + } + }.run { doNavigate(this) } + } + + private fun redeemDialog(): DialogHelper.DialogInstance = 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 + ) + + private fun showCloseDialog() = MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.request_gc_dialog_title) + .setMessage(R.string.request_gc_dialog_message) + .setNegativeButton(R.string.request_gc_dialog_negative_button) { _, _ -> viewModel.navigateBack() } + .setPositiveButton(R.string.request_gc_dialog_positive_button) { _, _ -> viewModel.navigateToHomeScreen() } + .create() + .show() + + private fun openDatePicker() = MaterialDatePicker.Builder + .datePicker() + .build() + .apply { + addOnPositiveButtonClickListener { timestamp -> + val localDate = LocalDate(timestamp) + binding.dateInputEdit.setText(localDate.toDayFormat()) + viewModel.birthDateChanged(localDate) + } + } + .show(childFragmentManager, "RequestGreenCertificateFragment.MaterialDatePicker") + + private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance = + when (exception) { + is BadRequestException -> createInvalidScanDialog() + is CwaClientError, is CwaServerError -> DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_error_dialog_web_generic_error_title, + R.string.submission_error_dialog_web_generic_network_error_body, + R.string.submission_error_dialog_web_generic_error_button_positive, + null, + true, + { viewModel.navigateToDispatcherScreen() } + ) + 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, + { viewModel.navigateToDispatcherScreen() } + ) + } + + private fun createInvalidScanDialog() = 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, + { viewModel.navigateBack() }, + { viewModel.navigateToDispatcherScreen() }, + { viewModel.navigateToDispatcherScreen() } + ) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragmentModule.kt similarity index 71% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragmentModule.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragmentModule.kt index 216a51af4..74c2e8f20 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragmentModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragmentModule.kt @@ -8,12 +8,12 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey @Module -abstract class RequestGreenCertificateFragmentModule { +abstract class RequestCovidCertificateFragmentModule { @Binds @IntoMap - @CWAViewModelKey(RequestGreenCertificateViewModel::class) + @CWAViewModelKey(RequestCovidCertificateViewModel::class) abstract fun requestGreenCertificateFragment( - factory: RequestGreenCertificateViewModel.Factory + factory: RequestCovidCertificateViewModel.Factory ): CWAViewModelFactory<out CWAViewModel> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModel.kt new file mode 100644 index 000000000..e6603079d --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModel.kt @@ -0,0 +1,97 @@ +package de.rki.coronawarnapp.ui.submission.greencertificate + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.coronatest.CoronaTestRepository +import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode +import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor +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.first +import org.joda.time.LocalDate +import timber.log.Timber + +class RequestCovidCertificateViewModel @AssistedInject constructor( + @Assisted private val coronaTestQrCode: CoronaTestQRCode, + @Assisted("coronaTestConsent") private val coronaTestConsent: Boolean, + @Assisted("deleteOldTest") private val deleteOldTest: Boolean, + private val qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor, + private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector, + private val submissionRepository: SubmissionRepository, + private val coronaTestRepository: CoronaTestRepository, +) : CWAViewModel() { + + // Test registration LiveData + val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning + val registrationState = qrCodeRegistrationStateProcessor.registrationState + val registrationError = qrCodeRegistrationStateProcessor.registrationError + val removalError = SingleLiveEvent<Throwable>() + + private val birthDateData = MutableLiveData<LocalDate>(null) + val birthDate: LiveData<LocalDate> = birthDateData + val events = SingleLiveEvent<RequestDccNavEvent>() + + fun birthDateChanged(localDate: LocalDate?) { + birthDateData.value = localDate + } + + fun onAgreeGC() = registerAndMaybeDelete(dccConsent = true) + + fun onDisagreeGC() = registerAndMaybeDelete(dccConsent = false) + + fun navigateBack() { + events.postValue(Back) + } + + fun navigateToHomeScreen() { + events.postValue(ToHomeScreen) + } + + fun navigateToDispatcherScreen() { + events.postValue(ToDispatcherScreen) + } + + private fun registerAndMaybeDelete(dccConsent: Boolean) = launch { + if (deleteOldTest) removeOldTest() + registerWithDccConsent(dccConsent) + } + + private suspend fun registerWithDccConsent(dccConsent: Boolean) { + val consentedQrCode = when (coronaTestQrCode) { + is CoronaTestQRCode.PCR -> coronaTestQrCode.copy( + dateOfBirth = birthDateData.value, + isDccConsentGiven = dccConsent + ) + is CoronaTestQRCode.RapidAntigen -> coronaTestQrCode.copy(isDccConsentGiven = dccConsent) + } + + qrCodeRegistrationStateProcessor.startQrCodeRegistration(consentedQrCode, coronaTestConsent) + if (coronaTestConsent) analyticsKeySubmissionCollector.reportAdvancedConsentGiven(consentedQrCode.type) + } + + private suspend fun removeOldTest() { + try { + submissionRepository.testForType(coronaTestQrCode.type).first()?.let { + coronaTestRepository.removeTest(it.identifier) + } ?: Timber.e("Test for type ${coronaTestQrCode.type} is not found") + } catch (e: Exception) { + Timber.d(e, "removeOldTest failed") + removalError.postValue(e) + } + } + + @AssistedFactory + interface Factory : CWAViewModelFactory<RequestCovidCertificateViewModel> { + fun create( + coronaTestQrCode: CoronaTestQRCode, + @Assisted("coronaTestConsent") coronaTestConsent: Boolean, + @Assisted("deleteOldTest") deleteOldTest: Boolean + ): RequestCovidCertificateViewModel + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestDccNavEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestDccNavEvent.kt new file mode 100644 index 000000000..1bb251b76 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestDccNavEvent.kt @@ -0,0 +1,6 @@ +package de.rki.coronawarnapp.ui.submission.greencertificate + +sealed class RequestDccNavEvent +object ToDispatcherScreen : RequestDccNavEvent() +object ToHomeScreen : RequestDccNavEvent() +object Back : RequestDccNavEvent() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragment.kt deleted file mode 100644 index 22c6a1a82..000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragment.kt +++ /dev/null @@ -1,74 +0,0 @@ -package de.rki.coronawarnapp.ui.submission.greencertificate - -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.View -import androidx.core.view.isVisible -import androidx.core.widget.doOnTextChanged -import androidx.navigation.fragment.navArgs -import com.google.android.material.datepicker.MaterialDatePicker -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.databinding.FragmentRequestGreenCertificateBinding -import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat -import de.rki.coronawarnapp.util.di.AutoInject -import de.rki.coronawarnapp.util.ui.viewBinding -import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider -import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted -import org.joda.time.LocalDate -import javax.inject.Inject - -class RequestGreenCertificateFragment : Fragment(R.layout.fragment_request_green_certificate), AutoInject { - - @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory - private val viewModel by cwaViewModelsAssisted<RequestGreenCertificateViewModel>( - factoryProducer = { viewModelFactory }, - constructorCall = { factory, _ -> - factory as RequestGreenCertificateViewModel.Factory - factory.create(args.testType) - } - ) - private val binding by viewBinding<FragmentRequestGreenCertificateBinding>() - private val args by navArgs<RequestGreenCertificateFragmentArgs>() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) = - with(binding) { - val isPCR = args.testType == CoronaTest.Type.PCR - birthDateGroup.isVisible = isPCR - privacyCard.pcrExtraBullet.isVisible = isPCR - - dateInputEdit.doOnTextChanged { text, _, _, _ -> - if (text.toString().isEmpty()) viewModel.birthDateChanged(null) - } - - toolbar.setNavigationOnClickListener { showDialog() } - agreeButton.setOnClickListener { viewModel.onAgreeGC() } - disagreeButton.setOnClickListener { viewModel.onDisagreeGC() } - dateInputEdit.setOnClickListener { openDatePicker() } - } - - private fun showDialog() { - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.request_gc_dialog_title) - .setMessage(R.string.request_gc_dialog_message) - .setNegativeButton(R.string.request_gc_dialog_negative_button) { _, _ -> /* TODO */ } - .setPositiveButton(R.string.request_gc_dialog_positive_button) { _, _ -> /* TODO */ } - .create() - .show() - } - - private fun openDatePicker() { - MaterialDatePicker.Builder - .datePicker() - .build() - .apply { - addOnPositiveButtonClickListener { timestamp -> - val localDate = LocalDate(timestamp) - binding.dateInputEdit.setText(localDate.toDayFormat()) - viewModel.birthDateChanged(localDate) - } - } - .show(childFragmentManager, "RequestGreenCertificateFragment.MaterialDatePicker") - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateViewModel.kt deleted file mode 100644 index dcefb79db..000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateViewModel.kt +++ /dev/null @@ -1,31 +0,0 @@ -package de.rki.coronawarnapp.ui.submission.greencertificate - -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.util.viewmodel.CWAViewModel -import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory -import org.joda.time.LocalDate - -class RequestGreenCertificateViewModel @AssistedInject constructor( - @Assisted private val testType: CoronaTest.Type, -) : CWAViewModel() { - - fun birthDateChanged(localDate: LocalDate?) { - // TODO - } - - fun onAgreeGC() { - // TODO - } - - fun onDisagreeGC() { - // TODO - } - - @AssistedFactory - interface Factory : CWAViewModelFactory<RequestGreenCertificateViewModel> { - fun create(type: CoronaTest.Type): RequestGreenCertificateViewModel - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt index e0e8fd033..19ec160f5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt @@ -34,12 +34,7 @@ class QrCodeRegistrationStateProcessor @Inject constructor( submissionRepository.giveConsentToSubmission(type = coronaTestQRCode.type) } checkTestResult(coronaTestQRCode, coronaTest) - registrationState.postValue( - RegistrationState( - ApiRequestState.SUCCESS, - coronaTest - ) - ) + registrationState.postValue(RegistrationState(ApiRequestState.SUCCESS, coronaTest)) } catch (err: CwaWebException) { registrationState.postValue(RegistrationState(ApiRequestState.FAILED)) registrationError.postValue(err) @@ -54,7 +49,7 @@ class QrCodeRegistrationStateProcessor @Inject constructor( } private fun checkTestResult(request: CoronaTestQRCode, test: CoronaTest) { - if (test.isFinal) { + if (test.isRedeemed) { throw InvalidQRCodeException("CoronaTestResult already redeemed ${request.registrationIdentifier}") } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt index cc3e41d58..6839584b0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt @@ -68,7 +68,7 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co submissionQrCodeScanViewfinderView.setCameraPreview(submissionQrCodeScanPreview) } - viewModel.routeToScreen.observe2(this) { + viewModel.events.observe2(this) { when (it) { is SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode -> { NavGraphDirections @@ -77,6 +77,9 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co } is SubmissionNavigationEvents.NavigateToDispatcher -> navigateToDispatchScreen() is SubmissionNavigationEvents.NavigateToConsent -> goBack() + is SubmissionNavigationEvents.NavigateToRequestDccFragment -> doNavigate( + NavGraphDirections.actionRequestCovidCertificateFragment(it.coronaTestQRCode, it.consentGiven) + ) } } @@ -136,7 +139,7 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co private fun startDecode() { binding.submissionQrCodeScanPreview.decodeSingle { - viewModel.onQrCodeAvailable(it.text) + viewModel.registerCoronaTest(it.text) } } 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 index cc2f2087c..b1e0a8348 100644 --- 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 @@ -26,49 +26,43 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor( private val qrCodeValidator: CoronaTestQrCodeValidator, private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { - val routeToScreen = SingleLiveEvent<SubmissionNavigationEvents>() - val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning + val events = SingleLiveEvent<SubmissionNavigationEvents>() val qrCodeValidationState = SingleLiveEvent<QrCodeRegistrationStateProcessor.ValidationState>() + val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning val registrationState = qrCodeRegistrationStateProcessor.registrationState val registrationError = qrCodeRegistrationStateProcessor.registrationError - fun onQrCodeAvailable(rawResult: String) { - launch { - startQrCodeRegistration(rawResult, isConsentGiven) - } - } - - suspend fun startQrCodeRegistration(rawResult: String, isConsentGiven: Boolean) { + fun registerCoronaTest(rawResult: String) = launch { try { - val coronaTestQRCode = qrCodeValidator.validate(rawResult) + val ctQrCode = qrCodeValidator.validate(rawResult) qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.SUCCESS) - val coronaTest = submissionRepository.testForType(coronaTestQRCode.type).first() - - if (coronaTest != null) { - routeToScreen.postValue( + val coronaTest = submissionRepository.testForType(ctQrCode.type).first() + when { + coronaTest != null -> events.postValue( SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode( - coronaTestQRCode = coronaTestQRCode, + coronaTestQRCode = ctQrCode, consentGiven = isConsentGiven ) ) - } else { - qrCodeRegistrationStateProcessor.startQrCodeRegistration(coronaTestQRCode, isConsentGiven) - if (isConsentGiven) { - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(coronaTestQRCode.type) + + else -> if (!ctQrCode.isDccSupportedByPoc) { + qrCodeRegistrationStateProcessor.startQrCodeRegistration(ctQrCode, isConsentGiven) + if (isConsentGiven) analyticsKeySubmissionCollector.reportAdvancedConsentGiven(ctQrCode.type) + } else { + events.postValue( + SubmissionNavigationEvents.NavigateToRequestDccFragment(ctQrCode, isConsentGiven) + ) } } } catch (err: InvalidQRCodeException) { + Timber.d(err, "Invalid QrCode") qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.INVALID) } } - fun onBackPressed() { - routeToScreen.postValue(SubmissionNavigationEvents.NavigateToConsent) - } + fun onBackPressed() = events.postValue(SubmissionNavigationEvents.NavigateToConsent) - fun onClosePressed() { - routeToScreen.postValue(SubmissionNavigationEvents.NavigateToDispatcher) - } + fun onClosePressed() = events.postValue(SubmissionNavigationEvents.NavigateToDispatcher) fun setCameraDeniedPermanently(denied: Boolean) { Timber.d("setCameraDeniedPermanently(denied=$denied)") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt index 64d13707e..2f0c1d509 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt @@ -8,8 +8,8 @@ import de.rki.coronawarnapp.ui.submission.fragment.SubmissionContactFragment import de.rki.coronawarnapp.ui.submission.fragment.SubmissionDispatcherFragment import de.rki.coronawarnapp.ui.submission.deletionwarning.SubmissionDeletionWarningFragment import de.rki.coronawarnapp.ui.submission.deletionwarning.SubmissionDeletionWarningModule -import de.rki.coronawarnapp.ui.submission.greencertificate.RequestGreenCertificateFragment -import de.rki.coronawarnapp.ui.submission.greencertificate.RequestGreenCertificateFragmentModule +import de.rki.coronawarnapp.ui.submission.greencertificate.RequestCovidCertificateFragment +import de.rki.coronawarnapp.ui.submission.greencertificate.RequestCovidCertificateFragmentModule import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentFragment import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentModule import de.rki.coronawarnapp.ui.submission.qrcode.scan.SubmissionQRCodeScanFragment @@ -113,6 +113,6 @@ internal abstract class SubmissionFragmentModule { @ContributesAndroidInjector(modules = [SubmissionTestResultKeysSharedModule::class]) abstract fun submissionTestResultKeysSharedScreen(): SubmissionTestResultKeysSharedFragment - @ContributesAndroidInjector(modules = [RequestGreenCertificateFragmentModule::class]) - abstract fun requestGreenCertificateFragment(): RequestGreenCertificateFragment + @ContributesAndroidInjector(modules = [RequestCovidCertificateFragmentModule::class]) + abstract fun requestGreenCertificateFragment(): RequestCovidCertificateFragment } 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 789a64d88..dcd2b83ca 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 @@ -16,12 +16,16 @@ sealed class SubmissionNavigationEvents { object NavigateToConsent : SubmissionNavigationEvents() object NavigateToMainActivity : SubmissionNavigationEvents() data class NavigateToResultPendingScreen(var coronaTestType: CoronaTest.Type) : SubmissionNavigationEvents() - data class NavigateToResultAvailableScreen(var coronaTestType: CoronaTest.Type) : SubmissionNavigationEvents() data class NavigateToDeletionWarningFragmentFromQrCode( val coronaTestQRCode: CoronaTestQRCode, val consentGiven: Boolean ) : SubmissionNavigationEvents() + data class NavigateToRequestDccFragment( + val coronaTestQRCode: CoronaTestQRCode, + val consentGiven: Boolean + ) : SubmissionNavigationEvents() + data class NavigateToDeletionWarningFragmentFromTan(val coronaTestTan: CoronaTestTAN, val consentGiven: Boolean) : SubmissionNavigationEvents() diff --git a/Corona-Warn-App/src/main/res/layout/fragment_request_green_certificate.xml b/Corona-Warn-App/src/main/res/layout/fragment_request_covid_certificate.xml similarity index 93% rename from Corona-Warn-App/src/main/res/layout/fragment_request_green_certificate.xml rename to Corona-Warn-App/src/main/res/layout/fragment_request_covid_certificate.xml index 7c6c51477..7129eb51e 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_request_green_certificate.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_request_covid_certificate.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorBackground" - tools:context="ui.submission.greencertificate.RequestGreenCertificateFragment"> + tools:context="ui.submission.greencertificate.RequestCovidCertificateFragment"> <com.google.android.material.appbar.MaterialToolbar android:id="@+id/toolbar" @@ -204,4 +204,17 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> + <com.google.android.material.progressindicator.CircularProgressIndicator + android:id="@+id/progress_bar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:indeterminate="true" + app:indicatorColor="@color/colorAccent" + app:layout_constraintBottom_toBottomOf="@+id/disagree_button" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/scrollview" + app:showAnimationBehavior="inward" + app:trackColor="@android:color/transparent" /> + </androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file 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 a5cb4fd1e..919fadb96 100644 --- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml @@ -395,8 +395,8 @@ app:popUpToInclusive="false" /> <action - android:id="@+id/action_submissionTestResultGreenCertificateFragment" - app:destination="@id/requestGreenCertificateFragment" /> + android:id="@+id/action_requestCovidCertificateFragment" + app:destination="@id/requestCovidCertificateFragment" /> <action android:id="@+id/action_to_submissionTestResultAvailableFragment" @@ -753,6 +753,12 @@ app:destination="@id/submissionConsentFragment" app:popUpTo="@id/mainFragment" app:popUpToInclusive="false" /> + + <action + android:id="@+id/action_submissionDeletionWarningFragment_to_requestCovidCertificateFragment" + app:destination="@id/requestCovidCertificateFragment" + app:popUpTo="@id/mainFragment" + app:popUpToInclusive="false" /> </fragment> <fragment @@ -823,12 +829,31 @@ android:id="@+id/action_global_qrCodeFullScreenFragment" app:destination="@id/qrCodeFullScreenFragment" /> <fragment - android:id="@+id/requestGreenCertificateFragment" - android:name="de.rki.coronawarnapp.ui.submission.greencertificate.RequestGreenCertificateFragment" - android:label="fragment_request_green_certificate" - tools:layout="@layout/fragment_request_green_certificate"> + android:id="@+id/requestCovidCertificateFragment" + android:name="de.rki.coronawarnapp.ui.submission.greencertificate.RequestCovidCertificateFragment" + android:label="fragment_request_covid_certificate" + tools:layout="@layout/fragment_request_covid_certificate"> <argument - android:name="testType" - app:argType="de.rki.coronawarnapp.coronatest.type.CoronaTest$Type" /> + android:name="coronaTestQrCode" + app:argType="de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode" /> + <argument + android:name="coronaTestConsent" + android:defaultValue="false" + app:argType="boolean" /> + + <argument + android:name="deleteOldTest" + android:defaultValue="false" + app:argType="boolean" /> + + <action + android:id="@+id/action_requestCovidCertificateFragment_to_dispatcherFragment" + app:popUpTo="@id/submissionDispatcherFragment" + app:popUpToInclusive="false" /> + + <action + android:id="@+id/action_requestCovidCertificateFragment_to_homeFragment" + app:popUpTo="@id/mainFragment" + app:popUpToInclusive="false" /> </fragment> </navigation> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeTest.kt index a1d45959d..8ef96725b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeTest.kt @@ -8,12 +8,12 @@ import testhelpers.BaseTest class CoronaTestQRCodeTest : BaseTest() { private val instancePCR = CoronaTestQRCode.PCR("pcr") - private val instanceRA = CoronaTestQRCode.RapidAntigen("ra", createdAt = Instant.EPOCH) + private val instanceRA = CoronaTestQRCode.RapidAntigen(hash = "ra", createdAt = Instant.EPOCH) @Test fun `PCR defaults`() { instancePCR.apply { - isDccSupportedbyPoc shouldBe true + isDccSupportedByPoc shouldBe true isDccConsentGiven shouldBe false dateOfBirth shouldBe null } @@ -22,7 +22,7 @@ class CoronaTestQRCodeTest : BaseTest() { @Test fun `RA defaults`() { instanceRA.apply { - isDccSupportedbyPoc shouldBe false + isDccSupportedByPoc shouldBe false isDccConsentGiven shouldBe false dateOfBirth shouldBe null } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTANTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTANTest.kt index 20b1559d6..7de9dca1b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTANTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTANTest.kt @@ -12,7 +12,7 @@ class CoronaTestTANTest : BaseTest() { fun `dcc is not supported by tans`() { instancePCR.apply { isDccConsentGiven shouldBe false - isDccSupportedbyPoc shouldBe false + isDccSupportedByPoc shouldBe false dateOfBirth shouldBe null } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModelTest.kt new file mode 100644 index 000000000..bd9be00a5 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModelTest.kt @@ -0,0 +1,304 @@ +package de.rki.coronawarnapp.ui.submission.greencertificate + +import androidx.lifecycle.MutableLiveData +import de.rki.coronawarnapp.coronatest.CoronaTestRepository +import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode +import de.rki.coronawarnapp.coronatest.type.CoronaTest +import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor +import de.rki.coronawarnapp.util.ui.SingleLiveEvent +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import kotlinx.coroutines.flow.flowOf +import org.joda.time.Instant +import org.joda.time.LocalDate +import org.joda.time.format.DateTimeFormat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.extension.ExtendWith +import testhelpers.BaseTest +import testhelpers.extensions.InstantExecutorExtension +import testhelpers.extensions.getOrAwaitValue + +@ExtendWith(InstantExecutorExtension::class) +internal class RequestCovidCertificateViewModelTest : BaseTest() { + + @MockK lateinit var qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor + @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector + @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var coronaTestRepository: CoronaTestRepository + @MockK lateinit var coronaTest: CoronaTest + + private val date = LocalDate.parse( + "01.01.1987", + DateTimeFormat.forPattern("dd.MM.yyyy") + ) + + private val ratQRCode = CoronaTestQRCode.RapidAntigen(hash = "hash", dateOfBirth = date, createdAt = Instant.EPOCH) + private val pcrQRCode = CoronaTestQRCode.PCR("GUID") + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + qrCodeRegistrationStateProcessor.apply { + coEvery { startQrCodeRegistration(any(), any()) } just Runs + coEvery { registrationError } returns SingleLiveEvent() + coEvery { showRedeemedTokenWarning } returns SingleLiveEvent() + coEvery { registrationState } returns MutableLiveData() + } + + submissionRepository.apply { + coEvery { registerTest(any()) } returns coronaTest + coEvery { testForType(any()) } returns flowOf(coronaTest) + } + + coEvery { coronaTestRepository.removeTest(any()) } returns coronaTest + + every { coronaTest.identifier } returns "identifier" + every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs + } + + @Test + fun birthDateChanged() { + createInstance().apply { + birthDateChanged(date) + birthDate.getOrAwaitValue() shouldBe date + } + } + + @Test + fun `PCR onAgreeGC removes and registers new test`() { + createInstance(deleteOldTest = true).apply { + birthDateChanged(date) + onAgreeGC() + + coVerify { + submissionRepository.testForType(any()) + coronaTestRepository.removeTest(any()) + qrCodeRegistrationStateProcessor.startQrCodeRegistration( + pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date), + any() + ) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + } + + @Test + fun `PCR onAgreeGC registers new test and does not remove old Test`() { + createInstance(deleteOldTest = false).apply { + birthDateChanged(date) + onAgreeGC() + + coVerify { + qrCodeRegistrationStateProcessor.startQrCodeRegistration( + pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date), + any() + ) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + + coVerify(exactly = 0) { + submissionRepository.testForType(any()) + coronaTestRepository.removeTest(any()) + } + } + } + + @Test + fun `PCR onDisagreeGC removes and registers new test`() { + createInstance(deleteOldTest = true).apply { + onDisagreeGC() + + coVerify { + submissionRepository.testForType(any()) + coronaTestRepository.removeTest(any()) + qrCodeRegistrationStateProcessor.startQrCodeRegistration( + pcrQRCode.copy(isDccConsentGiven = false), + any() + ) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + } + + @Test + fun `PCR onDisagreeGC registers new test and does not remove old Test`() { + createInstance(deleteOldTest = false).apply { + onDisagreeGC() + + coVerify { + qrCodeRegistrationStateProcessor.startQrCodeRegistration( + pcrQRCode.copy(isDccConsentGiven = false), + any() + ) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + + coVerify(exactly = 0) { + submissionRepository.testForType(any()) + coronaTestRepository.removeTest(any()) + } + } + } + + @Test + fun `RAT onAgreeGC removes and registers new test`() { + createInstance(coronaTestQRCode = ratQRCode, deleteOldTest = true).apply { + onAgreeGC() + + coVerify { + submissionRepository.testForType(any()) + coronaTestRepository.removeTest(any()) + qrCodeRegistrationStateProcessor.startQrCodeRegistration( + ratQRCode.copy(isDccConsentGiven = true, dateOfBirth = date), + any() + ) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + } + + @Test + fun `RAT onAgreeGC registers new test and does not remove old Test`() { + createInstance(coronaTestQRCode = ratQRCode, deleteOldTest = false).apply { + onAgreeGC() + + coVerify { + qrCodeRegistrationStateProcessor.startQrCodeRegistration( + ratQRCode.copy(isDccConsentGiven = true), + any() + ) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + + coVerify(exactly = 0) { + submissionRepository.testForType(any()) + coronaTestRepository.removeTest(any()) + } + } + } + + @Test + fun `RAT onDisagreeGC removes and registers new test`() { + createInstance(coronaTestQRCode = ratQRCode, deleteOldTest = true).apply { + onDisagreeGC() + + coVerify { + submissionRepository.testForType(any()) + coronaTestRepository.removeTest(any()) + qrCodeRegistrationStateProcessor.startQrCodeRegistration( + ratQRCode.copy(isDccConsentGiven = false), + any() + ) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + } + + @Test + fun `RAT onDisagreeGC registers new test and does not remove old Test`() { + createInstance(coronaTestQRCode = ratQRCode, deleteOldTest = false).apply { + onDisagreeGC() + + coVerify { + qrCodeRegistrationStateProcessor.startQrCodeRegistration( + ratQRCode.copy(isDccConsentGiven = false), + any() + ) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + + coVerify(exactly = 0) { + submissionRepository.testForType(any()) + coronaTestRepository.removeTest(any()) + } + } + } + + @Test + fun navigateBack() { + createInstance().apply { + navigateBack() + events.getOrAwaitValue() shouldBe Back + } + } + + @Test + fun navigateToHomeScreen() { + createInstance().apply { + navigateToHomeScreen() + events.getOrAwaitValue() shouldBe ToHomeScreen + } + } + + @Test + fun navigateToDispatcherScreen() { + createInstance().apply { + navigateToDispatcherScreen() + events.getOrAwaitValue() shouldBe ToDispatcherScreen + } + } + + @Test + fun `onAgreeGC reports analytics`() { + createInstance(coronTestConsent = true).apply { + onAgreeGC() + coVerify { + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + } + + @Test + fun `onDisagreeGC reports analytics`() { + createInstance(coronTestConsent = true).apply { + onDisagreeGC() + coVerify { + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + } + + @Test + fun `onAgreeGC does not report analytics`() { + createInstance(coronTestConsent = false).apply { + onAgreeGC() + coVerify(exactly = 0) { + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + } + + @Test + fun `onDisagreeGC does not report analytics`() { + createInstance(coronTestConsent = false).apply { + onDisagreeGC() + coVerify(exactly = 0) { + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + } + + private fun createInstance( + coronaTestQRCode: CoronaTestQRCode = pcrQRCode, + coronTestConsent: Boolean = true, + deleteOldTest: Boolean = false + ) = RequestCovidCertificateViewModel( + coronaTestQrCode = coronaTestQRCode, + coronaTestConsent = coronTestConsent, + deleteOldTest = deleteOldTest, + coronaTestRepository = coronaTestRepository, + submissionRepository = submissionRepository, + qrCodeRegistrationStateProcessor = qrCodeRegistrationStateProcessor, + analyticsKeySubmissionCollector = analyticsKeySubmissionCollector + ) +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt index 92d0fba01..18b2c8425 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt @@ -22,6 +22,7 @@ import io.mockk.just import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runBlockingTest +import org.joda.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -39,15 +40,11 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() { @MockK lateinit var qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector - private val coronaTestFlow = MutableStateFlow<CoronaTest?>( - null - ) - @BeforeEach fun setUp() { MockKAnnotations.init(this) - every { submissionRepository.testForType(any()) } returns coronaTestFlow + every { submissionRepository.testForType(any()) } returns MutableStateFlow<CoronaTest?>(null) coEvery { qrCodeRegistrationStateProcessor.showRedeemedTokenWarning } returns SingleLiveEvent() coEvery { qrCodeRegistrationStateProcessor.registrationState } returns MutableLiveData( QrCodeRegistrationStateProcessor.RegistrationState(ApiRequestState.IDLE) @@ -86,12 +83,12 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() { viewModel.qrCodeValidationState.value shouldBe ValidationState.STARTED - viewModel.onQrCodeAvailable(validQrCode) + viewModel.registerCoronaTest(validQrCode) viewModel.qrCodeValidationState.observeForever {} viewModel.qrCodeValidationState.value shouldBe ValidationState.SUCCESS // invalid guid - viewModel.onQrCodeAvailable(invalidQrCode) + viewModel.registerCoronaTest(invalidQrCode) viewModel.qrCodeValidationState.value shouldBe ValidationState.INVALID } @@ -104,34 +101,79 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() { } @Test - fun `startQrCodeRegistration() should call analyticsKeySubmissionCollector for PCR tests`() = runBlockingTest { - val coronaTestQRCode = CoronaTestQRCode.PCR(qrCodeGUID = "123456-12345678-1234-4DA7-B166-B86D85475064") + fun `registerCoronaTest() should call analyticsKeySubmissionCollector for PCR tests`() = + runBlockingTest { + val coronaTestQRCode = CoronaTestQRCode.PCR(qrCodeGUID = "123456-12345678-1234-4DA7-B166-B86D85475064") - every { qrCodeValidator.validate(any()) } returns coronaTestQRCode - every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs - coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs + every { qrCodeValidator.validate(any()) } returns coronaTestQRCode + every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs + coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs - createViewModel().startQrCodeRegistration(rawResult = "", isConsentGiven = true) + createViewModel().registerCoronaTest(rawResult = "") - verify(exactly = 1) { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR) } - verify(exactly = 0) { - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN) + verify(exactly = 0) { + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN) + } } - } @Test - fun `startQrCodeRegistration() should NOT call analyticsKeySubmissionCollector for RAT tests`() = runBlockingTest { - val coronaTestQRCode = CoronaTestQRCode.PCR(qrCodeGUID = "123456-12345678-1234-4DA7-B166-B86D85475064") + fun `registerCoronaTest() should NOT call analyticsKeySubmissionCollector for RAT tests`() = + runBlockingTest { + val coronaTestQRCode = CoronaTestQRCode.PCR(qrCodeGUID = "123456-12345678-1234-4DA7-B166-B86D85475064") - every { qrCodeValidator.validate(any()) } returns coronaTestQRCode - every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs - coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs + every { qrCodeValidator.validate(any()) } returns coronaTestQRCode + every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs + coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs - createViewModel().startQrCodeRegistration(rawResult = "", isConsentGiven = true) + createViewModel().registerCoronaTest(rawResult = "") - verify(exactly = 1) { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR) } - verify(exactly = 0) { - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN) + verify(exactly = 0) { + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN) + } + } + + @Test + fun `registerCoronaTest() should call analyticsKeySubmissionCollector for RAT tests - no-dcc support`() = + runBlockingTest { + val coronaTestQRCode = CoronaTestQRCode.RapidAntigen( + hash = "123456-12345678-1234-4DA7-B166-B86D85475064", + createdAt = Instant.EPOCH, + isDccSupportedByPoc = false + ) + + every { qrCodeValidator.validate(any()) } returns coronaTestQRCode + every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs + coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs + + createViewModel().registerCoronaTest(rawResult = "") + + verify(exactly = 1) { + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN) + } + verify(exactly = 0) { + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR) + } + } + + @Test + fun `registerCoronaTest() should Not call analyticsKeySubmissionCollector for RAT tests - dcc support`() = + runBlockingTest { + val coronaTestQRCode = CoronaTestQRCode.RapidAntigen( + hash = "123456-12345678-1234-4DA7-B166-B86D85475064", + createdAt = Instant.EPOCH, + isDccSupportedByPoc = true + ) + + every { qrCodeValidator.validate(any()) } returns coronaTestQRCode + every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs + coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs + + createViewModel().registerCoronaTest(rawResult = "") + verify(exactly = 0) { + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR) + } } - } } -- GitLab