diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInEvent.kt index b7c535d6e3aff991e1e08a40d8a5560e19adb1ff..ab3433e8ae64f52ff3e1f2a746131cc96811dbdb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInEvent.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInEvent.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins import de.rki.coronawarnapp.presencetracing.checkins.CheckIn import de.rki.coronawarnapp.presencetracing.checkins.qrcode.VerifiedTraceLocation +import de.rki.coronawarnapp.util.ui.LazyString sealed class CheckInEvent { @@ -17,6 +18,8 @@ sealed class CheckInEvent { data class ConfirmSwipeItem(val checkIn: CheckIn, val position: Int) : CheckInEvent() + data class InvalidQrCode(val errorText: LazyString) : CheckInEvent() + object ShowInformation : CheckInEvent() object OpenDeviceSettings : CheckInEvent() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsFragment.kt index 488b48271452b4ac0e7012fb90a267387ea46d21..74502474c81d559f1bb54a6a1b64c656211b144a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsFragment.kt @@ -31,6 +31,7 @@ import de.rki.coronawarnapp.util.lists.decorations.TopBottomPaddingDecorator import de.rki.coronawarnapp.util.lists.diffutil.update import de.rki.coronawarnapp.util.onScroll import de.rki.coronawarnapp.util.tryHumanReadableError +import de.rki.coronawarnapp.util.ui.LazyString import de.rki.coronawarnapp.util.ui.doNavigate import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBinding @@ -66,14 +67,8 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag bindRecycler() bindFAB() - viewModel.checkins.observe2(this) { items -> - updateViews(items) - } - - viewModel.events.observe2(this) { - onNavigationEvent(it) - } - + viewModel.checkins.observe2(this) { items -> updateViews(items) } + viewModel.events.observe2(this) { it?.let { onNavigationEvent(it) } } viewModel.errorEvent.observe2(this) { val errorForHumans = it.tryHumanReadableError(requireContext()) Toast.makeText(requireContext(), errorForHumans.description, Toast.LENGTH_LONG).show() @@ -85,7 +80,7 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag viewModel.checkCameraSettings() } - private fun onNavigationEvent(event: CheckInEvent?) { + private fun onNavigationEvent(event: CheckInEvent) { when (event) { is CheckInEvent.ConfirmCheckIn -> { setupAxisTransition() @@ -127,9 +122,21 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag doNavigate(CheckInsFragmentDirections.actionCheckInsFragmentToCheckInOnboardingFragment(false)) } is CheckInEvent.OpenDeviceSettings -> openDeviceSettings() + is CheckInEvent.InvalidQrCode -> showInvalidQrCodeInformation(event.errorText) } } + private fun showInvalidQrCodeInformation(lazyErrorText: LazyString) { + val errorText = lazyErrorText.get(requireContext()) + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.trace_location_attendee_invalid_qr_code_dialog_title) + .setMessage(getString(R.string.trace_location_attendee_invalid_qr_code_dialog_message, errorText)) + .setPositiveButton(R.string.trace_location_attendee_invalid_qr_code_dialog_positive_button) { _, _ -> + // NO-OP + } + .show() + } + private fun updateViews(items: List<CheckInsItem>) { checkInsAdapter.update(items) binding.apply { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsViewModel.kt index 49ff5d5c58b886f892112fdbc136c6aa3a3cb786..c8aaf8345fc7b71785f2e44711f94f95207cbbb6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsViewModel.kt @@ -20,6 +20,8 @@ import de.rki.coronawarnapp.util.coroutine.AppScope import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.flow.intervalFlow import de.rki.coronawarnapp.util.ui.SingleLiveEvent +import de.rki.coronawarnapp.util.ui.toLazyString +import de.rki.coronawarnapp.util.ui.toResolvingString import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import kotlinx.coroutines.CoroutineScope @@ -140,15 +142,24 @@ class CheckInsViewModel @AssistedInject constructor( } private fun verifyUri(uri: String) = launch { - Timber.i("uri: $uri") - val qrCodePayload = qrCodeUriParser.getQrCodePayload(uri) - when (val verifyResult = traceLocationVerifier.verifyTraceLocation(qrCodePayload)) { - is TraceLocationVerifier.VerificationResult.Valid -> events.postValue( - if (cleanHistory) - CheckInEvent.ConfirmCheckInWithoutHistory(verifyResult.verifiedTraceLocation) - else - CheckInEvent.ConfirmCheckIn(verifyResult.verifiedTraceLocation) - ) + try { + Timber.i("uri: $uri") + val qrCodePayload = qrCodeUriParser.getQrCodePayload(uri) + when (val verifyResult = traceLocationVerifier.verifyTraceLocation(qrCodePayload)) { + is TraceLocationVerifier.VerificationResult.Valid -> events.postValue( + if (cleanHistory) + CheckInEvent.ConfirmCheckInWithoutHistory(verifyResult.verifiedTraceLocation) + else + CheckInEvent.ConfirmCheckIn(verifyResult.verifiedTraceLocation) + ) + is TraceLocationVerifier.VerificationResult.Invalid -> events.postValue( + CheckInEvent.InvalidQrCode(verifyResult.errorTextRes.toResolvingString()) + ) + } + } catch (e: Exception) { + Timber.d(e, "TraceLocation verification failed") + val msg = e.message ?: "QR-Code was invalid" + events.postValue(CheckInEvent.InvalidQrCode(msg.toLazyString())) } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsViewModelTest.kt index dbb826612e6d7ebc4439768e9978e2e3030643ec..822070756dd6a94a7f3d85dec0b4a696895c5462 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsViewModelTest.kt @@ -6,11 +6,14 @@ import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository import de.rki.coronawarnapp.presencetracing.checkins.qrcode.QRCodeUriParser import de.rki.coronawarnapp.presencetracing.checkins.qrcode.TraceLocationVerifier import de.rki.coronawarnapp.presencetracing.checkins.checkout.CheckOutHandler +import de.rki.coronawarnapp.presencetracing.checkins.qrcode.InvalidQrCodeDataException +import de.rki.coronawarnapp.presencetracing.checkins.qrcode.InvalidQrCodeUriException import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items.ActiveCheckInVH import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items.CameraPermissionVH import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items.PastCheckInVH import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.permission.CameraPermissionProvider +import io.kotest.assertions.throwables.shouldNotThrow import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf import io.mockk.MockKAnnotations @@ -180,6 +183,32 @@ class CheckInsViewModelTest : BaseTest() { } } + @Test + fun `Handle uri InvalidQrCodeUriException`() = runBlockingTest { + every { savedState.get<String>("deeplink.last") } returns null + coEvery { qrCodeUriParser.getQrCodePayload(any()) } throws InvalidQrCodeUriException("Invalid") + val url = "https://e.coronawarn.app?v=1#place_holder" + + shouldNotThrow<InvalidQrCodeUriException> { + createInstance(deepLink = url, scope = this).apply { + events.getOrAwaitValue().shouldBeInstanceOf<CheckInEvent.InvalidQrCode>() + } + } + } + + @Test + fun `Handle uri InvalidQrCodeDataException`() = runBlockingTest { + every { savedState.get<String>("deeplink.last") } returns null + coEvery { qrCodeUriParser.getQrCodePayload(any()) } throws InvalidQrCodeDataException("Invalid") + val url = "https://e.coronawarn.app?v=1#place_holder" + + shouldNotThrow<InvalidQrCodeDataException> { + createInstance(deepLink = url, scope = this).apply { + events.getOrAwaitValue().shouldBeInstanceOf<CheckInEvent.InvalidQrCode>() + } + } + } + private fun createInstance(deepLink: String?, scope: CoroutineScope) = CheckInsViewModel( savedState = savedState,