diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesFragmentTest.kt index 39be22363ad93e8911be994071f4b7e9591ce19d..a1865e350a2cf081bc25d2949059ab328264e63b 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesFragmentTest.kt @@ -42,7 +42,7 @@ class CertificatesFragmentTest : BaseUITest() { @MockK lateinit var vaccinatedPerson: VaccinatedPerson private val formatter = DateTimeFormat.forPattern("dd.MM.yyyy HH:mm") - private val testDate = DateTime.parse("12.05.2021 19:00", formatter).toInstant() + private val testDate = DateTime.parse("12.05.2021 19:00", formatter) @Before fun setup() { diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt index 4b7f88440901b41d38e243161bfd088e54e4ac6e..7ea2e16565672eb475f993f182df41a03fd66880 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt @@ -7,7 +7,7 @@ import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQrCodeValidator import de.rki.coronawarnapp.nearby.modules.tekhistory.TEKHistoryProvider import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository import de.rki.coronawarnapp.submission.SubmissionRepository -import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor +import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentFragment import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentFragmentArgs import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentViewModel @@ -31,7 +31,7 @@ class SubmissionConsentFragmentTest : BaseUITest() { @MockK lateinit var submissionRepository: SubmissionRepository @MockK lateinit var interoperabilityRepository: InteroperabilityRepository @MockK lateinit var tekHistoryProvider: TEKHistoryProvider - @MockK lateinit var qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor + @MockK lateinit var testRegistrationStateProcessor: TestRegistrationStateProcessor @MockK lateinit var qrCodeValidator: CoronaTestQrCodeValidator private lateinit var viewModel: SubmissionConsentViewModel @@ -48,7 +48,7 @@ class SubmissionConsentFragmentTest : BaseUITest() { interoperabilityRepository, TestDispatcherProvider(), tekHistoryProvider, - qrCodeRegistrationStateProcessor, + testRegistrationStateProcessor, submissionRepository, qrCodeValidator ) diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt index 9efe83450ff7438a21e8d19d08f852e8543a79c3..628b6cd792cc25f91aba21a62d3320e1b69dd7fc 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt @@ -7,8 +7,8 @@ import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.pcr.notification.PCRTestResultAvailableNotificationService +import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository import de.rki.coronawarnapp.submission.SubmissionRepository -import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.ui.submission.testresult.negative.SubmissionTestResultNegativeFragment import de.rki.coronawarnapp.ui.submission.testresult.negative.SubmissionTestResultNegativeViewModel import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultConsentGivenFragmentArgs @@ -34,6 +34,7 @@ class SubmissionTestResultNegativeFragmentTest : BaseUITest() { lateinit var viewModel: SubmissionTestResultNegativeViewModel @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var certificateRepository: TestCertificateRepository @MockK lateinit var testResultAvailableNotificationService: PCRTestResultAvailableNotificationService @MockK lateinit var testType: CoronaTest.Type private val resultNegativeFragmentArgs = @@ -44,11 +45,13 @@ class SubmissionTestResultNegativeFragmentTest : BaseUITest() { MockKAnnotations.init(this, relaxed = true) every { submissionRepository.testForType(any()) } returns flowOf() + every { certificateRepository.certificates } returns flowOf() viewModel = spyk( SubmissionTestResultNegativeViewModel( TestDispatcherProvider(), submissionRepository, + certificateRepository, testResultAvailableNotificationService, testType ) @@ -70,12 +73,13 @@ class SubmissionTestResultNegativeFragmentTest : BaseUITest() { @Screenshot fun capture_fragment() { every { viewModel.testResult } returns MutableLiveData( - TestResultUIState( + SubmissionTestResultNegativeViewModel.UIState( coronaTest = mockk<CoronaTest>().apply { every { testResult } returns CoronaTestResult.PCR_NEGATIVE every { registeredAt } returns Instant.now() every { type } returns CoronaTest.Type.PCR - } + }, + certificateState = SubmissionTestResultNegativeViewModel.CertificateState.AVAILABLE ) ) launchFragmentInContainer2<SubmissionTestResultNegativeFragment>(fragmentArgs = resultNegativeFragmentArgs) diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragmentTest.kt index 68b8a1b80a3d8ae83fad80d050cced8549a2e6b0..1f1b0dac59fa9341c3a57ea7c9c2362955e1b324 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragmentTest.kt @@ -9,9 +9,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.coronatest.TestRegistrationRequest import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode -import de.rki.coronawarnapp.ui.submission.ApiRequestState -import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor +import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK @@ -36,13 +36,13 @@ class RequestCovidCertificateFragmentTest : BaseUITest() { every { viewModel.birthDate } returns MutableLiveData(null) every { viewModel.registrationState } returns MutableLiveData( - QrCodeRegistrationStateProcessor.RegistrationState(ApiRequestState.IDLE) + TestRegistrationStateProcessor.State.Idle ) setupMockViewModel( object : RequestCovidCertificateViewModel.Factory { override fun create( - coronaTestQrCode: CoronaTestQRCode, + testRegistrationRequest: TestRegistrationRequest, coronaTestConsent: Boolean, deleteOldTest: Boolean ): RequestCovidCertificateViewModel = viewModel diff --git a/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml b/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..88a2064d0a9ce8cd0aeff6f6f8bbf950930d744f --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/AndroidManifest.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + + <queries> + <!-- See DebugLogger.isAutoLoggingEnabled --> + <package android:name="de.rki.coronawarnapp.els.autologger" /> + </queries> + +</manifest> diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragment.kt index 4eaae484d689e3f167712fb188037a219e6ddaf3..c744a067c8a4c9463485f31d30ddd7b9bacaafcd 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragment.kt @@ -65,16 +65,16 @@ class CoronaTestTestFragment : Fragment(R.layout.fragment_test_coronatest), Auto qrcodeScanViewfinder.setCameraPreview(binding.qrcodeScanPreview) } - viewModel.pcrtState.observe2(this) { - binding.pcrtData.text = it.getNiceTextForHumans() + viewModel.pcrtState.observe2(this) { state -> + binding.pcrtData.text = state.getNiceTextForHumans() } binding.apply { pcrtDeleteAction.setOnClickListener { viewModel.deletePCRT() } pcrtRefreshAction.setOnClickListener { viewModel.refreshPCRT() } } - viewModel.ratState.observe2(this) { - binding.ratData.text = it.getNiceTextForHumans() + viewModel.ratState.observe2(this) { state -> + binding.ratData.text = state.getNiceTextForHumans() } binding.apply { ratDeleteAction.setOnClickListener { viewModel.deleteRAT() } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragmentViewModel.kt index 544158248957931dfca9e3df2390b194c2ff719f..79c8f84ce15a08b94c5843b4461ae055b17d398a 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/coronatest/ui/CoronaTestTestFragmentViewModel.kt @@ -6,12 +6,8 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository import de.rki.coronawarnapp.coronatest.CoronaTestRepository -import de.rki.coronawarnapp.coronatest.latestPCRT -import de.rki.coronawarnapp.coronatest.latestRAT import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQrCodeValidator import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest -import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel @@ -28,17 +24,17 @@ class CoronaTestTestFragmentViewModel @AssistedInject constructor( ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val errorEvents = SingleLiveEvent<Throwable>() - val pcrtState = coronaTestRepository.latestPCRT.map { - PCRTState( - coronaTest = it - ) - }.asLiveData(context = dispatcherProvider.Default) + val pcrtState = coronaTestRepository.coronaTests + .map { tests -> tests.filter { it.type == CoronaTest.Type.PCR } } + .map { pcrTests -> + PCRTState(coronaTests = pcrTests) + }.asLiveData(context = dispatcherProvider.Default) - val ratState = coronaTestRepository.latestRAT.map { - RATState( - coronaTest = it - ) - }.asLiveData(context = dispatcherProvider.Default) + val ratState = coronaTestRepository.coronaTests + .map { tests -> tests.filter { it.type == CoronaTest.Type.RAPID_ANTIGEN } } + .map { raTests -> + RATState(coronaTests = raTests) + }.asLiveData(context = dispatcherProvider.Default) val testsInContactDiary = contactDiaryRepository.testResults.map { it.foldIndexed(StringBuilder()) { id, buffer, item -> @@ -58,12 +54,12 @@ class CoronaTestTestFragmentViewModel @AssistedInject constructor( fun deletePCRT() = launch { try { - val pcrTest = coronaTestRepository.latestPCRT.first() - if (pcrTest == null) { - Timber.d("No PCR test to delete") - return@launch - } - coronaTestRepository.removeTest(pcrTest.identifier) + Timber.i("Deleting PCR tests.") + coronaTestRepository.coronaTests.first() + .filter { it.type == CoronaTest.Type.PCR } + .forEach { test -> + coronaTestRepository.removeTest(test.identifier) + } } catch (e: Exception) { Timber.e(e, "Failed to delete PCR test.") errorEvents.postValue(e) @@ -80,14 +76,14 @@ class CoronaTestTestFragmentViewModel @AssistedInject constructor( } } - fun deleteRAT() = launch { + fun deleteRAT(): Unit = launch { try { - val raTest = coronaTestRepository.latestRAT.first() - if (raTest == null) { - Timber.d("No RA test to delete") - return@launch - } - coronaTestRepository.removeTest(raTest.identifier) + Timber.i("Deleting RA tests.") + coronaTestRepository.coronaTests.first() + .filter { it.type == CoronaTest.Type.RAPID_ANTIGEN } + .forEach { test -> + coronaTestRepository.removeTest(test.identifier) + } } catch (e: Exception) { Timber.e(e, "Failed to delete RA test.") errorEvents.postValue(e) @@ -105,28 +101,34 @@ class CoronaTestTestFragmentViewModel @AssistedInject constructor( } data class PCRTState( - val coronaTest: PCRCoronaTest? + val coronaTests: Collection<CoronaTest> ) { fun getNiceTextForHumans(): String { - return coronaTest - ?.toString() - ?.replace("PCRCoronaTest(", "") - ?.replace(",", ",\n") - ?.trimEnd { it == ')' } - ?: "No PCR test registered." + if (coronaTests.isEmpty()) { + return "No PCR test registered." + } + return coronaTests.joinToString("\n") { test -> + test.toString() + .replace("PCRCoronaTest(", "") + .replace(",", ",\n") + .trimEnd { it == ')' } + } } } data class RATState( - val coronaTest: RACoronaTest? + val coronaTests: Collection<CoronaTest> ) { fun getNiceTextForHumans(): String { - return coronaTest - ?.toString() - ?.replace("RACoronaTest(", "") - ?.replace(",", ",\n") - ?.trimEnd { it == ')' } - ?: "No rapid antigen test registered." + if (coronaTests.isEmpty()) { + return "No rapid antigen test registered." + } + return coronaTests.joinToString("\n") { test -> + test.toString() + .replace("RACoronaTest(", "") + .replace(",", ",\n") + .trimEnd { it == ')' } + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt index ab7f0f1f689ab045fca88abbefb6e70b9cdc0e1f..3860af4b3168b998dd46e7f56eb9d9c0bfbfca8d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.bugreporting.debuglog import android.annotation.SuppressLint import android.content.Context +import android.content.pm.PackageManager import android.util.Log import de.rki.coronawarnapp.bugreporting.censors.BugCensor import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLogStorageCheck @@ -62,13 +63,30 @@ class DebugLogger( ) } + private fun isAutoLoggingEnabled(): Boolean { + if (!CWADebug.isDeviceForTestersBuild) return false + + return try { + val autoLoggerPkg = "de.rki.coronawarnapp.els.autologger" + context.packageManager.getPackageInfo(autoLoggerPkg, 0) + Timber.tag(TAG).i("Autologger package is installed (%s).", autoLoggerPkg) + true + } catch (e: PackageManager.NameNotFoundException) { + Timber.tag(TAG).i("DeviceForTester build, but no autologger package installed.") + false + } catch (e: Exception) { + Timber.tag(TAG).e(e, "Failed to determiner if autologger package is installed.") + false + } + } + fun init() = try { val startLogger = when { triggerFile.exists() -> { Timber.tag(TAG).i("Trigger file exists, starting debug log.") true } - CWADebug.isDeviceForTestersBuild -> { + isAutoLoggingEnabled() -> { Timber.tag(TAG).i("Trigger file does not exist, but it's a tester build, starting debug log.") true } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt index 06648265c95838ea0cafb2b40594bbfe850cd725..6ef130cb08c18a8fa7c27ab940a6a1a9027b80a7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.coronatest import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.coronatest.errors.AlreadyRedeemedException import de.rki.coronawarnapp.coronatest.errors.CoronaTestNotFoundException import de.rki.coronawarnapp.coronatest.errors.DuplicateCoronaTestException import de.rki.coronawarnapp.coronatest.migration.PCRTestMigration @@ -10,6 +11,8 @@ import de.rki.coronawarnapp.coronatest.storage.CoronaTestStorage import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor import de.rki.coronawarnapp.coronatest.type.TestIdentifier +import de.rki.coronawarnapp.exception.ExceptionCategory +import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.util.coroutine.AppScope import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.flow.HotDataFlow @@ -72,22 +75,65 @@ class CoronaTestRepository @Inject constructor( private fun getProcessor(type: CoronaTest.Type) = processors.single { it.type == type } - suspend fun registerTest(request: TestRegistrationRequest): CoronaTest { - Timber.tag(TAG).i("registerTest(request=%s)", request) + /** + * Default preconditions prevent duplicate test registration, + * and registration of an already redeemed test. + * If pre and post-condition are not met an [IllegalStateException] is thrown. + * + * @return the new test that was registered (or an exception is thrown) + */ + suspend fun registerTest( + request: TestRegistrationRequest, + preCondition: ((Collection<CoronaTest>) -> Boolean) = { currentTests -> + if (currentTests.any { it.type == request.type }) { + throw DuplicateCoronaTestException("There is already a test of this type: ${request.type}.") + } + true + }, + postCondition: ((CoronaTest) -> Boolean) = { newTest -> + if (newTest.isRedeemed) { + Timber.w("Replacement test was already redeemed, removing it, will not use.") + throw AlreadyRedeemedException(newTest) + } + true + } + ): CoronaTest { + Timber.tag(TAG).i( + "registerTest(request=%s, preCondition=%s, postCondition=%s)", + request, preCondition, postCondition + ) // We check early, if there is no processor, crash early, "should" never happen though... val processor = getProcessor(request.type) val currentTests = internalData.updateBlocking { - if (values.any { it.type == request.type }) { - throw DuplicateCoronaTestException("There is already a test of this type: ${request.type}.") + if (!preCondition(values)) { + throw IllegalStateException("PreCondition for current tests not fullfilled.") } - val test = processor.create(request).also { + val existing = values.singleOrNull { it.type == request.type } + + val newTest = processor.create(request).also { Timber.tag(TAG).i("New test created: %s", it) } - toMutableMap().apply { this[test.identifier] = test } + if (!postCondition(newTest)) { + throw IllegalStateException("PostCondition for new tests not fullfilled.") + } + + if (existing != null) { + Timber.tag(TAG).w("We already have a test of this type, removing old test: %s", request) + try { + getProcessor(existing.type).onRemove(existing) + } catch (e: Exception) { + e.report(ExceptionCategory.INTERNAL) + } + } + + toMutableMap().apply { + existing?.let { remove(it.identifier) } + this[newTest.identifier] = newTest + } } return currentTests[request.identifier]!! 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 99ab06763c99b475ee7f25a631f09bac318dac55..5e0ec73e6dbf2d1dda8d64bc83c15bf05faf8bca 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 @@ -1,9 +1,10 @@ package de.rki.coronawarnapp.coronatest +import android.os.Parcelable import de.rki.coronawarnapp.coronatest.type.CoronaTest import org.joda.time.LocalDate -interface TestRegistrationRequest { +interface TestRegistrationRequest : Parcelable { val type: CoronaTest.Type val identifier: String val isDccSupportedByPoc: Boolean diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/errors/AlreadyRedeemedException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/errors/AlreadyRedeemedException.kt new file mode 100644 index 0000000000000000000000000000000000000000..40ceeef924d23b21513c4e4ccede47ba3b86cf52 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/errors/AlreadyRedeemedException.kt @@ -0,0 +1,7 @@ +package de.rki.coronawarnapp.coronatest.errors + +import de.rki.coronawarnapp.coronatest.type.CoronaTest + +class AlreadyRedeemedException( + coronaTest: CoronaTest +) : IllegalArgumentException("Test was already redeemed ${coronaTest.identifier}") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestResult.kt index 0fdb395a62ec5d5ca519f8d009b3ee5b9f8514d2..09c931f12a74f13ca57de95b32e7eecf41136844 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestResult.kt @@ -8,13 +8,15 @@ import org.json.JSONObject data class CoronaTestResultResponse( val coronaTestResult: CoronaTestResult, - val sampleCollectedAt: Instant? + val sampleCollectedAt: Instant?, + val labId: String? ) { companion object { fun fromResponse(response: VerificationApiV1.TestResultResponse) = CoronaTestResultResponse( coronaTestResult = CoronaTestResult.fromInt(response.testResult), - sampleCollectedAt = response.sampleCollectedAt?.toLong()?.let { Instant.ofEpochSecond(it) } + sampleCollectedAt = response.sampleCollectedAt?.toLong()?.let { Instant.ofEpochSecond(it) }, + labId = response.labId ) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt index 4f46627679001d32028ec1307cdb8a281e3cca00..b7c4efd790ac9045d3345a4d6e753ee6a98ce332 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt @@ -32,7 +32,8 @@ interface VerificationApiV1 { data class TestResultResponse( @SerializedName("testResult") val testResult: Int, - @SerializedName("sc") val sampleCollectedAt: Int? + @SerializedName("sc") val sampleCollectedAt: Int?, + @SerializedName("labId") val labId: String? ) @POST("version/v1/testresult") 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 824a468b746938f0b30c3c532cc7f00800ad8cd4..d1fc0813d294254adcc85f57aac5b327e5731b3f 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 @@ -49,6 +49,9 @@ interface CoronaTest { // Has the corresponding entry been created in the test certificate storage val isDccDataSetCreated: Boolean + // The ID of the lab that uploaded the test result + val labId: String? + enum class Type(val raw: String) { @SerializedName("PCR") PCR("PCR"), 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 fd28cb44782cf18837dda25c487667d006a4c34a..0fc8c26a31eae2e78ddccfc1a0240b522eb0da17 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 @@ -46,6 +46,9 @@ data class PCRCoronaTest( @SerializedName("isDccDataSetCreated") override val isDccDataSetCreated: Boolean = false, + + @SerializedName("labId") + override val labId: String? = null, ) : CoronaTest { override val type: CoronaTest.Type diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRTestProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRTestProcessor.kt index 58bfe8a28d16fd55ab3d07efd251fd7f4458e288..ce35035b404f73f9d96b48bdbdde63b0166a9ea3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRTestProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRTestProcessor.kt @@ -130,6 +130,7 @@ class PCRTestProcessor @Inject constructor( testResult = testResult, testResultReceivedAt = determineReceivedDate(null, testResult), isDccConsentGiven = request.isDccConsentGiven, + labId = response.testResultResponse.labId ) } 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 6dfa9ad11f4179a805596e85dee86c89e613b510..fa0933bc2da91e216d9278ea1565ba9f41f2a839 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 @@ -70,6 +70,9 @@ data class RACoronaTest( override val isDccConsentGiven: Boolean = false, @SerializedName("isDccDataSetCreated") override val isDccDataSetCreated: Boolean = false, + + @SerializedName("labId") + override val labId: String? = null, ) : CoronaTest { override val type: CoronaTest.Type diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RATestProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RATestProcessor.kt index 075881497a8447e524264062768309f02867f60d..e119cba0f64720b70a39a218182be317879ac02e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RATestProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RATestProcessor.kt @@ -98,6 +98,7 @@ class RATestProcessor @Inject constructor( sampleCollectedAt = sampleCollectedAt, isDccSupportedByPoc = request.isDccSupportedByPoc, isDccConsentGiven = request.isDccConsentGiven, + labId = registrationData.testResultResponse.labId ) } @@ -137,7 +138,8 @@ class RATestProcessor @Inject constructor( Timber.tag(TAG).w("HTTP 400 error after 21 days, remapping to RAT_REDEEMED.") CoronaTestResultResponse( coronaTestResult = RAT_REDEEMED, - sampleCollectedAt = null + sampleCollectedAt = null, + labId = null ) } else { Timber.tag(TAG).v("Unexpected HTTP 400 error, rethrowing...") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/exception/TestCertificateServerException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/exception/TestCertificateServerException.kt index c5770640218d238c5fe2dcc65f58be6a63ab5518..647d26a192d6d5f8ab1ced05d3b633f62b0a9026 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/exception/TestCertificateServerException.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/exception/TestCertificateServerException.kt @@ -62,6 +62,10 @@ class TestCertificateServerException( "DCC Components failed with error 500: Signing server error", ERROR_MESSAGE_E2E_ERROR_CALL_HOTLINE ), + DCC_NOT_SUPPORTED_BY_LAB( + "DCC is not supported by the lab", + ERROR_MESSAGE_DCC_NOT_SUPPORTED_BY_LAB + ), DCC_COMP_NO_NETWORK( "DCC Test Certificate Components failed due to no network connection.", ERROR_MESSAGE_NO_NETWORK diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/items/CovidTestCertificatePendingCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/items/CovidTestCertificatePendingCard.kt index 9bcbf87936cc1dd3c6187082729203a7bf7e6282..0565eec8d468af286024b667bc8bf9e1b7dab2b8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/items/CovidTestCertificatePendingCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/items/CovidTestCertificatePendingCard.kt @@ -7,16 +7,16 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificate import de.rki.coronawarnapp.covidcertificate.test.ui.CertificatesAdapter import de.rki.coronawarnapp.databinding.CovidTestErrorCardBinding -import de.rki.coronawarnapp.util.TimeAndDateExtensions.toShortDayFormat +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat import de.rki.coronawarnapp.util.TimeAndDateExtensions.toShortTimeFormat import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer +import org.joda.time.DateTime class CovidTestCertificatePendingCard(parent: ViewGroup) : CertificatesAdapter.CertificatesItemVH<CovidTestCertificatePendingCard.Item, CovidTestErrorCardBinding>( R.layout.home_card_container_layout, parent ) { - override val viewBinding = lazy { CovidTestErrorCardBinding.inflate(layoutInflater, itemView.findViewById(R.id.card_container), true) } @@ -28,11 +28,10 @@ class CovidTestCertificatePendingCard(parent: ViewGroup) : val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item - val registeredAt = curItem.certificate.registeredAt testTime.text = context.getString( R.string.test_certificate_time, - registeredAt.toShortDayFormat(), - registeredAt.toShortTimeFormat(), + curItem.testDate.toDayFormat(), + curItem.testDate.toShortTimeFormat() ) retryButton.setOnClickListener { @@ -56,6 +55,7 @@ class CovidTestCertificatePendingCard(parent: ViewGroup) : } data class Item( + override val testDate: DateTime, val certificate: TestCertificate, val onRetryAction: (Item) -> Unit, val onDeleteAction: (Item) -> Unit diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateRepository.kt index ed383c7d1fa83bb75448b86cb217054caeea4445..b632cea276213c1ca43a3f1a7046921588cf1046 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateRepository.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.covidcertificate.test.core import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.coronatest.type.CoronaTest +import de.rki.coronawarnapp.covidcertificate.exception.TestCertificateServerException import de.rki.coronawarnapp.covidcertificate.test.core.qrcode.TestCertificateQRCodeExtractor import de.rki.coronawarnapp.covidcertificate.test.core.storage.PCRCertificateData import de.rki.coronawarnapp.covidcertificate.test.core.storage.RACertificateData @@ -87,7 +88,7 @@ class TestCertificateRepository @Inject constructor( /** * Will create a new test certificate entry. - * Automation via [de.rki.coronawarnapp.coronatest.type.common.TestCertificateRetrievalScheduler] will kick in. + * Automation via [de.rki.coronawarnapp.covidcertificate.test.core.execution.TestCertificateRetrievalScheduler] will kick in. * * Throws an exception if there already is a test certificate entry for this test * or this is not a valid test (no consent, not supported by PoC). @@ -114,11 +115,13 @@ class TestCertificateRepository @Inject constructor( identifier = identifier, registeredAt = test.registeredAt, registrationToken = test.registrationToken, + labId = test.labId ) CoronaTest.Type.RAPID_ANTIGEN -> RACertificateData( identifier = identifier, registeredAt = test.registeredAt, registrationToken = test.registrationToken, + labId = test.labId ) } val container = TestCertificateContainer( @@ -172,12 +175,42 @@ class TestCertificateRepository @Inject constructor( } } + // Not sure i really like this + internalData.updateBlocking { + Timber.tag(TAG).d("Checking for invalid lab id.") + + val refreshedCerts = values + .filter { workedOnIds.contains(it.identifier) } // Refresh targets + .filter { it.labId == null } // Targets of this step + .map { cert -> + Timber.tag(TAG).d("%s is missing a lab id returning exception", cert) + RefreshResult( + cert, + TestCertificateServerException( + TestCertificateServerException.ErrorCode.DCC_NOT_SUPPORTED_BY_LAB + ) + ) + } + + refreshedCerts.forEach { + refreshCallResults[it.certificateContainer.identifier] = it + } + + mutate { + refreshedCerts + .filter { it.error == null } + .map { it.certificateContainer } + .forEach { this[it.identifier] = it } + } + } + internalData.updateBlocking { Timber.tag(TAG).d("Checking for unregistered public keys.") val refreshedCerts = values .filter { workedOnIds.contains(it.identifier) } // Refresh targets .filter { !it.isPublicKeyRegistered } // Targets of this step + .filter { it.labId != null } .map { cert -> withContext(dispatcherProvider.IO) { try { @@ -208,6 +241,7 @@ class TestCertificateRepository @Inject constructor( val refreshedCerts = values .filter { workedOnIds.contains(it.identifier) } // Refresh targets .filter { it.isPublicKeyRegistered && it.isCertificateRetrievalPending } // Targets of this step + .filter { it.labId != null } .map { cert -> withContext(dispatcherProvider.IO) { try { @@ -262,6 +296,29 @@ class TestCertificateRepository @Inject constructor( internalData.updateBlocking { emptyMap() } } + suspend fun markCertificateAsSeenByUser(identifier: TestCertificateIdentifier) { + Timber.tag(TAG).d("markCertificateSeenByUser(identifier=%s)", identifier) + + internalData.updateBlocking { + val current = this[identifier] + if (current == null) { + Timber.tag(TAG).w("Can't mark %s as seen, it doesn't exist, racecondition?", identifier) + return@updateBlocking this + } + + if (current.isCertificateRetrievalPending) { + Timber.tag(TAG).w("Can't mark %s as seen, certificate has not been retrieved yet.", identifier) + return@updateBlocking this + } + + val updated = current.copy( + data = processor.updateSeenByUser(current.data, true) + ) + + mutate { this[identifier] = updated } + } + } + companion object { private val TAG = TestCertificateRepository::class.simpleName!! } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateWrapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateWrapper.kt index 2be76074c81765443b9c52167bf4d028cceadaab..0df7c8164ead0b32c0651a94157746965feeeab7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateWrapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/TestCertificateWrapper.kt @@ -17,7 +17,11 @@ data class TestCertificateWrapper( val registeredAt = container.registeredAt + val seenByUser = container.certificateSeenByUser + val testCertificate: TestCertificate? by lazy { container.toTestCertificate(valueSets) } + + val registrationToken = container.registrationToken } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/execution/TestCertificateRetrievalScheduler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/execution/TestCertificateRetrievalScheduler.kt index 2ad3ee6454b7fa8be7038f7c5b0fd0bb5654db43..02e1e616f3c5bce3fbb2826c0a19ca37381227df 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/execution/TestCertificateRetrievalScheduler.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/execution/TestCertificateRetrievalScheduler.kt @@ -14,7 +14,6 @@ import de.rki.coronawarnapp.util.device.ForegroundState import de.rki.coronawarnapp.util.flow.combine import de.rki.coronawarnapp.worker.BackgroundConstants import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -30,7 +29,7 @@ class TestCertificateRetrievalScheduler @Inject constructor( private val workManager: WorkManager, private val certificateRepo: TestCertificateRepository, private val testRepo: CoronaTestRepository, - private val foregroundState: ForegroundState, + foregroundState: ForegroundState, ) : ResultScheduler( workManager = workManager ) { @@ -69,21 +68,27 @@ class TestCertificateRetrievalScheduler @Inject constructor( .onEach { testsWithoutCert -> Timber.tag(TAG).d("State change: testsWithoutCert=$testsWithoutCert") testsWithoutCert.forEach { test -> - val cert = certificateRepo.requestCertificate(test) - Timber.tag(TAG).v("Certificate was created: %s", cert) - testRepo.markDccAsCreated(test.identifier, created = true) + try { + val cert = certificateRepo.requestCertificate(test) + Timber.tag(TAG).v("Certificate was created: %s", cert) + testRepo.markDccAsCreated(test.identifier, created = true) + } catch (e: Exception) { + Timber.tag(TAG).e(e, "Creation trigger failed.") + } } } - .catch { Timber.tag(TAG).e(it, "Creation trigger failed.") } .launchIn(appScope) // For each change to the set of existing certificates, check if we need to refresh/load data refreshTrigger .onEach { checkCerts -> - Timber.tag(TAG).d("State change: checkCerts=$checkCerts") - if (checkCerts) scheduleWorker() + try { + Timber.tag(TAG).d("State change: checkCerts=$checkCerts") + if (checkCerts) scheduleWorker() + } catch (e: Exception) { + Timber.tag(TAG).e(e, "Refresh trigger failed.") + } } - .catch { Timber.tag(TAG).e(it, "Refresh trigger failed.") } .launchIn(appScope) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/PCRCertificateData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/PCRCertificateData.kt index 118bf5bc112a3fc4399a49210542b1f6069c7256..d53a1d54b8bd82a026349e87f191443e7564fb4f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/PCRCertificateData.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/PCRCertificateData.kt @@ -37,6 +37,12 @@ data class PCRCertificateData internal constructor( @SerializedName("testCertificateQrCode") override val testCertificateQrCode: String? = null, + + @SerializedName("labId") + override val labId: String? = null, + + @SerializedName("certificateSeenByUser") + override val certificateSeenByUser: Boolean = false, ) : StoredTestCertificateData { // Otherwise GSON unsafes reflection to create this class, and sets the LAZY to null diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/RACertificateData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/RACertificateData.kt index dfcb82bad80c8f2000c505ce3929fb62dc6f1f64..fe10fabb6604501b72a8f73f6482095e834db3dc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/RACertificateData.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/RACertificateData.kt @@ -37,6 +37,12 @@ data class RACertificateData( @SerializedName("testCertificateQrCode") override val testCertificateQrCode: String? = null, + + @SerializedName("labId") + override val labId: String? = null, + + @SerializedName("certificateSeenByUser") + override val certificateSeenByUser: Boolean = false, ) : StoredTestCertificateData { // Otherwise GSON unsafes reflection to create this class, and sets the LAZY to null diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/StoredTestCertificateData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/StoredTestCertificateData.kt index 131c18e26608fb530f939f6cccc0cbb49128133d..9ce2281d176f35a686386dcea9203446f856edea 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/StoredTestCertificateData.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/StoredTestCertificateData.kt @@ -18,4 +18,6 @@ interface StoredTestCertificateData { val encryptedDataEncryptionkey: ByteString? val encryptedDccCose: ByteString? val testCertificateQrCode: String? + val labId: String? + val certificateSeenByUser: Boolean } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/TestCertificateProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/TestCertificateProcessor.kt index 181d3f8aee6eabbcf13aee81c29806dae2b39966..f929e89fbcf5ddb9d9d112c870f838ede70705d1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/TestCertificateProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/core/storage/TestCertificateProcessor.kt @@ -150,6 +150,17 @@ class TestCertificateProcessor @Inject constructor( } } + internal suspend fun updateSeenByUser( + data: StoredTestCertificateData, + seenByUser: Boolean, + ): StoredTestCertificateData { + Timber.tag(TAG).d("updateSeenByUser(data=%s, seenByUser=%b)", data, seenByUser) + return when (data.type) { + CoronaTest.Type.PCR -> (data as PCRCertificateData).copy(certificateSeenByUser = seenByUser) + CoronaTest.Type.RAPID_ANTIGEN -> (data as RACertificateData).copy(certificateSeenByUser = seenByUser) + } + } + companion object { private val TAG = TestCertificateProcessor::class.simpleName!! } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesViewModel.kt index 9a83e0d986a23848e5fae91f6c3f29c77fc5a5a2..d287cc6255231aab51e41ab159e634e977f4f30b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/CertificatesViewModel.kt @@ -1,9 +1,10 @@ package de.rki.coronawarnapp.covidcertificate.test.ui +import android.content.Context import androidx.lifecycle.LiveData -import androidx.lifecycle.asLiveData import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.contactdiary.util.getLocale import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository import de.rki.coronawarnapp.covidcertificate.person.ui.overview.items.CertificatesItem import de.rki.coronawarnapp.covidcertificate.vaccination.core.VaccinatedPerson @@ -14,17 +15,30 @@ import de.rki.coronawarnapp.covidcertificate.vaccination.ui.cards.HeaderInfoVacc import de.rki.coronawarnapp.covidcertificate.vaccination.ui.cards.ImmuneVaccinationCard import de.rki.coronawarnapp.covidcertificate.vaccination.ui.cards.NoCovidTestCertificatesCard import de.rki.coronawarnapp.covidcertificate.vaccination.ui.cards.VaccinationCard +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone +import de.rki.coronawarnapp.covidcertificate.valueset.ValueSetsRepository +import de.rki.coronawarnapp.util.di.AppContext 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.catch import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import timber.log.Timber class CertificatesViewModel @AssistedInject constructor( vaccinationRepository: VaccinationRepository, + valueSetsRepository: ValueSetsRepository, + @AppContext context: Context, private val vaccinationSettings: VaccinationSettings, private val testCertificateRepository: TestCertificateRepository ) : CWAViewModel() { + init { + valueSetsRepository.triggerUpdateValueSet(languageCode = context.getLocale()) + } + val events = SingleLiveEvent<CertificatesFragmentEvents>() val screenItems: LiveData<List<CertificatesItem>> = @@ -50,7 +64,8 @@ class CertificatesViewModel @AssistedInject constructor( add(NoCovidTestCertificatesCard.Item) } } - }.asLiveData() + } + .asLiveData2() private fun Set<VaccinatedPerson>.toCertificateItems(): List<CertificatesItem> = map { vaccinatedPerson -> when (vaccinatedPerson.getVaccinationStatus()) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidCertificateTestItem.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidCertificateTestItem.kt index 85ddac6dcb9ddf4fded87e296b67482f52b934c1..5e241cdd80a234e30c69954139a9cd9de46b9ae9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidCertificateTestItem.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidCertificateTestItem.kt @@ -1,10 +1,10 @@ package de.rki.coronawarnapp.covidcertificate.test.ui.cards import de.rki.coronawarnapp.covidcertificate.person.ui.overview.items.CertificatesItem -import org.joda.time.Instant +import org.joda.time.DateTime interface CovidCertificateTestItem : CertificatesItem { - val testDate: Instant + val testDate: DateTime override val stableId: Long get() = testDate.hashCode().toLong() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidTestCertificateCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidTestCertificateCard.kt index 791baaf5ab8d8625f9aa814583dd4d9975107aad..a5eb74819dfab46b7c0260eb167b255b6c6f9161 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidTestCertificateCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/cards/CovidTestCertificateCard.kt @@ -4,10 +4,10 @@ import android.view.ViewGroup import de.rki.coronawarnapp.R import de.rki.coronawarnapp.covidcertificate.test.ui.CertificatesAdapter import de.rki.coronawarnapp.databinding.CovidTestSuccessCardBinding -import de.rki.coronawarnapp.util.TimeAndDateExtensions.toShortDayFormat +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat import de.rki.coronawarnapp.util.TimeAndDateExtensions.toShortTimeFormat import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer -import org.joda.time.Instant +import org.joda.time.DateTime class CovidTestCertificateCard(parent: ViewGroup) : CertificatesAdapter.CertificatesItemVH<CovidTestCertificateCard.Item, CovidTestSuccessCardBinding>( @@ -26,8 +26,8 @@ class CovidTestCertificateCard(parent: ViewGroup) : val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item testTime.text = context.getString( R.string.test_certificate_time, - curItem.testDate.toShortDayFormat(), - curItem.testDate.toShortTimeFormat(), + item.testDate.toDayFormat(), + item.testDate.toShortTimeFormat() ) personName.text = curItem.testPerson @@ -36,7 +36,7 @@ class CovidTestCertificateCard(parent: ViewGroup) : } data class Item( - override val testDate: Instant, + override val testDate: DateTime, val testPerson: String, val onClickAction: (Item) -> Unit, ) : CovidCertificateTestItem, HasPayloadDiffer { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/details/CovidCertificateDetailsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/details/CovidCertificateDetailsFragment.kt index 670da564f0f1f45af487c4cdca23c5ab9a2fa4f9..671bcc4865983b5330bb63ea2fd54bde29ea30ce 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/details/CovidCertificateDetailsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/details/CovidCertificateDetailsFragment.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.View import android.widget.LinearLayout import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.view.isGone import androidx.fragment.app.Fragment import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController @@ -43,6 +44,8 @@ class CovidCertificateDetailsFragment : Fragment(R.layout.fragment_covid_certifi ) override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) { + qrCodeCard.title.isGone = true + qrCodeCard.subtitle.isGone = true appBarLayout.onOffsetChange { titleAlpha, subtitleAlpha -> title.alpha = titleAlpha subtitle.alpha = subtitleAlpha diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPerson.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPerson.kt index 942fe20bd7096d0eb936facb7a8e867abfc99c8c..9b751fc6ae731ab6ed5ebc1be8e89469830ebca3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPerson.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPerson.kt @@ -39,7 +39,7 @@ data class VaccinatedPerson( val daysToImmunity = getTimeUntilImmunity(nowUTC)?.standardDays ?: return Status.INCOMPLETE return when { - daysToImmunity <= 0 -> Status.IMMUNITY + daysToImmunity < 0 -> Status.IMMUNITY else -> Status.COMPLETE } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt index 9215b9f36c135c5fa7d96017bd02f82c05932c8c..8d75bf3d4be2413a59a57b76081bdb7152996d8c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.submission import de.rki.coronawarnapp.coronatest.CoronaTestRepository import de.rki.coronawarnapp.coronatest.TestRegistrationRequest +import de.rki.coronawarnapp.coronatest.errors.AlreadyRedeemedException import de.rki.coronawarnapp.coronatest.errors.CoronaTestNotFoundException import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.type.CoronaTest @@ -20,7 +21,6 @@ import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton -@Suppress("LongParameterList") @Singleton class SubmissionRepository @Inject constructor( @AppScope private val scope: CoroutineScope, @@ -92,6 +92,32 @@ class SubmissionRepository @Inject constructor( return coronaTest } + /** + * Attempt to register a new test, but if it is already redeemed, keep the previous test. + */ + suspend fun tryReplaceTest(request: TestRegistrationRequest): CoronaTest { + Timber.tag(TAG).v("tryReplaceTest(request=%s)", request) + + val coronaTest = coronaTestRepository.registerTest( + request = request, + preCondition = { currentTests -> + if (currentTests.any { it.type == request.type }) { + Timber.tag(TAG).i("Test type already exists, will try to replace.") + } + true + }, + postCondition = { newTest -> + if (newTest.isRedeemed) { + Timber.w("Replacement test was already redeemed, removing it, will not use.") + throw AlreadyRedeemedException(newTest) + } + true + } + ) + Timber.d("Registered test %s -> %s", request, coronaTest) + return coronaTest + } + suspend fun reset() { Timber.tag(TAG).v("reset()") tekHistoryStorage.clear() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessor.kt new file mode 100644 index 0000000000000000000000000000000000000000..817591e968fdb28dca40793b92f3ceabf977d489 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessor.kt @@ -0,0 +1,111 @@ +package de.rki.coronawarnapp.submission + +import android.content.Context +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.bugreporting.ui.toErrorDialogBuilder +import de.rki.coronawarnapp.coronatest.TestRegistrationRequest +import de.rki.coronawarnapp.coronatest.errors.AlreadyRedeemedException +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.exception.ExceptionCategory +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.exception.reporting.report +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import javax.inject.Inject + +class TestRegistrationStateProcessor @Inject constructor( + private val submissionRepository: SubmissionRepository, + private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector, +) { + + private val mutex = Mutex() + + sealed class State { + object Idle : State() + object Working : State() + data class TestRegistered(val test: CoronaTest) : State() + + data class Error(val exception: Exception) : State() { + fun getDialogBuilder(context: Context): MaterialAlertDialogBuilder { + val builder = MaterialAlertDialogBuilder(context).apply { + setCancelable(true) + } + + return when (exception) { + is AlreadyRedeemedException -> builder.apply { + setTitle(R.string.submission_error_dialog_web_tan_redeemed_title) + setMessage(R.string.submission_error_dialog_web_tan_redeemed_body) + setPositiveButton(android.R.string.ok) { _, _ -> + /* dismiss */ + } + } + is BadRequestException -> builder.apply { + setTitle(R.string.submission_qr_code_scan_invalid_dialog_headline) + setMessage(R.string.submission_qr_code_scan_invalid_dialog_body) + setPositiveButton(android.R.string.ok) { _, _ -> + /* dismiss */ + } + } + is CwaClientError, is CwaServerError -> builder.apply { + setTitle(R.string.submission_error_dialog_web_generic_error_title) + setMessage(R.string.submission_error_dialog_web_generic_network_error_body) + setPositiveButton(android.R.string.ok) { _, _ -> + /* dismiss */ + } + } + is CwaWebException -> builder.apply { + setTitle(R.string.submission_error_dialog_web_generic_error_title) + setMessage(R.string.submission_error_dialog_web_generic_error_body) + setPositiveButton(android.R.string.ok) { _, _ -> + /* dismiss */ + } + } + else -> exception.toErrorDialogBuilder(context) + } + } + } + } + + private val stateInternal = MutableStateFlow<State>(State.Idle) + val state: Flow<State> = stateInternal + + suspend fun startRegistration( + request: TestRegistrationRequest, + isSubmissionConsentGiven: Boolean, + allowReplacement: Boolean, + ): CoronaTest? = mutex.withLock { + return try { + stateInternal.value = State.Working + + val coronaTest = if (allowReplacement) { + submissionRepository.tryReplaceTest(request) + } else { + submissionRepository.registerTest(request) + } + + if (isSubmissionConsentGiven) { + submissionRepository.giveConsentToSubmission(type = coronaTest.type) + if (request is CoronaTestQRCode) { + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(request.type) + } + } + + stateInternal.value = State.TestRegistered(test = coronaTest) + coronaTest + } catch (err: Exception) { + stateInternal.value = State.Error(exception = err) + if (err !is CwaWebException) { + err.report(ExceptionCategory.INTERNAL) + } + null + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeFragment.kt index 790a2cbca56872f2a4ef0643fdde97b27979eda2..c9c72866e42bf329a6ba89256bc5477369c3fa51 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeFragment.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.submission.ui.testresults.negative import android.os.Bundle import android.view.View +import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.core.text.bold import androidx.core.text.buildSpannedString import androidx.core.view.isGone @@ -9,13 +10,14 @@ import androidx.fragment.app.Fragment import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionAntigenTestResultNegativeBinding import de.rki.coronawarnapp.util.DialogHelper +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toShortTimeFormat import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone import de.rki.coronawarnapp.util.di.AutoInject 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.cwaViewModels -import org.joda.time.format.DateTimeFormat import javax.inject.Inject class RATResultNegativeFragment : Fragment(R.layout.fragment_submission_antigen_test_result_negative), AutoInject { @@ -24,8 +26,6 @@ class RATResultNegativeFragment : Fragment(R.layout.fragment_submission_antigen_ private val binding: FragmentSubmissionAntigenTestResultNegativeBinding by viewBinding() - private val shortTime = DateTimeFormat.shortTime() - private val deleteRatTestConfirmationDialog by lazy { DialogHelper.DialogInstance( requireActivity(), @@ -58,38 +58,38 @@ class RATResultNegativeFragment : Fragment(R.layout.fragment_submission_antigen_ } private fun FragmentSubmissionAntigenTestResultNegativeBinding.bindView( - testAge: RATResultNegativeViewModel.TestAge + uiState: RATResultNegativeViewModel.UIState ) { - resultReceivedCounter.chronometer.text = testAge.ageText + resultReceivedCounter.chronometer.text = uiState.ageText val patientName = getString( R.string.submission_test_result_antigen_patient_name_placeholder, - testAge.test.firstName ?: "", - testAge.test.lastName ?: "" + uiState.test.firstName ?: "", + uiState.test.lastName ?: "" ) rapidTestCardPatientInfo.text = buildSpannedString { bold { if (patientName.isNotBlank()) append(patientName) } - testAge.test.dateOfBirth?.let { + uiState.test.dateOfBirth?.let { val birthDate = getString( R.string.submission_test_result_antigen_patient_birth_date_placeholder, - it.toString(DATE_FORMAT) + it.toDayFormat() ) if (this.isNotBlank()) append(", ") append(birthDate) } } - val localTime = testAge.test.testTakenAt.toUserTimeZone() + val localTime = uiState.test.testTakenAt.toUserTimeZone() resultReceivedTimeAndDate.text = getString( R.string.coronatest_negative_antigen_result_time_date_placeholder, - localTime.toString(DATE_FORMAT), - localTime.toString(shortTime) + localTime.toDayFormat(), + localTime.toShortTimeFormat() ) - val isAnonymousTest = with(testAge.test) { + val isAnonymousTest = with(uiState.test) { firstName == null && lastName == null && dateOfBirth == null } @@ -108,9 +108,32 @@ class RATResultNegativeFragment : Fragment(R.layout.fragment_submission_antigen_ negativeTestProofBody.text = getString(proofBodyString) negativeTestProofAdditionalInformation.isGone = isAnonymousTest - } - companion object { - private const val DATE_FORMAT = "dd.MM.yyyy" + when (uiState.certificateState) { + RATResultNegativeViewModel.CertificateState.NOT_REQUESTED -> { + coronatestNegativeAntigenResultThirdInfo.setIsFinal(true) + coronatestNegativeAntigenResultFourthInfo.isGone = true + } + RATResultNegativeViewModel.CertificateState.PENDING -> { + coronatestNegativeAntigenResultThirdInfo.setIsFinal(false) + coronatestNegativeAntigenResultFourthInfo.isGone = false + coronatestNegativeAntigenResultFourthInfo.setEntryText( + getText(R.string.submission_test_result_pending_steps_test_certificate_not_available_yet_body) + ) + coronatestNegativeAntigenResultFourthInfo.setIcon( + getDrawable(requireContext(), R.drawable.ic_result_pending_certificate_info) + ) + } + RATResultNegativeViewModel.CertificateState.AVAILABLE -> { + coronatestNegativeAntigenResultThirdInfo.setIsFinal(false) + coronatestNegativeAntigenResultFourthInfo.isGone = false + coronatestNegativeAntigenResultFourthInfo.setEntryText( + getText(R.string.coronatest_negative_result_certificate_info_body) + ) + coronatestNegativeAntigenResultFourthInfo.setIcon( + getDrawable(requireContext(), R.drawable.ic_qr_code_illustration) + ) + } + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeViewModel.kt index 70990f8e12ca4f2f5b95082d58f6fa46701c4a23..763476722f855d1523fe01a5f1a9b26047e2b443 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeViewModel.kt @@ -6,6 +6,8 @@ import dagger.assisted.AssistedInject import de.rki.coronawarnapp.coronatest.CoronaTestRepository import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest +import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository +import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateWrapper import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.submission.SubmissionRepository @@ -25,22 +27,28 @@ class RATResultNegativeViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val timeStamper: TimeStamper, private val submissionRepository: SubmissionRepository, - coronaTestRepository: CoronaTestRepository + coronaTestRepository: CoronaTestRepository, + certificateRepository: TestCertificateRepository ) : CWAViewModel(dispatcherProvider) { val events = SingleLiveEvent<RATResultNegativeNavigation>() val testAge = combine( intervalFlow(1), - coronaTestRepository.coronaTests - ) { _, tests -> + coronaTestRepository.coronaTests, + certificateRepository.certificates + ) { _, tests, certs -> val rapidTest = tests.firstOrNull { it.type == CoronaTest.Type.RAPID_ANTIGEN } - rapidTest?.testAge() + val certificate = certs.firstOrNull { + it.registrationToken == rapidTest?.registrationToken + } + + rapidTest?.uiState(certificate) }.asLiveData(context = dispatcherProvider.Default) - private fun CoronaTest.testAge(): TestAge? { + private fun CoronaTest.uiState(certificate: TestCertificateWrapper?): UIState? { if (this !is RACoronaTest) { Timber.d("Rapid test is missing") return null @@ -50,7 +58,17 @@ class RATResultNegativeViewModel @AssistedInject constructor( val age = nowUTC.millis - testTakenAt.millis val ageText = formatter.print(Duration(age).toPeriod()) - return TestAge(test = this, ageText) + val certificateState: CertificateState = when (certificate?.isCertificateRetrievalPending) { + true -> CertificateState.PENDING + false -> CertificateState.AVAILABLE + else -> CertificateState.NOT_REQUESTED + } + + return UIState( + test = this, + ageText = ageText, + certificateState = certificateState + ) } fun onDeleteTestConfirmed() { @@ -75,9 +93,16 @@ class RATResultNegativeViewModel @AssistedInject constructor( @AssistedFactory interface Factory : SimpleCWAViewModelFactory<RATResultNegativeViewModel> - data class TestAge( + enum class CertificateState { + NOT_REQUESTED, + PENDING, + AVAILABLE + } + + data class UIState( val test: RACoronaTest, val ageText: String, + val certificateState: CertificateState ) companion object { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt index 0313c13a83921b33d5a9b037821f821d2fbe771d..cf227d6ca7bc01a71f2f79b93020b1795e5e071a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt @@ -26,13 +26,13 @@ import de.rki.coronawarnapp.ui.setupWithNavController2 import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentFragment import de.rki.coronawarnapp.util.AppShortcuts import de.rki.coronawarnapp.util.CWADebug -import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.device.PowerManagement import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper.Companion.getShortcutExtra import de.rki.coronawarnapp.util.ui.findNavController import de.rki.coronawarnapp.util.ui.findNestedGraph +import de.rki.coronawarnapp.util.ui.updateCountBadge import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModels import org.joda.time.LocalDate @@ -103,16 +103,11 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector { } vm.activeCheckIns.observe(this) { count -> - val targetId = R.id.trace_location_attendee_nav_graph - binding.mainBottomNavigation.apply { - if (count > 0) { - val badge = getOrCreateBadge(targetId) - badge.number = count - badge.badgeTextColor = getColorCompat(android.R.color.white) - } else { - removeBadge(targetId) - } - } + binding.mainBottomNavigation.updateCountBadge(R.id.trace_location_attendee_nav_graph, count) + } + + vm.newCertificates.observe(this) { count -> + binding.mainBottomNavigation.updateCountBadge(R.id.green_certificate_graph, count) } if (savedInstanceState == null) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt index 6fe194da45ba54ed22a245032879f0a93aeb8556..6890d05c4e7cbfbb73d9a5dfdcded9e7b0238435 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt @@ -2,10 +2,10 @@ package de.rki.coronawarnapp.ui.main import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.asLiveData import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings +import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository import de.rki.coronawarnapp.covidcertificate.vaccination.core.VaccinationSettings import de.rki.coronawarnapp.environment.EnvironmentSetup import de.rki.coronawarnapp.playbook.BackgroundNoise @@ -31,7 +31,8 @@ class MainActivityViewModel @AssistedInject constructor( private val onboardingSettings: OnboardingSettings, private val traceLocationSettings: TraceLocationSettings, private val vaccinationSettings: VaccinationSettings, - checkInRepository: CheckInRepository + checkInRepository: CheckInRepository, + testCertificateRepository: TestCertificateRepository, ) : CWAViewModel( dispatcherProvider = dispatcherProvider ) { @@ -49,7 +50,13 @@ class MainActivityViewModel @AssistedInject constructor( val activeCheckIns = checkInRepository.checkInsWithinRetention .map { checkins -> checkins.filter { !it.completed }.size } - .asLiveData(context = dispatcherProvider.Default) + .asLiveData2() + + val newCertificates = testCertificateRepository.certificates + .map { certs -> + certs.filter { !it.seenByUser && !it.isCertificateRetrievalPending }.size + } + .asLiveData2() init { if (CWADebug.isDeviceForTestersBuild) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragment.kt index 2635a01a5d5cdcfec0d8b5f48f392d1616777c46..b3e85a78717130c063c3fb68ad51207cdfcd56aa 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateFragment.kt @@ -1,30 +1,22 @@ package de.rki.coronawarnapp.ui.submission.covidcertificate import android.os.Bundle -import androidx.fragment.app.Fragment import android.view.View +import androidx.activity.addCallback import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.Fragment 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.coronatest.type.CoronaTest 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.submission.TestRegistrationStateProcessor.State import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.doNavigate @@ -33,7 +25,6 @@ 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 { @@ -43,7 +34,7 @@ class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid factoryProducer = { viewModelFactory }, constructorCall = { factory, _ -> factory as RequestCovidCertificateViewModel.Factory - factory.create(args.coronaTestQrCode, args.coronaTestConsent, args.deleteOldTest) + factory.create(args.testRegistrationRequest, args.coronaTestConsent, args.deleteOldTest) } ) private val binding by viewBinding<FragmentRequestCovidCertificateBinding>() @@ -51,7 +42,7 @@ class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) { - val isPCR = args.coronaTestQrCode is CoronaTestQRCode.PCR + val isPCR = args.testRegistrationRequest.type == CoronaTest.Type.PCR birthDateGroup.isVisible = isPCR privacyCard.pcrExtraBullet.isVisible = isPCR @@ -59,7 +50,9 @@ class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid if (text.toString().isEmpty()) viewModel.birthDateChanged(null) } + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { showCloseDialog() } toolbar.setNavigationOnClickListener { showCloseDialog() } + agreeButton.setOnClickListener { viewModel.onAgreeGC() } disagreeButton.setOnClickListener { viewModel.onDisagreeGC() } dateInputEdit.setOnClickListener { openDatePicker() } @@ -68,74 +61,64 @@ class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid viewModel.events.observe(viewLifecycleOwner) { event -> when (event) { Back -> popBackStack() - ToDispatcherScreen -> doNavigate( + + ToDispatcherScreen -> RequestCovidCertificateFragmentDirections .actionRequestCovidCertificateFragmentToDispatcherFragment() - ) - ToHomeScreen -> doNavigate( + .run { doNavigate(this) } + + ToHomeScreen -> RequestCovidCertificateFragmentDirections.actionRequestCovidCertificateFragmentToHomeFragment() - ) + .run { doNavigate(this) } } } 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 + private fun handleRegistrationState(state: State) { + val isWorking = state is State.Working + binding.apply { + if (isWorking) progressBar.show() else progressBar.hide() + agreeButton.isInvisible = isWorking + disagreeButton.isInvisible = isWorking + } + when (state) { + State.Idle, + State.Working -> { + // Handled above } - else -> binding.apply { - progressBar.hide() - agreeButton.isInvisible = false - disagreeButton.isInvisible = false + is State.Error -> { + state.getDialogBuilder(requireContext()).apply { + if (state.exception is BadRequestException) { + setPositiveButton(R.string.submission_qr_code_scan_invalid_dialog_button_positive) { _, _ -> + viewModel.navigateBack() + } + setNegativeButton(R.string.submission_qr_code_scan_invalid_dialog_button_negative) { _, _ -> + viewModel.navigateToDispatcherScreen() + } + setOnCancelListener { viewModel.navigateToDispatcherScreen() } + } else { + setOnDismissListener { viewModel.navigateToDispatcherScreen() } + } + }.show() } - } - - 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 + is State.TestRegistered -> when { + state.test.isPositive -> + NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = state.test.type) + .run { doNavigate(this) } + + else -> + NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type) + .run { doNavigate(this) } } - }.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() } + .setNegativeButton(R.string.request_gc_dialog_negative_button) { _, _ -> } .setPositiveButton(R.string.request_gc_dialog_positive_button) { _, _ -> viewModel.navigateToHomeScreen() } .create() .show() @@ -151,39 +134,4 @@ class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid } } .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/covidcertificate/RequestCovidCertificateViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModel.kt index ff0e272e71fbba2a1b6de47be1defa317be6f66c..cdbf4b7dcaac3a95a57f758e388f3e8be7102b7c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModel.kt @@ -5,33 +5,22 @@ 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.TestRegistrationRequest 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.submission.TestRegistrationStateProcessor 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 private val testRegistrationRequest: TestRegistrationRequest, @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, + private val registrationStateProcessor: TestRegistrationStateProcessor, ) : CWAViewModel() { - // Test registration LiveData - val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning - val registrationState = qrCodeRegistrationStateProcessor.registrationState - val registrationError = qrCodeRegistrationStateProcessor.registrationError - val removalError = SingleLiveEvent<Throwable>() + val registrationState = registrationStateProcessor.state.asLiveData2() private val birthDateData = MutableLiveData<LocalDate>(null) val birthDate: LiveData<LocalDate> = birthDateData @@ -58,38 +47,26 @@ class RequestCovidCertificateViewModel @AssistedInject constructor( } 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( + val consentedQrCode = when (testRegistrationRequest) { + is CoronaTestQRCode.PCR -> testRegistrationRequest.copy( dateOfBirth = birthDateData.value, isDccConsentGiven = dccConsent ) - is CoronaTestQRCode.RapidAntigen -> coronaTestQrCode.copy(isDccConsentGiven = dccConsent) + is CoronaTestQRCode.RapidAntigen -> testRegistrationRequest.copy(isDccConsentGiven = dccConsent) + else -> testRegistrationRequest } - 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) - } + registrationStateProcessor.startRegistration( + request = consentedQrCode, + isSubmissionConsentGiven = coronaTestConsent, + allowReplacement = deleteOldTest + ) } @AssistedFactory interface Factory : CWAViewModelFactory<RequestCovidCertificateViewModel> { fun create( - coronaTestQrCode: CoronaTestQRCode, + testRegistrationRequest: TestRegistrationRequest, @Assisted("coronaTestConsent") coronaTestConsent: Boolean, @Assisted("deleteOldTest") deleteOldTest: Boolean ): RequestCovidCertificateViewModel diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt index 623f30a36ff938ee643c382133385a2ebe348b8c..f2b01f53a3c88b9ad1c6fb223465843dc8910274 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt @@ -6,17 +6,12 @@ import android.view.accessibility.AccessibilityEvent import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs +import de.rki.coronawarnapp.NavGraphDirections import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.bugreporting.ui.toErrorDialogBuilder -import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.databinding.FragmentSubmissionDeletionWarningBinding -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.submission.TestRegistrationStateProcessor.State import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents -import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.ui.doNavigate @@ -38,7 +33,7 @@ class SubmissionDeletionWarningFragment : Fragment(R.layout.fragment_submission_ factoryProducer = { viewModelFactory }, constructorCall = { factory, _ -> factory as SubmissionDeletionWarningViewModel.Factory - factory.create(args.coronaTestQrCode, args.coronaTestTan, args.isConsentGiven) + factory.create(args.testRegistrationRequest, args.isConsentGiven) } ) private val binding: FragmentSubmissionDeletionWarningBinding by viewBinding() @@ -65,57 +60,38 @@ class SubmissionDeletionWarningFragment : Fragment(R.layout.fragment_submission_ } viewModel.registrationState.observe2(this) { state -> - binding.submissionQrCodeScanSpinner.isVisible = state.isFetching - binding.continueButton.isVisible = !state.isFetching && state.coronaTest == null - } - viewModel.registrationError.observe2(this) { - showErrorDialog(it) - doNavigate( - SubmissionDeletionWarningFragmentDirections - .actionSubmissionDeletionWarningFragmentToSubmissionDispatcherFragment() - ) - } + val isWorking = state is State.Working + binding.apply { + submissionQrCodeScanSpinner.isVisible = isWorking + continueButton.isVisible = !isWorking + } + when (state) { + State.Idle, + State.Working -> { + // Handled above + } + is State.Error -> { + state.getDialogBuilder(requireContext()).show() + SubmissionDeletionWarningFragmentDirections + .actionSubmissionDeletionWarningFragmentToSubmissionDispatcherFragment() + .run { doNavigate(this) } + } + is State.TestRegistered -> when { + state.test.isPositive -> + NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = state.test.type) + .run { doNavigate(this) } - viewModel.routeToScreen.observe2(this) { - Timber.d("Navigating to %s", it) - doNavigate(it) - } - } + else -> + NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type) + .run { doNavigate(this) } + } + } - private fun showErrorDialog(exception: Throwable) = when (exception) { - is InvalidQRCodeException -> DialogHelper.DialogInstance( - context = requireActivity(), - title = R.string.submission_error_dialog_web_tan_redeemed_title, - message = R.string.submission_error_dialog_web_tan_redeemed_body, - cancelable = true, - positiveButton = R.string.submission_error_dialog_web_tan_redeemed_button_positive, - positiveButtonFunction = { /* dismiss */ }, - ).run { DialogHelper.showDialog(this) } - is BadRequestException -> DialogHelper.DialogInstance( - context = requireActivity(), - title = R.string.submission_qr_code_scan_invalid_dialog_headline, - message = R.string.submission_qr_code_scan_invalid_dialog_body, - cancelable = true, - positiveButton = R.string.submission_qr_code_scan_invalid_dialog_button_positive, - positiveButtonFunction = { /* dismiss */ }, - ).run { DialogHelper.showDialog(this) } - is CwaClientError, is CwaServerError -> DialogHelper.DialogInstance( - context = requireActivity(), - title = R.string.submission_error_dialog_web_generic_error_title, - message = R.string.submission_error_dialog_web_generic_network_error_body, - cancelable = true, - positiveButton = R.string.submission_error_dialog_web_generic_error_button_positive, - positiveButtonFunction = { /* dismiss */ }, - ).run { DialogHelper.showDialog(this) } - is CwaWebException -> DialogHelper.DialogInstance( - context = requireActivity(), - title = R.string.submission_error_dialog_web_generic_error_title, - message = R.string.submission_error_dialog_web_generic_error_body, - cancelable = true, - positiveButton = R.string.submission_error_dialog_web_generic_error_button_positive, - positiveButtonFunction = { /* dismiss */ }, - ).run { DialogHelper.showDialog(this) } - else -> exception.toErrorDialogBuilder(requireContext()).show() + viewModel.routeToScreen.observe2(this) { + Timber.d("Navigating to %s", it) + doNavigate(it) + } + } } override fun onResume() { 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 5db57987c07498586d0921592400b48e558619e8..7f479939cbfc28be5c9039e69e2abaf997bc222d 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 @@ -1,153 +1,81 @@ package de.rki.coronawarnapp.ui.submission.deletionwarning -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.navigation.NavDirections 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.coronatest.qrcode.InvalidQRCodeException +import de.rki.coronawarnapp.coronatest.TestRegistrationRequest import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor 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 timber.log.Timber class SubmissionDeletionWarningViewModel @AssistedInject constructor( - @Assisted private val coronaTestQrCode: CoronaTestQRCode?, - @Assisted private val coronaTestQrTan: CoronaTestTAN?, + @Assisted private val testRegistrationRequest: TestRegistrationRequest, @Assisted private val isConsentGiven: Boolean, - private val submissionRepository: SubmissionRepository, - private val coronaTestRepository: CoronaTestRepository, + private val registrationStateProcessor: TestRegistrationStateProcessor, ) : CWAViewModel() { val routeToScreen = SingleLiveEvent<NavDirections>() - private val mutableRegistrationState = MutableLiveData(RegistrationState()) - val registrationState: LiveData<RegistrationState> = mutableRegistrationState - val registrationError = SingleLiveEvent<Throwable>() + val registrationState = registrationStateProcessor.state.asLiveData2() - private fun getRegistrationType(): RegistrationType = if (coronaTestQrCode != null) { - RegistrationType.QR - } else { - RegistrationType.TAN - } - - // If there is no qrCode, it must be a TAN, and TANs are always PCR - internal fun getTestType(): CoronaTest.Type = coronaTestQrCode?.type ?: CoronaTest.Type.PCR + internal fun getTestType(): CoronaTest.Type = testRegistrationRequest.type fun deleteExistingAndRegisterNewTest() = launch { - when { - coronaTestQrTan != null -> deleteExistingAndRegisterNewTestWitTAN() - else -> deleteExistingAndRegisterNewTestWithQrCode() - } - } - - private suspend fun deleteExistingAndRegisterNewTestWithQrCode() = try { - requireNotNull(coronaTestQrCode) { "QR Code was unavailable" } - if (coronaTestQrCode.isDccSupportedByPoc) { + if (testRegistrationRequest.isDccSupportedByPoc) { SubmissionDeletionWarningFragmentDirections .actionSubmissionDeletionWarningFragmentToRequestCovidCertificateFragment( - coronaTestQrCode = coronaTestQrCode, + testRegistrationRequest = testRegistrationRequest, coronaTestConsent = isConsentGiven, deleteOldTest = true ).run { routeToScreen.postValue(this) } } else { - removeAndRegisterNew(coronaTestQrCode) + removeAndRegisterNew(testRegistrationRequest) } - } 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) + private suspend fun removeAndRegisterNew(request: TestRegistrationRequest) { + val newTest = registrationStateProcessor.startRegistration( + request = request, + isSubmissionConsentGiven = isConsentGiven, + allowReplacement = true + ) - 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("Test is already redeemed") + if (newTest == null) { + Timber.w("Test registration failed.") + return + } else { + Timber.d("Continuing with our new CoronaTest: %s", newTest) } - if (isConsentGiven) submissionRepository.giveConsentToSubmission(type = coronaTestQrCode.type) - - continueWithNewTest(coronaTest) - mutableRegistrationState.postValue(RegistrationState(coronaTest = coronaTest)) - } - - private suspend fun deleteExistingAndRegisterNewTestWitTAN() = try { - requireNotNull(coronaTestQrTan) { "TAN was unavailable" } - - submissionRepository.testForType(CoronaTest.Type.PCR).first()?.let { - coronaTestRepository.removeTest(it.identifier) - } ?: Timber.w("Test we will replace with TAN was already removed?") - - mutableRegistrationState.postValue(RegistrationState(isFetching = true)) - - val coronaTest = submissionRepository.registerTest(coronaTestQrTan) - continueWithNewTest(coronaTest) - - mutableRegistrationState.postValue(RegistrationState(coronaTest = coronaTest)) - } catch (e: Exception) { - Timber.e(e, "Error during test registration via TAN") - mutableRegistrationState.postValue(RegistrationState(isFetching = false)) - registrationError.postValue(e) - } - - fun onCancelButtonClick() { - SubmissionDeletionWarningFragmentDirections - .actionSubmissionDeletionWarningFragmentToSubmissionConsentFragment() - .run { routeToScreen.postValue(this) } - } + when (request) { + is CoronaTestTAN -> + SubmissionDeletionWarningFragmentDirections + .actionSubmissionDeletionFragmentToSubmissionTestResultNoConsentFragment(newTest.type) - 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) { + else -> if (newTest.isPositive) { SubmissionDeletionWarningFragmentDirections - .actionSubmissionDeletionWarningFragmentToSubmissionTestResultAvailableFragment(testType) + .actionSubmissionDeletionWarningFragmentToSubmissionTestResultAvailableFragment(newTest.type) } else { SubmissionDeletionWarningFragmentDirections - .actionSubmissionDeletionWarningFragmentToSubmissionTestResultPendingFragment(testType) + .actionSubmissionDeletionWarningFragmentToSubmissionTestResultPendingFragment(newTest.type) } - - RegistrationType.TAN -> - SubmissionDeletionWarningFragmentDirections - .actionSubmissionDeletionFragmentToSubmissionTestResultNoConsentFragment(getTestType()) }.run { routeToScreen.postValue(this) } } - data class RegistrationState( - val isFetching: Boolean = false, - val coronaTest: CoronaTest? = null, - ) - - sealed class RegistrationType { - object TAN : RegistrationType() - object QR : RegistrationType() + fun onCancelButtonClick() { + SubmissionDeletionWarningFragmentDirections + .actionSubmissionDeletionWarningFragmentToSubmissionConsentFragment() + .run { routeToScreen.postValue(this) } } @AssistedFactory interface Factory : CWAViewModelFactory<SubmissionDeletionWarningViewModel> { fun create( - coronaTestQrCode: CoronaTestQRCode?, - coronaTestTan: CoronaTestTAN?, + testRegistrationRequest: TestRegistrationRequest, isConsentGiven: Boolean ): SubmissionDeletionWarningViewModel } 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 deleted file mode 100644 index 19ec160f5228e0ec0336abb76c5afdf7f7c9a180..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt +++ /dev/null @@ -1,60 +0,0 @@ -package de.rki.coronawarnapp.ui.submission.qrcode - -import androidx.lifecycle.MutableLiveData -import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode -import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException -import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.exception.ExceptionCategory -import de.rki.coronawarnapp.exception.http.CwaWebException -import de.rki.coronawarnapp.exception.reporting.report -import de.rki.coronawarnapp.submission.SubmissionRepository -import de.rki.coronawarnapp.ui.submission.ApiRequestState -import de.rki.coronawarnapp.util.ui.SingleLiveEvent -import timber.log.Timber -import javax.inject.Inject - -class QrCodeRegistrationStateProcessor @Inject constructor( - private val submissionRepository: SubmissionRepository -) { - - data class RegistrationState( - val apiRequestState: ApiRequestState, - val test: CoronaTest? = null - ) - - val showRedeemedTokenWarning = SingleLiveEvent<Unit>() - val registrationState = MutableLiveData(RegistrationState(ApiRequestState.IDLE)) - val registrationError = SingleLiveEvent<CwaWebException>() - - suspend fun startQrCodeRegistration(coronaTestQRCode: CoronaTestQRCode, isConsentGiven: Boolean) = - try { - registrationState.postValue(RegistrationState(ApiRequestState.STARTED)) - val coronaTest = submissionRepository.registerTest(coronaTestQRCode) - if (isConsentGiven) { - submissionRepository.giveConsentToSubmission(type = coronaTestQRCode.type) - } - checkTestResult(coronaTestQRCode, coronaTest) - registrationState.postValue(RegistrationState(ApiRequestState.SUCCESS, coronaTest)) - } catch (err: CwaWebException) { - registrationState.postValue(RegistrationState(ApiRequestState.FAILED)) - registrationError.postValue(err) - } catch (err: InvalidQRCodeException) { - registrationState.postValue(RegistrationState(ApiRequestState.FAILED)) - Timber.d("deregisterTestFromDevice()") - submissionRepository.removeTestFromDevice(type = coronaTestQRCode.type) - showRedeemedTokenWarning.postValue(Unit) - } catch (err: Exception) { - registrationState.postValue(RegistrationState(ApiRequestState.FAILED)) - err.report(ExceptionCategory.INTERNAL) - } - - private fun checkTestResult(request: CoronaTestQRCode, test: CoronaTest) { - if (test.isRedeemed) { - throw InvalidQRCodeException("CoronaTestResult already redeemed ${request.registrationIdentifier}") - } - } - - enum class ValidationState { - STARTED, INVALID, SUCCESS - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt index e5fd022f5390e06bdbdb6a4a1c91e76dcb3429e4..6c5384b49dc6b328b67a05eb293109ce9f05cbdd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt @@ -10,14 +10,8 @@ import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs import de.rki.coronawarnapp.NavGraphDirections import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.databinding.FragmentSubmissionConsentBinding -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.ValidationState +import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor.State import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject @@ -61,71 +55,54 @@ class SubmissionConsentFragment : Fragment(R.layout.fragment_submission_consent) requireActivity(), REQUEST_USER_RESOLUTION ) - is SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode -> { - doNavigate( - NavGraphDirections - .actionToSubmissionDeletionWarningFragment( - it.consentGiven, - it.coronaTestQRCode - ) + is SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode -> doNavigate( + NavGraphDirections.actionToSubmissionDeletionWarningFragment( + testRegistrationRequest = it.coronaTestQRCode, + isConsentGiven = it.consentGiven, ) - } + ) + is SubmissionNavigationEvents.NavigateToRequestDccFragment -> doNavigate( + NavGraphDirections.actionRequestCovidCertificateFragment( + testRegistrationRequest = it.coronaTestQRCode, + coronaTestConsent = it.consentGiven + ) + ) } } viewModel.countries.observe2(this) { binding.countries = it } - 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) - popBackStack() - } - - viewModel.qrCodeValidationState.observe2(this) { - if (ValidationState.INVALID == it) { - showInvalidQrCodeDialog() - } + viewModel.qrCodeError.observe2(this) { + showInvalidQrCodeDialog() } viewModel.registrationState.observe2(this) { state -> - binding.progressSpinner.isVisible = state.apiRequestState == ApiRequestState.STARTED - binding.submissionConsentButton.isEnabled = when (state.apiRequestState) { - ApiRequestState.STARTED -> false - else -> true + val isWorking = state is State.Working + binding.apply { + progressSpinner.isVisible = isWorking + submissionConsentButton.isEnabled = !isWorking } - - if (ApiRequestState.SUCCESS == state.apiRequestState) { - when (state.test?.type) { - CoronaTest.Type.PCR -> throw UnsupportedOperationException() - CoronaTest.Type.RAPID_ANTIGEN -> { - when { - state.test.isPositive -> - doNavigate( - NavGraphDirections.actionToSubmissionTestResultAvailableFragment( - CoronaTest.Type.RAPID_ANTIGEN - ) - ) - else -> doNavigate( - NavGraphDirections.actionSubmissionTestResultPendingFragment( - testType = CoronaTest.Type.RAPID_ANTIGEN - ) - ) - } - } + when (state) { + State.Idle, + State.Working -> { + // Handled above + } + is State.Error -> { + state.getDialogBuilder(requireContext()).show() + popBackStack() + } + is State.TestRegistered -> when { + state.test.isPositive -> + NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = state.test.type) + .run { doNavigate(this) } + + else -> + NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type) + .run { doNavigate(this) } } } } - - viewModel.registrationError.observe2(this) { - DialogHelper.showDialog(buildErrorDialog(it)) - } } override fun onResume() { @@ -149,49 +126,15 @@ class SubmissionConsentFragment : Fragment(R.layout.fragment_submission_consent) R.string.submission_qr_code_scan_invalid_dialog_button_negative, true, positiveButtonFunction = {}, - negativeButtonFunction = ::navigateHome + negativeButtonFunction = { + popBackStack() + Unit + } ) DialogHelper.showDialog(invalidScanDialogInstance) } - 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, - { }, - ::navigateHome - ) - 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, - ::navigateHome - ) - 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, - ::navigateHome - ) - } - } - - private fun navigateHome() { - popBackStack() - } - companion object { private const val REQUEST_USER_RESOLUTION = 3000 fun canHandle(rootUri: String): Boolean = rootUri.startsWith("https://s.coronawarn.app") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt index f6876b62aeebd0f5662569e1a26073a0fd7e8e66..643028c08ebfdd80dd2992c326020d243adc3031 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt @@ -9,7 +9,7 @@ import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException import de.rki.coronawarnapp.nearby.modules.tekhistory.TEKHistoryProvider import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository import de.rki.coronawarnapp.submission.SubmissionRepository -import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor +import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent @@ -22,17 +22,14 @@ class SubmissionConsentViewModel @AssistedInject constructor( interoperabilityRepository: InteroperabilityRepository, dispatcherProvider: DispatcherProvider, private val tekHistoryProvider: TEKHistoryProvider, - private val qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor, + private val registrationStateProcessor: TestRegistrationStateProcessor, private val submissionRepository: SubmissionRepository, private val qrCodeValidator: CoronaTestQrCodeValidator ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val routeToScreen = SingleLiveEvent<SubmissionNavigationEvents>() - val qrCodeValidationState = SingleLiveEvent<QrCodeRegistrationStateProcessor.ValidationState>() - - val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning - val registrationState = qrCodeRegistrationStateProcessor.registrationState - val registrationError = qrCodeRegistrationStateProcessor.registrationError + val qrCodeError = SingleLiveEvent<Exception>() + val registrationState = registrationStateProcessor.state.asLiveData2() val countries = interoperabilityRepository.countryList .asLiveData(context = dispatcherProvider.Default) @@ -76,24 +73,36 @@ class SubmissionConsentViewModel @AssistedInject constructor( } private suspend fun validateAndRegister(qrCodeString: String) { - try { - val coronaTestQRCode = qrCodeValidator.validate(qrCodeString) - qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.SUCCESS) - val coronaTest = submissionRepository.testForType(coronaTestQRCode.type).first() + val coronaTestQRCode = try { + qrCodeValidator.validate(qrCodeString) + } catch (err: InvalidQRCodeException) { + Timber.i(err, "Failed to validate QRCode") + qrCodeError.postValue(err) + return + } + + val coronaTest = submissionRepository.testForType(coronaTestQRCode.type).first() - if (coronaTest != null) { - routeToScreen.postValue( - SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode( - coronaTestQRCode, - consentGiven = true - ) + when { + coronaTest != null -> { + SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode( + coronaTestQRCode, + consentGiven = true + ).run { routeToScreen.postValue(this) } + } + coronaTestQRCode.isDccSupportedByPoc && !coronaTestQRCode.isDccConsentGiven -> { + SubmissionNavigationEvents.NavigateToRequestDccFragment( + coronaTestQRCode = coronaTestQRCode, + consentGiven = true, + ).run { routeToScreen.postValue(this) } + } + else -> { + registrationStateProcessor.startRegistration( + request = coronaTestQRCode, + isSubmissionConsentGiven = true, + allowReplacement = false ) - } else { - qrCodeRegistrationStateProcessor.startQrCodeRegistration(coronaTestQRCode, true) } - } catch (err: InvalidQRCodeException) { - Timber.i(err) - qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.INVALID) } } 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 6839584b08883c9d03fcb3e5b107c8123a8bf331..6484865f7cb2706c9559ad71214401bec7d2b5fc 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 @@ -7,19 +7,15 @@ import android.view.View import android.view.accessibility.AccessibilityEvent import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.zxing.BarcodeFormat import com.journeyapps.barcodescanner.DefaultDecoderFactory import de.rki.coronawarnapp.NavGraphDirections import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.coronatest.server.CoronaTestResult -import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type +import de.rki.coronawarnapp.coronatest.errors.AlreadyRedeemedException import de.rki.coronawarnapp.databinding.FragmentSubmissionQrCodeScanBinding 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.submission.TestRegistrationStateProcessor.State import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject @@ -30,7 +26,6 @@ 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 timber.log.Timber import javax.inject.Inject /** @@ -72,7 +67,10 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co when (it) { is SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode -> { NavGraphDirections - .actionToSubmissionDeletionWarningFragment(it.consentGiven, it.coronaTestQRCode) + .actionToSubmissionDeletionWarningFragment( + testRegistrationRequest = it.coronaTestQRCode, + isConsentGiven = it.consentGiven, + ) .run { doNavigate(this) } } is SubmissionNavigationEvents.NavigateToDispatcher -> navigateToDispatchScreen() @@ -83,57 +81,44 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co } } - viewModel.qrCodeValidationState.observe2(this) { - if (QrCodeRegistrationStateProcessor.ValidationState.INVALID == it) { - DialogHelper.showDialog(createInvalidScanDialog()) - } - } - - viewModel.registrationError.observe2(this) { - DialogHelper.showDialog(buildErrorDialog(it)) - } - 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) - goBack() + viewModel.qrCodeErrorEvent.observe2(this) { + showInvalidQrCodeDialog() } viewModel.registrationState.observe2(this) { state -> - when (state.apiRequestState) { - ApiRequestState.STARTED -> binding.submissionQrCodeScanSpinner.show() - else -> binding.submissionQrCodeScanSpinner.hide() + if (state is State.Working) { + binding.submissionQrCodeScanSpinner.show() + } else { + binding.submissionQrCodeScanSpinner.hide() } - when (state.test?.testResult) { - CoronaTestResult.PCR_POSITIVE -> - NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = Type.PCR) - - CoronaTestResult.PCR_OR_RAT_PENDING -> - NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type) - - CoronaTestResult.PCR_NEGATIVE, - CoronaTestResult.PCR_INVALID, - CoronaTestResult.PCR_REDEEMED -> - NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = Type.PCR) - - CoronaTestResult.RAT_POSITIVE -> - NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = Type.RAPID_ANTIGEN) - - CoronaTestResult.RAT_NEGATIVE, - CoronaTestResult.RAT_INVALID, - CoronaTestResult.RAT_PENDING, - CoronaTestResult.RAT_REDEEMED -> - NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = Type.RAPID_ANTIGEN) - null -> { - Timber.w("Successful API request, but test was null?") - return@observe2 + when (state) { + State.Idle, + State.Working -> { + // Handled above + } + is State.Error -> { + when (state.exception) { + is BadRequestException -> showInvalidQrCodeDialog() + else -> { + state.getDialogBuilder(requireContext()).apply { + when (state.exception) { + is AlreadyRedeemedException -> setOnDismissListener { goBack() } + else -> setOnDismissListener { navigateToDispatchScreen() } + } + }.show() + } + } } - }.run { doNavigate(this) } + is State.TestRegistered -> when { + state.test.isPositive -> + NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = state.test.type) + .run { doNavigate(this) } + + else -> + NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type) + .run { doNavigate(this) } + } + } } } @@ -143,45 +128,23 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co } } - private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance { - return 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, - ::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() ) - 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, - { startDecode() }, - { viewModel.onBackPressed() }, - { viewModel.onBackPressed() } - ) + private fun showInvalidQrCodeDialog() { + MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(R.string.submission_qr_code_scan_invalid_dialog_headline) + setMessage(R.string.submission_qr_code_scan_invalid_dialog_body) + setPositiveButton(R.string.submission_qr_code_scan_invalid_dialog_button_positive) { _, _ -> + startDecode() + } + setNegativeButton(R.string.submission_qr_code_scan_invalid_dialog_button_negative) { _, _ -> + viewModel.onBackPressed() + } + setOnCancelListener { viewModel.onBackPressed() } + }.show() + } override fun onRequestPermissionsResult( requestCode: Int, 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 b1e0a834805da9dffe95c759703f5ef3752ce5f9..7cc747194e11cccf1cfc0319a132d1dbad705fa6 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 @@ -5,9 +5,8 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQrCodeValidator import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException -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.submission.TestRegistrationStateProcessor import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.permission.CameraSettings @@ -21,21 +20,19 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, @Assisted private val isConsentGiven: Boolean, private val cameraSettings: CameraSettings, - private val qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor, + private val registrationStateProcessor: TestRegistrationStateProcessor, private val submissionRepository: SubmissionRepository, private val qrCodeValidator: CoronaTestQrCodeValidator, - private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + val events = SingleLiveEvent<SubmissionNavigationEvents>() - val qrCodeValidationState = SingleLiveEvent<QrCodeRegistrationStateProcessor.ValidationState>() - val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning - val registrationState = qrCodeRegistrationStateProcessor.registrationState - val registrationError = qrCodeRegistrationStateProcessor.registrationError + val qrCodeErrorEvent = SingleLiveEvent<Exception>() + val registrationState = registrationStateProcessor.state.asLiveData2() fun registerCoronaTest(rawResult: String) = launch { try { val ctQrCode = qrCodeValidator.validate(rawResult) - qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.SUCCESS) + val coronaTest = submissionRepository.testForType(ctQrCode.type).first() when { coronaTest != null -> events.postValue( @@ -46,8 +43,11 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor( ) else -> if (!ctQrCode.isDccSupportedByPoc) { - qrCodeRegistrationStateProcessor.startQrCodeRegistration(ctQrCode, isConsentGiven) - if (isConsentGiven) analyticsKeySubmissionCollector.reportAdvancedConsentGiven(ctQrCode.type) + registrationStateProcessor.startRegistration( + request = ctQrCode, + isSubmissionConsentGiven = isConsentGiven, + allowReplacement = false + ) } else { events.postValue( SubmissionNavigationEvents.NavigateToRequestDccFragment(ctQrCode, isConsentGiven) @@ -56,7 +56,7 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor( } } catch (err: InvalidQRCodeException) { Timber.d(err, "Invalid QrCode") - qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.INVALID) + qrCodeErrorEvent.postValue(err) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanFragment.kt index 97ea51f53dcb69910aeda363e1097fbcf3f10aff..f669290e7550a91070a4e39dff6d96c0224d0207 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanFragment.kt @@ -55,8 +55,8 @@ class SubmissionTanFragment : Fragment(R.layout.fragment_submission_tan), AutoIn is SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromTan -> doNavigate( SubmissionTanFragmentDirections.actionSubmissionTanFragmentToSubmissionDeletionWarningFragment( + testRegistrationRequest = it.coronaTestTan, isConsentGiven = it.consentGiven, - coronaTestTan = it.coronaTestTan ) ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeFragment.kt index b3eb8f9bb14043d99eab30fe568aa671ef9f4a2f..b3e8bdd3c47256a29473af30801a6eae25b390aa 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeFragment.kt @@ -4,6 +4,8 @@ import android.content.DialogInterface import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent +import androidx.appcompat.content.res.AppCompatResources.getDrawable +import androidx.core.view.isGone import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs import de.rki.coronawarnapp.R @@ -45,7 +47,38 @@ class SubmissionTestResultNegativeFragment : Fragment(R.layout.fragment_submissi } viewModel.testResult.observe2(this) { - binding.submissionTestResultSection.setTestResultSection(it.coronaTest) + binding.apply { + submissionTestResultSection.setTestResultSection(it.coronaTest) + + when (it.certificateState) { + SubmissionTestResultNegativeViewModel.CertificateState.NOT_REQUESTED -> { + testResultNegativeStepsNegativeResult.setIsFinal(true) + testResultNegativeStepsCertificate.isGone = true + } + SubmissionTestResultNegativeViewModel.CertificateState.PENDING -> { + testResultNegativeStepsNegativeResult.setIsFinal(false) + testResultNegativeStepsCertificate.isGone = false + testResultNegativeStepsCertificate.setEntryText( + getText( + R.string.submission_test_result_pending_steps_test_certificate_not_available_yet_body + ) + ) + testResultNegativeStepsCertificate.setIcon( + getDrawable(requireContext(), R.drawable.ic_result_pending_certificate_info) + ) + } + SubmissionTestResultNegativeViewModel.CertificateState.AVAILABLE -> { + testResultNegativeStepsNegativeResult.setIsFinal(false) + testResultNegativeStepsCertificate.isGone = false + testResultNegativeStepsCertificate.setEntryText( + getText(R.string.coronatest_negative_result_certificate_info_body) + ) + testResultNegativeStepsCertificate.setIcon( + getDrawable(requireContext(), R.drawable.ic_qr_code_illustration) + ) + } + } + } } viewModel.routeToScreen.observe2(this) { navDirections -> diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt index 98ed23aeeba648f2888d14b4eddd749a73c2f73e..b9f77425471d4a5524286cfa86f1fcdcaeeed465 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt @@ -1,6 +1,5 @@ package de.rki.coronawarnapp.ui.submission.testresult.negative -import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import androidx.navigation.NavDirections import dagger.assisted.Assisted @@ -8,19 +7,20 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.pcr.notification.PCRTestResultAvailableNotificationService +import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository import de.rki.coronawarnapp.submission.SubmissionRepository -import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState 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.combine import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.map import timber.log.Timber class SubmissionTestResultNegativeViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val submissionRepository: SubmissionRepository, + certificateRepository: TestCertificateRepository, private val testResultAvailableNotificationService: PCRTestResultAvailableNotificationService, @Assisted private val testType: CoronaTest.Type ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { @@ -30,11 +30,25 @@ class SubmissionTestResultNegativeViewModel @AssistedInject constructor( } val routeToScreen = SingleLiveEvent<NavDirections?>() - val testResult: LiveData<TestResultUIState> = submissionRepository.testForType(type = testType) - .filterNotNull() - .map { test -> - TestResultUIState(coronaTest = test) - }.asLiveData(context = dispatcherProvider.Default) + val testResult = combine( + submissionRepository.testForType(type = testType).filterNotNull(), + certificateRepository.certificates + ) { test, certs -> + val cert = certs.firstOrNull { + it.registrationToken == test.registrationToken + } + + val certificateState: CertificateState = when (cert?.isCertificateRetrievalPending) { + true -> CertificateState.PENDING + false -> CertificateState.AVAILABLE + else -> CertificateState.NOT_REQUESTED + } + + UIState( + coronaTest = test, + certificateState = certificateState + ) + }.asLiveData(context = dispatcherProvider.Default) fun deregisterTestFromDevice() = launch { Timber.tag(TAG).d("deregisterTestFromDevice()") @@ -49,6 +63,17 @@ class SubmissionTestResultNegativeViewModel @AssistedInject constructor( testResultAvailableNotificationService.cancelTestResultAvailableNotification() } + enum class CertificateState { + NOT_REQUESTED, + PENDING, + AVAILABLE + } + + data class UIState( + val coronaTest: CoronaTest, + val certificateState: CertificateState + ) + @AssistedFactory interface Factory : CWAViewModelFactory<SubmissionTestResultNegativeViewModel> { fun create(testType: CoronaTest.Type): SubmissionTestResultNegativeViewModel diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt index 0a7ea7fe96df788be96c9b00af8de2dc9ff166ac..599af6b26539326859a360ec7374b093dc7e373b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt @@ -106,7 +106,7 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( R.string.submission_test_result_pending_steps_test_certificate_not_supported_body } else -> { - if (it.coronaTest.isAdvancedConsentGiven) { + if (it.coronaTest.isDccConsentGiven) { R.string.submission_test_result_pending_steps_test_certificate_not_available_yet_body } else { R.string.submission_test_result_pending_steps_test_certificate_not_desired_by_user_body diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt index f8c117a56350b2d839999f3f3d1c45a3d090513b..d6f50ad585e4ede14cbc4d64433f8912901f26f8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.ui.view import android.content.Context +import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.View import android.view.ViewGroup @@ -20,6 +21,9 @@ open class StepEntry @JvmOverloads constructor( val body: FrameLayout? + private lateinit var entryLine: View + private lateinit var entryIcon: ImageView + init { inflate(context, R.layout.view_step_entry, this) @@ -27,14 +31,24 @@ open class StepEntry @JvmOverloads constructor( context.withStyledAttributes(attrs, R.styleable.StepEntry) { val icon = getDrawable(R.styleable.StepEntry_step_entry_icon) - findViewById<ImageView>(R.id.step_entry_icon).setImageDrawable(icon) + entryIcon = findViewById(R.id.step_entry_icon) + setIcon(icon) val isFinal = getBoolean(R.styleable.StepEntry_step_entry_final, false) - findViewById<View>(R.id.step_entry_line).visibility = if (isFinal) { - View.INVISIBLE - } else { - View.VISIBLE - } + entryLine = findViewById(R.id.step_entry_line) + setIsFinal(isFinal) + } + } + + fun setIcon(icon: Drawable?) { + entryIcon.setImageDrawable(icon) + } + + fun setIsFinal(isFinal: Boolean) { + entryLine.visibility = if (isFinal) { + View.INVISIBLE + } else { + View.VISIBLE } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt index 5175efcfa2e55acfd56450775bef96df63f6de12..401f895a839666f8542a2477d1f34003b96f60f3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/TimeAndDateExtensions.kt @@ -121,16 +121,26 @@ object TimeAndDateExtensions { */ fun LocalDate.toDayFormat(): String = toString(dayFormatter) + /** + * Returns a readable date String with the format "dd.MM.yyyy" like 23.05.1989 of a DateTime + */ + fun DateTime.toDayFormat(): String = toString(dayFormatter) + /** * Returns a readable time String with the format "hh:mm" like 12:00 of a LocalDate */ fun LocalDate.toShortTimeFormat(): String = toString(shortTime) /** - * Returns a readable time String with the format "hh:mm" like 12:00 of a LocalDate + * Returns a readable time String with the format "hh:mm" like 12:00 of a Instant */ fun Instant.toShortTimeFormat(): String = toString(shortTime) + /** + * Returns a readable time String with the format "hh:mm" like 12:00 of a DateTime + */ + fun DateTime.toShortTimeFormat(): String = toString(shortTime) + /** * Returns a readable date String with the format "dd.MM.yy" like 23.05.89 of an Instant */ diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/BottomNavigationViewExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/BottomNavigationViewExtensions.kt new file mode 100644 index 0000000000000000000000000000000000000000..e118feb598543f94085928d719c1850abbf3b246 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/BottomNavigationViewExtensions.kt @@ -0,0 +1,15 @@ +package de.rki.coronawarnapp.util.ui + +import androidx.annotation.IdRes +import com.google.android.material.bottomnavigation.BottomNavigationView +import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat + +fun BottomNavigationView.updateCountBadge(@IdRes badgeId: Int, count: Int) { + if (count > 0) { + val badge = getOrCreateBadge(badgeId) + badge.number = count + badge.badgeTextColor = context.getColorCompat(android.R.color.white) + } else { + removeBadge(badgeId) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt index eca763ca8350351466411c1cdbf9051ac9d5bde8..d132186293c7d25e07426910920b187be39ec3b6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/viewmodel/CWAViewModel.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.util.viewmodel import androidx.annotation.CallSuper import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import de.rki.coronawarnapp.util.coroutine.DefaultDispatcherProvider import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -27,6 +28,8 @@ abstract class CWAViewModel constructor( Timber.tag(tag).v("Initialized") } + fun <T> Flow<T>.asLiveData2() = asLiveData(context = dispatcherProvider.Default) + /** * This launches a coroutine on another thread * Remember to switch to the main thread if you want to update the UI directly diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_antigen_test_result_negative.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_antigen_test_result_negative.xml index 0a623944ccacd4ba886906078827ffa1bdbf0095..1f89e65894908f1bfe8cf3896ccd01875d31ea00 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_submission_antigen_test_result_negative.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_antigen_test_result_negative.xml @@ -226,9 +226,22 @@ app:layout_constraintTop_toBottomOf="@+id/test_result_negative_steps_negative_result" app:simple_step_entry_text="@string/coronatest_negative_antigen_restul_third_info_body" app:simple_step_entry_title="@string/coronatest_negative_antigen_result_third_info_title" - app:step_entry_final="true" + app:step_entry_final="false" app:step_entry_icon="@drawable/ic_test_result_delete_test" /> + <de.rki.coronawarnapp.ui.view.SimpleStepEntry + android:id="@+id/coronatest_negative_antigen_result_fourth_info" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/spacing_normal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@id/coronatest_negative_antigen_result_third_info" + app:layout_constraintTop_toBottomOf="@+id/coronatest_negative_antigen_result_third_info" + app:simple_step_entry_text="@string/coronatest_negative_result_certificate_info_body" + app:simple_step_entry_title="@string/coronatest_negative_result_certificate_info_title" + app:step_entry_final="true" + app:step_entry_icon="@drawable/ic_qr_code_illustration" /> + <LinearLayout android:id="@+id/further_info" android:layout_width="match_parent" @@ -236,7 +249,7 @@ android:background="@color/colorSurface2" android:orientation="vertical" android:padding="@dimen/spacing_normal" - app:layout_constraintTop_toBottomOf="@id/coronatest_negative_antigen_result_third_info"> + app:layout_constraintTop_toBottomOf="@id/coronatest_negative_antigen_result_fourth_info"> <TextView android:id="@+id/further_info_title" diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml index 49d5aab4c16290b5dbfae8b3908674b1f6f69173..bba45502a73051f0722591844122fe8cc7529596 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml @@ -88,9 +88,22 @@ app:layout_constraintTop_toBottomOf="@+id/test_result_negative_steps_added" app:simple_step_entry_text="@string/submission_test_result_negative_steps_negative_body" app:simple_step_entry_title="@string/submission_test_result_negative_steps_negative_heading" - app:step_entry_final="true" + app:step_entry_final="false" app:step_entry_icon="@drawable/ic_test_result_step_done" /> + <de.rki.coronawarnapp.ui.view.SimpleStepEntry + android:id="@+id/test_result_negative_steps_certificate" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/spacing_normal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@+id/test_result_negative_steps_negative_result" + app:layout_constraintTop_toBottomOf="@+id/test_result_negative_steps_negative_result" + app:simple_step_entry_text="@string/coronatest_negative_result_certificate_info_body" + app:simple_step_entry_title="@string/coronatest_negative_result_certificate_info_title" + app:step_entry_final="true" + app:step_entry_icon="@drawable/ic_qr_code_illustration" /> + <LinearLayout android:id="@+id/further_info" android:layout_width="match_parent" @@ -98,7 +111,7 @@ android:background="@color/colorSurface2" android:orientation="vertical" android:padding="@dimen/spacing_normal" - app:layout_constraintTop_toBottomOf="@id/test_result_negative_steps_negative_result"> + app:layout_constraintTop_toBottomOf="@id/test_result_negative_steps_certificate"> <TextView android:id="@+id/further_info_title" 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 217906dc87c006ef5b5aba2b0eca4c7bf84c34dc..1d171160791470a43dca36467ad62968b6508433 100644 --- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml @@ -709,20 +709,13 @@ android:name="de.rki.coronawarnapp.ui.submission.deletionwarning.SubmissionDeletionWarningFragment" android:label="SubmissionDeletionWarningFragment" tools:layout="@layout/fragment_submission_deletion_warning"> + <argument + android:name="testRegistrationRequest" + app:argType="de.rki.coronawarnapp.coronatest.TestRegistrationRequest" /> <argument android:name="isConsentGiven" android:defaultValue="false" app:argType="boolean" /> - <argument - android:name="coronaTestQrCode" - android:defaultValue="@null" - app:argType="de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode" - app:nullable="true" /> - <argument - android:name="coronaTestTan" - android:defaultValue="@null" - app:argType="de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN" - app:nullable="true" /> <action android:id="@+id/action_submissionDeletionWarningFragment_to_submissionDispatcherFragment" app:popUpTo="@id/submissionDispatcherFragment" @@ -828,8 +821,8 @@ android:label="fragment_request_covid_certificate" tools:layout="@layout/fragment_request_covid_certificate"> <argument - android:name="coronaTestQrCode" - app:argType="de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode" /> + android:name="testRegistrationRequest" + app:argType="de.rki.coronawarnapp.coronatest.TestRegistrationRequest" /> <argument android:name="coronaTestConsent" android:defaultValue="false" @@ -842,7 +835,8 @@ <action android:id="@+id/action_requestCovidCertificateFragment_to_dispatcherFragment" - app:popUpTo="@id/submissionDispatcherFragment" + app:destination="@id/submissionDispatcherFragment" + app:popUpTo="@id/mainFragment" app:popUpToInclusive="false" /> <action diff --git a/Corona-Warn-App/src/main/res/values-bg/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-bg/antigen_strings.xml index 100652ed537682e1f90c188696f927b0ad0de261..ff9f329d4d311627cc34144a8a4f812d17ffc2cc 100644 --- a/Corona-Warn-App/src/main/res/values-bg/antigen_strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/antigen_strings.xml @@ -140,6 +140,10 @@ <string name="coronatest_negative_antigen_result_third_info_title">"Премахване на теÑта"</string> <!-- XTXT: coronatest negative antigen result third info body --> <string name="coronatest_negative_antigen_restul_third_info_body">"МолÑ, изтрийте теÑта от приложението Corona-Warn-App, за да можете да запазите нов код на теÑÑ‚, ако е необходимо."</string> + <!-- XTXT: coronatest negative result certificate info title --> + <string name="coronatest_negative_result_certificate_info_title">"Сертификат за теÑтване"</string> + <!-- XTXT: coronatest negative result certificate info body --> + <string name="coronatest_negative_result_certificate_info_body">"Сертификатът за направен теÑÑ‚ е наличен на таба „Сертификати“."</string> <!-- #################################### Rapid Antigen Test Profile diff --git a/Corona-Warn-App/src/main/res/values-bg/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-bg/green_certificate_strings.xml index ddae0a81127cfe8a2349f50c4d572b3b68e19b95..fa89d6092649084bd11e75f81ffd1d159ab31e15 100644 --- a/Corona-Warn-App/src/main/res/values-bg/green_certificate_strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/green_certificate_strings.xml @@ -1,16 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?><resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"> <!-- XTXT: Request green certificate title --> - <string name="request_green_certificate_title">"Сертификат за теÑÑ‚ за COVID"</string> + <string name="request_green_certificate_title">"COVID Ñертификат за теÑтване"</string> <!-- XTXT: Request green certificate subtitle --> - <string name="request_green_certificate_subtitle">"Можете да използвате приложението, за да заÑвите официален електронен Ñертификат за теÑтване, който ще бъде добавен в приложението."</string> + <string name="request_green_certificate_subtitle">"Можете да използвате приложението, за да заÑвите официален цифров Ñертификат за теÑтване, който ще бъде добавен вътре."</string> <!-- XTXT: Request green certificate body section 1 --> - <string name="request_green_certificate_body_1">"Сертификатът за теÑтване Ñе издава Ñамо ако получите отрицателен резултат от теÑта."</string> + <string name="request_green_certificate_body_1">"Сертификат за теÑтване Ñе издава Ñамо ако резултатът от теÑта е отрицателен."</string> <!-- XTXT: Request green certificate body section 2 --> <string name="request_green_certificate_body_2">"Сертификатът за теÑтване е валидно доказателÑтво за отрицателен резултат от теÑÑ‚ в рамките на ЕС (например при пътуване)."</string> <!-- XTXT: Request green certificate body section 3 --> - <string name="request_green_certificate_body_3">"Сертификатът за теÑтване Ñъдържа валидни данни, които позволÑват на приложението за проверка да валидира Ñертификата ви."</string> + <string name="request_green_certificate_body_3">"Сертификатът за теÑтване Ñъдържа данни, които позволÑват на приложението за проверка да валидира Ñертификата Ви."</string> <!-- XBUT: Request green certificate agree button --> - <string name="request_green_certificate_agree_button">"ЗаÑвете теÑтов Ñертификат"</string> + <string name="request_green_certificate_agree_button">"ЗаÑвете Ñертификат за теÑÑ‚"</string> <!-- XBUT: Request green certificate disagree button --> <string name="request_green_certificate_disagree_button">"Ðе, благодарÑ!"</string> <!-- XTXT: Request green certificate birth date description --> @@ -26,25 +26,77 @@ <!-- XBUT: Request green certificate exit dialog negative button --> <string name="request_gc_dialog_negative_button">"Отказ"</string> <!-- XTXT: Detail green certificate title --> - <string name="detail_green_certificate_title">"Електронен Ñертификат на ЕС за теÑтване за COVID"</string> + <string name="detail_green_certificate_title">"Цифров COVID Ñертификат на ЕС за теÑтване"</string> <!-- XTXT: Detail green certificate test type --> <string name="detail_green_certificate_test_type">"ТеÑÑ‚ за SARS-CoV-2"</string> <!-- XTXT: Detail green certificate card title --> <string name="detail_green_certificate_card_title">"Сертификат за теÑтване"</string> + <!-- XTXT: Detail green certificate card subtitle --> + <string name="detail_green_certificate_card_subtitle">"ТеÑÑ‚ÑŠÑ‚ е извършен на %1$s %2$s"</string> <!-- XTXT: Detail green certificate travel notice link en --> <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string> <!-- XTXT: Detail green certificate travel notice link de --> <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string> + <!-- XTXT: Detail green certificate menu item: delete --> + <string name="green_certificate_details_menu_item_delete">"Изтриване"</string> + <!-- XTXT: Detail green certificate diaglog title --> + <string name="green_certificate_details_dialog_remove_test_title">"Желаете ли да изтриете Ñертификата за теÑтване?"</string> + <!-- XTXT: Detail green certificate diaglog message --> + <string name="green_certificate_details_dialog_remove_test_message">"Ðко изтриете този Ñертификат, повече нÑма да можете да го използвате за доказателÑтво чрез приложението."</string> + <!-- XTXT: Detail green certificate diaglog positive --> + <string name="green_certificate_details_dialog_remove_test_button_positive">"Изтриване"</string> + <!-- XTXT: Detail green certificate diaglog negative --> + <string name="green_certificate_details_dialog_remove_test_button_negative">"Отказ"</string> + <!-- XTXT: Green certificate main screen title --> <string name="certification_screen_title">"Сертификати"</string> <!-- XTXT: Green certificate menu item --> <string name="menu_certification_information">"ИнформациÑ"</string> <!-- XTXT: Green certificate main screen header text --> - <string name="green_certification_header_info">"Тук Ñе показват електронните ви Ñертификати за вакÑиниране и за теÑтваниÑ."</string> + <string name="green_certification_header_info">"Тук Ñе показват цифровите Ви Ñертификати за вакÑиниране и за теÑтваниÑ."</string> <!-- XTXT: Green certificate info card title 1st line --> <string name="info_banner_title_1">"Електронен COVID-19"</string> <!-- XTXT: Green certificate info card title 2nd line --> - <string name="info_banner_title_2">"Сертификат за теÑÑ‚ за COVID"</string> + <string name="info_banner_title_2">"COVID Ñертификат за теÑтване"</string> <!-- XTXT: Green certificate info card body --> - <string name="info_banner_body">"РегиÑтрирайте теÑÑ‚ на Ð½Ð°Ñ‡Ð°Ð»Ð½Ð¸Ñ ÐµÐºÑ€Ð°Ð½ и дайте ÑъглаÑие да получите електронен Ñертификат за теÑтване. Той ще Ñе покаже тук веднага щом е наличен."</string> + <string name="info_banner_body">"РегиÑтрирайте теÑÑ‚ от Ð½Ð°Ñ‡Ð°Ð»Ð½Ð¸Ñ ÐµÐºÑ€Ð°Ð½ и дайте ÑъглаÑие да получите цифров Ñертификат за теÑтване. Той ще Ñе покаже тук веднага щом Ñтане наличен."</string> + + <!-- XTXT: Test certificate time --> + <string name="test_certificate_time">"ТеÑÑ‚ÑŠÑ‚ е извършен на %1$s, %2$s"</string> + <!-- XTXT: Test error label --> + <string name="test_certificate_error_label">"Грешка при извличането на Ñертификата"</string> + <!-- XBUT: Test error retry button --> + <string name="test_certificate_error_retry_button">"Опитайте пак"</string> + <!-- XTXT: Error text --> + <string name="error_tc_try_again">"Ðе беше уÑтановена връзка. Опитайте отново."</string> + <!-- XTXT: Error text --> + <string name="error_tc_dcc_not_supported_by_lab">"Ðе можете да заÑвите Ñертификат за направен теÑÑ‚, тъй като пунктът не поддържа издаването на такива. МолÑ, изтрийте Ñертификата или Ñе Ñвържете Ñ Ð³Ð¾Ñ€ÐµÑ‰Ð°Ñ‚Ð° Ð»Ð¸Ð½Ð¸Ñ Ð·Ð° техничеÑки проблеми, поÑочена в â€žÐ˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° приложението“ -> „ТехничеÑки въпроÑи“."</string> + <!-- XTXT: Error text --> + <string name="error_tc_no_network">"ÐÑма връзка Ñ Ð¸Ð½Ñ‚ÐµÑ€Ð½ÐµÑ‚. МолÑ, проверете Ñ Ð¸ опитайте отново."</string> + <!-- XTXT: Error text --> + <string name="error_tc_e2e_error_call_hotline">"Възникна грешка. МолÑ, опитайте по-къÑно или Ñе Ñвържете Ñ Ð³Ð¾Ñ€ÐµÑ‰Ð°Ñ‚Ð° Ð»Ð¸Ð½Ð¸Ñ Ð·Ð° техничеÑки проблеми, поÑочена в â€žÐ˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° приложението“ -> „ТехничеÑки въпроÑи“."</string> + <!-- XTXT: Error text --> + <string name="error_tc_try_again_dcc_not_available_yet">"ВашиÑÑ‚ Ñертификат още не е наличен. МолÑ, опитайте отново. Ðко грешката продължи да възниква, Ñе обърнете към горещата Ð»Ð¸Ð½Ð¸Ñ Ð·Ð° техничеÑки проблеми, поÑочена в â€žÐ˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° приложението“ -> „ТехничеÑки въпроÑи“."</string> + <!-- XTXT: Error text --> + <string name="error_tc_client_error_call_hotline">"Възникна грешка. МолÑ, опитайте по-къÑно или Ñе Ñвържете Ñ Ð³Ð¾Ñ€ÐµÑ‰Ð°Ñ‚Ð° Ð»Ð¸Ð½Ð¸Ñ Ð·Ð° техничеÑки проблеми, поÑочена в â€žÐ˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° приложението“ -> „ТехничеÑки въпроÑи“."</string> + <!-- XTXT: Error text --> + <string name="error_tc_dcc_expired">"Сертификатът вече не е актуален. Можете да го изтриете от Corona-Warn-App."</string> + <!-- XBUT: Test error delete button --> + <string name="test_certificate_error_delete_button">"Изтриване на Ñертификата за теÑтване"</string> + <!-- XTXT: Test error refresh dialog title --> + <string name="test_certificate_refresh_dialog_title">"Ð’Ñе още има проблем ÑÑŠÑ Ð·Ð°Ñвката."</string> + <!-- XBUT: Test error refresh dialog confirm button --> + <string name="test_certificate_refresh_dialog_confirm_button">"OK"</string> + <!-- XTXT: Test error delete dialog title --> + <string name="test_certificate_delete_dialog_title">"Желаете ли да изтриете Ñертификата?"</string> + <!-- XTXT: Test error delete dialog body --> + <string name="test_certificate_delete_dialog_body">"Ðко изтриете този Ñертификат, не можете да го заÑвите отново."</string> + <!-- XBUT: Test error delete dialog confirm button --> + <string name="test_certificate_delete_dialog_confirm_button">"Изтриване"</string> + <!-- XBUT: Test error delete dialog cancel button --> + <string name="test_certificate_delete_dialog_cancel_button">"Отказ"</string> + <!-- XTXT: Test error card body refreshing --> + <string name="test_certificate_error_label_refreshing">"ВашиÑÑ‚ Ñертификат Ñе Ñъздава..."</string> + <!-- XTXT: Test error card body refreshing --> + <string name="test_certificate_error_refreshing_status">"ВашиÑÑ‚ Ñертификат Ñе заÑвÑва. Това може да отнеме нÑколко минути..."</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-bg/strings.xml b/Corona-Warn-App/src/main/res/values-bg/strings.xml index 9d754260bca4c26af7a54b40655eac90a195aa73..45c8bcb75e157cb31e3b3d37fc4314d9f7dd47ab 100644 --- a/Corona-Warn-App/src/main/res/values-bg/strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/strings.xml @@ -308,7 +308,7 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Този период Ñе включва в изчиÑлението."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> -<!-- Dialog part 1--> + <!-- Dialog part 1--> <string name="risk_details_information_body_period_logged">"ВашиÑÑ‚ риÑк от заразÑване може да Ñе изчиÑли Ñамо за периодите, в които региÑтрирането на излаганиÑта на риÑк е било активно. Ето защо тази Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ñ‚Ñ€Ñбва да бъде активирана поÑтоÑнно. РегиÑтрирането на контактите Ви покрива поÑледните 14 дни."</string> <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Приложението Corona-Warn-App е инÑталирано преди %s дни. РиÑкът да Ñте Ñе заразили Ñе изчиÑлÑва за периодите, през които е било активно региÑтрирането на контактите Ви. Ðко Ñте били в близоÑÑ‚ до други хора и региÑтрирането на излаганиÑта е било активно, риÑкът да Ñте Ñе заразили ще бъде изчиÑлен."</string> @@ -1143,8 +1143,12 @@ <string name="submission_test_result_dialog_tracing_required_button">"OK"</string> <!-- XHED: Dialog title for test removal --> <string name="submission_test_result_dialog_remove_test_title">"ТеÑÑ‚ÑŠÑ‚ може да Ñе Ñканира Ñамо веднъж."</string> + <!-- XHED: Dialog title for test removal no submission --> + <string name="submission_test_result_dialog_remove_test_title_no_submission">"ÐаиÑтина ли иÑкате да изтриете теÑта?"</string> <!-- YTXT: Dialog text for test removal --> <string name="submission_test_result_dialog_remove_test_message">"Ðко изтриете теÑта, повече нÑма да можете да извлечете данните за резултата Ñи. Ще получите резултата Ñи от център за теÑтване или лабораториÑ, незавиÑимо от валидноÑтта на QR кода. Ðко Ви бъде поÑтавена диагноза „коронавируÑ“, Ñлужбата за общеÑтвено здравеопазване ще бъде уведомена за това по уÑÑ‚Ð°Ð½Ð¾Ð²ÐµÐ½Ð¸Ñ Ð¾Ñ‚ правните норми канал и ще Ñе Ñвърже Ñ Ð’Ð°Ñ."</string> + <!-- YTXT: Dialog text for test removal no submission --> + <string name="submission_test_result_dialog_remove_test_message_no_submission">"Ðко го направите, другите нÑма да бъдат предупредени."</string> <!-- XBUT: Positive button for test removal --> <string name="submission_test_result_dialog_remove_test_button_positive">"Изтриване"</string> <!-- XBUT: Negative button for test removal --> @@ -1233,6 +1237,8 @@ <string name="submission_test_result_positive_no_consent_text_3">"МолÑ, Ñледвайте инÑтрукциите на Ð’Ð°ÑˆÐ¸Ñ Ð»ÐµÐºÐ°Ñ€ и Ñи оÑтанете вкъщи, за да не заразÑвате други хора."</string> <!-- XBUT: Button for giving consent for key sharing --> <string name="submission_test_result_positive_no_consent_button_warn_others">"Предупредете другите"</string> + <!-- XBUT: Button for test removal --> + <string name="submission_test_result_positive_no_consent_button_remove_test">"Премахване на теÑта"</string> <!-- Submission Country Selector --> <!-- XHED: Page title for the submission country selection page --> diff --git a/Corona-Warn-App/src/main/res/values-bg/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values-bg/vaccination_strings.xml index fe8bd5d56c8839e6420023b221c98576bb25c0d9..384be191b9e1bf252f265e196ef4963d4ab79370 100644 --- a/Corona-Warn-App/src/main/res/values-bg/vaccination_strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/vaccination_strings.xml @@ -36,21 +36,21 @@ <string name="vaccination_list_vaccination_card_subtitle">"Извършено на %1$s"</string> <!-- XTXT: Vaccination List immunity information card body--> <plurals name="vaccination_list_immunity_card_body"> - <item quantity="one">"Получихте вÑички планирани за момента вакÑинации, но защитата ви от вакÑиниране ще е пълна Ñлед %1$d ден."</item> - <item quantity="other">"Получихте вÑички планирани за момента вакÑинации, но защитата ви от вакÑиниране ще е пълна едва Ñлед %1$d дни."</item> - <item quantity="zero">"Получихте вÑички планирани за момента вакÑинации, но защитата ви от вакÑиниране ще е пълна едва Ñлед %1$d дни."</item> - <item quantity="two">"Получихте вÑички планирани за момента вакÑинации, но защитата ви от вакÑиниране ще е пълна едва Ñлед %1$d дни."</item> - <item quantity="few">"Получихте вÑички планирани за момента вакÑинации, но защитата ви от вакÑиниране ще е пълна едва Ñлед %1$d дни."</item> - <item quantity="many">"Получихте вÑички планирани за момента вакÑинации, но защитата ви от вакÑиниране ще е пълна едва Ñлед %1$d дни."</item> + <item quantity="one">"Получихте вÑички планирани за момента вакÑинации, но вакÑинационната Ви защита ще е пълна Ñлед %1$d ден."</item> + <item quantity="other">"Получихте вÑички планирани за момента вакÑинации, но вакÑинационната Ви защита ще е пълна едва Ñлед %1$d дни."</item> + <item quantity="zero">"Получихте вÑички планирани за момента вакÑинации, но вакÑинационната Ви защита ще е пълна едва Ñлед %1$d дни."</item> + <item quantity="two">"Получихте вÑички планирани за момента вакÑинации, но вакÑинационната Ви защита ще е пълна едва Ñлед %1$d дни."</item> + <item quantity="few">"Получихте вÑички планирани за момента вакÑинации, но вакÑинационната Ви защита ще е пълна едва Ñлед %1$d дни."</item> + <item quantity="many">"Получихте вÑички планирани за момента вакÑинации, но вакÑинационната Ви защита ще е пълна едва Ñлед %1$d дни."</item> </plurals> <!-- XBUT: Vaccination List register additional vaccination button --> <string name="vaccination_list_register_new_vaccination_button">"РегиÑтрирайте друго вакÑиниране"</string> <!-- XBUT: Vaccination List delete button --> <string name="vaccination_list_delete_button">"Изтриване"</string> <!-- XTXT: Vaccination List deletion dialog title--> - <string name="vaccination_list_deletion_dialog_title">"Желаете ли да изтриете вакÑÐ¸Ð½Ð°Ñ†Ð¸Ð¾Ð½Ð½Ð¸Ñ Ñертификат?"</string> + <string name="vaccination_list_deletion_dialog_title">"Желаете ли да изтриете Ñертификата за вакÑиниране?"</string> <!-- XTXT: Vaccination List deletion dialog message--> - <string name="vaccination_list_deletion_dialog_message">"Ðко изтриете Ñертификата, приложението вече нÑма да може да използва тази вакÑÐ¸Ð½Ð°Ñ†Ð¸Ñ Ð·Ð° удоÑтоверÑване на Ð²Ð°ÑˆÐ¸Ñ Ð²Ð°ÐºÑинационен ÑтатуÑ."</string> + <string name="vaccination_list_deletion_dialog_message">"Ðко изтриете Ñертификата за вакÑиниране, приложението вече нÑма да може да използва вакÑинациÑта за удоÑтоверÑване на Ð²Ð°ÑˆÐ¸Ñ Ð²Ð°ÐºÑинационен ÑтатуÑ."</string> <!-- XBUT: Vaccination List deletion dialog positive button--> <string name="vaccination_list_deletion_dialog_positive_button">"Изтриване"</string> <!-- XBUT: Vaccination List deletion dialog negative button--> @@ -66,11 +66,13 @@ <!-- XHED: Title for Vaccination Certificate Registration Home Card --> <string name="vaccination_card_registration_title_line_2">"за вакÑиниране"</string> <!-- YTXT: Body text for Vaccination Certificate Registration Home Card --> - <string name="vaccination_card_registration_body">"Добавете вакÑинационните Ñи Ñертификати в приложението, за да Ñа винаги Ñ Ð²Ð°Ñ. За да направите това, Ñканирайте QR кода от Ð²Ð°ÑˆÐ¸Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚."</string> + <string name="vaccination_card_registration_body">"Добавете вакÑинационните Ñи Ñертификати в приложението, за да Ñа винаги Ñ Ð’Ð°Ñ. За целта Ñканирайте QR кода от Ð’Ð°ÑˆÐ¸Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚."</string> <!-- XBUT: button for Vaccination Certificate Registration Home Card --> <string name="vaccination_card_register">"ДобавÑне"</string> <!-- XHED: Homescreen vaccination status card title --> - <string name="vaccination_card_status_title">"Електронно доказателÑтво за вакÑиниране"</string> + <string name="vaccination_card_status_title_line_1">"Цифрово"</string> + <!-- XHED: Homescreen vaccination status card title --> + <string name="vaccination_card_status_title_line_2">"ДоказателÑтво за вакÑинациÑ"</string> <!-- XHED: Homescreen vaccination status card vaccination name --> <string name="vaccination_card_status_vaccination_name">"ВакÑиниране Ñрещу SARS-CoV-2"</string> <!-- XTXT: Homescreen card incomplete vaccination status label --> @@ -84,17 +86,19 @@ <item quantity="few">"Пълна защита от вакÑиниране Ñлед %1$d дни"</item> <item quantity="many">"Пълна защита от вакÑиниране Ñлед %1$d дни"</item> </plurals> + <!-- XTXT: Homescreen card complete vaccination status label --> + <string name="vaccination_card_status_vaccination_complete">"Валидно до %s"</string> <!-- XTXT: Vaccination QR code scan error message--> <string name="error_vc_invalid">"Този QR не е валиден вакÑинационен Ñертификат.\n\nЗа да разберете как можете да Ñе Ñдобиете ÑÑŠÑ Ñертификат за извършена вакÑинациÑ, вижте Ñтраницата ни Ñ Ñ‡ÐµÑто задавани въпроÑи (ЧЗВ)."</string> <!-- XTXT: Vaccination QR code scan error message--> - <string name="error_vc_not_yet_supported">"Този вакÑинационен Ñертификат вÑе още не Ñе поддържа във верÑиÑта на Вашето приложение. МолÑ, актуализирайте приложението Ñи или Ñе Ñвържете Ñ Ð³Ð¾Ñ€ÐµÑ‰Ð°Ñ‚Ð° Ð»Ð¸Ð½Ð¸Ñ Ð·Ð° техничеÑки проблеми, поÑочена в â€žÐ˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° приложението“."</string> + <string name="error_vc_not_yet_supported">"Този Ñертификат за вакÑиниране вÑе още не Ñе поддържа във верÑиÑта на вашето приложение. МолÑ, актуализирайте приложението Ñи или Ñе Ñвържете Ñ Ð³Ð¾Ñ€ÐµÑ‰Ð°Ñ‚Ð° Ð»Ð¸Ð½Ð¸Ñ Ð·Ð° техничеÑки проблеми от â€œÐ˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° приложениетоâ€."</string> <!-- XTXT: Vaccination QR code scan error message--> <string name="error_vc_scan_again">"ВакÑинационниÑÑ‚ Ñертификат не може да бъде запазен на Ñмартфона Ви. МолÑ, опитайте отново по-къÑно или Ñе Ñвържете Ñ Ð³Ð¾Ñ€ÐµÑ‰Ð°Ñ‚Ð° Ð»Ð¸Ð½Ð¸Ñ Ð·Ð° техничеÑки проблеми, поÑочена в â€žÐ˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° приложението“."</string> <!-- XTXT: Vaccination QR code scan error message--> <string name="error_vc_already_registered">"ВакÑинационниÑÑ‚ Ñертификат вече е региÑтриран в приложението Ви."</string> <!-- XTXT: Vaccination QR code scan error message--> - <string name="error_vc_different_person">"Личната Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð² този вакÑинационен Ñертификат не ÑъответÑтва на тази във вече региÑтрираните Ñертификати. Ð’ приложението можете да региÑтрирате Ñертификати Ñамо за едно лице."</string> + <string name="error_vc_different_person">"Личната Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð² този Ñертификат за вакÑиниране не ÑъответÑтва на тази във вече региÑтрираните Ñертификати. Ð’ приложението можете да региÑтрирате Ñертификати Ñамо за едно лице."</string> <!-- XTXT: Vaccination Consent title--> <string name="vaccination_consent_title">"Вашето ÑъглаÑие"</string> @@ -107,7 +111,7 @@ <!-- XTXT: Vaccination Consent qr code text --> <string name="vaccination_consent_qr_info_qr_code_text">"Тези Ñертификати Ñе Ñчитат за валидно доказателÑтво в рамките на ЕС (например при пътуване)."</string> <!-- XTXT: Vaccination Consent time text --> - <string name="vaccination_consent_qr_info_time_text">"След като добавите Ñертификат, той ще Ñе запази в Ñмартфона Ви. Ще Ñе ÑÐ¿Ð¾Ð´ÐµÐ»Ñ Ñ Ð¾Ñтаналите Ñамо ако предÑтавите Ñертификат за вакÑиниране."</string> + <string name="vaccination_consent_qr_info_time_text">"След като добавите Ñертификат, той ще Ñе запази в Ñмартфона Ви. Ще Ñе ÑÐ¿Ð¾Ð´ÐµÐ»Ñ Ñ Ð¾Ñтаналите Ñамо ако предÑтавите вакÑинационен Ñертификат."</string> <!-- XTXT: Text for vaccination consent legal information button --> <string name="vaccination_consent_onboarding_legal_information">"За повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно обработката на данни, прочетете декларациÑта за поверителноÑÑ‚."</string> <!-- XBUT: Text for vaccination consent accept button --> diff --git a/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml index 3eb70a980ce8286f10c6cb13bcb86e4971d746de..d43011a41eb89212c632f17a2e6d6ceca3e1e2d0 100644 --- a/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml @@ -141,6 +141,10 @@ <string name="coronatest_negative_antigen_result_third_info_title">"Test entfernen"</string> <!-- XTXT: coronatest negative antigen result third info body --> <string name="coronatest_negative_antigen_restul_third_info_body">"Bitte entfernen Sie den Test wieder aus der Corona-Warn-App, damit Sie bei Bedarf einen neuen Test hinterlegen können."</string> + <!-- XTXT: coronatest negative result certificate info title --> + <string name="coronatest_negative_result_certificate_info_title">"Testzertifikat"</string> + <!-- XTXT: coronatest negative result certificate info body --> + <string name="coronatest_negative_result_certificate_info_body">"Das Testzertifikat liegt im Tab „Zertifikate“ vor."</string> <!-- #################################### Rapid Antigen Test Profile diff --git a/Corona-Warn-App/src/main/res/values-de/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-de/green_certificate_strings.xml index 43963ce719e701a30573a977f1a8aadf03262801..efb49c76369609b6cf22be32af4b232e569094e6 100644 --- a/Corona-Warn-App/src/main/res/values-de/green_certificate_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/green_certificate_strings.xml @@ -22,10 +22,10 @@ <string name="request_gc_dialog_title">Registrierung abbrechen</string> <!-- XTXT: Request green certificate exit dialog message --> <string name="request_gc_dialog_message">Wenn Sie die Test-Registrierung abbrechen, können Sie Ihr Testergebnis nicht in der App erhalten.</string> + <!-- XBUT: Request green certificate exit dialog positive button --> + <string name="request_gc_dialog_positive_button">Registrierung abbrechen</string> <!-- XBUT: Request green certificate exit dialog negative button --> - <string name="request_gc_dialog_positive_button">OK</string> - <!-- XBUT: Request green certificate exit dialog negative button --> - <string name="request_gc_dialog_negative_button">Abbrechen</string> + <string name="request_gc_dialog_negative_button">Registrierung fortsetzen</string> <!-- XTXT: Detail green certificate title --> <string name="detail_green_certificate_title">EU Digitales COVID-Testzertifikat</string> <!-- XTXT: Detail green certificate test type --> @@ -100,4 +100,8 @@ <string name="test_certificate_error_label_refreshing">"Ihr Zertifikat wird gerade erstellt…"</string> <!-- XTXT: Test error card body refreshing --> <string name="test_certificate_error_refreshing_status">"Ihr Zertifikat wird gerade angefragt, dies kann einige Minuten dauern…"</string> + <!-- XBUT: Text for invalid test certificate error button, linking to FAQ--> + <string name="test_certificate_error_invalid_labid_faq">"FAQ zu Testzertifikaten"</string> + <!-- XTXT: Explains user about test certificate: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#vac_cert_invalid --> + <string name="test_certificate_error_invalid_labid_faq_link">"https://www.coronawarn.app/de/faq/#test_cert"</string> </resources> diff --git a/Corona-Warn-App/src/main/res/values-en/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-en/antigen_strings.xml index 566c614eb2cf7ad4f67557ea834c5a065deca493..c9f84fba0b2d487a61bca413ea73d7fc0b546915 100644 --- a/Corona-Warn-App/src/main/res/values-en/antigen_strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/antigen_strings.xml @@ -140,6 +140,10 @@ <string name="coronatest_negative_antigen_result_third_info_title">"Remove Test"</string> <!-- XTXT: coronatest negative antigen result third info body --> <string name="coronatest_negative_antigen_restul_third_info_body">"Please delete the test from the Corona-Warn-App, so that you can save a new test code here if necessary."</string> + <!-- XTXT: coronatest negative result certificate info title --> + <string name="coronatest_negative_result_certificate_info_title">"Test Certificate"</string> + <!-- XTXT: coronatest negative result certificate info body --> + <string name="coronatest_negative_result_certificate_info_body">"The test certificate is available on the “Certificates†tab."</string> <!-- #################################### Rapid Antigen Test Profile diff --git a/Corona-Warn-App/src/main/res/values-en/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-en/green_certificate_strings.xml index 4d945372179f5d78a3cd8891514823e8e52f5377..f0daa94caf21542f19635794aae920a0adea4add 100644 --- a/Corona-Warn-App/src/main/res/values-en/green_certificate_strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/green_certificate_strings.xml @@ -8,7 +8,7 @@ <!-- XTXT: Request green certificate body section 2 --> <string name="request_green_certificate_body_2">"The test certificate is valid proof of a negative test result within the EU (e.g. for travel)."</string> <!-- XTXT: Request green certificate body section 3 --> - <string name="request_green_certificate_body_3">"The test certificate contains valid that enables the verification app to validate your certificate."</string> + <string name="request_green_certificate_body_3">"The test certificate contains data that enables the verification app to validate your certificate."</string> <!-- XBUT: Request green certificate agree button --> <string name="request_green_certificate_agree_button">"Request Test Certificate"</string> <!-- XBUT: Request green certificate disagree button --> @@ -31,10 +31,23 @@ <string name="detail_green_certificate_test_type">"SARS-CoV-2 Test"</string> <!-- XTXT: Detail green certificate card title --> <string name="detail_green_certificate_card_title">"Test Certificate"</string> + <!-- XTXT: Detail green certificate card subtitle --> + <string name="detail_green_certificate_card_subtitle">"Test performed on %1$s %2$s"</string> <!-- XTXT: Detail green certificate travel notice link en --> <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string> <!-- XTXT: Detail green certificate travel notice link de --> <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string> + <!-- XTXT: Detail green certificate menu item: delete --> + <string name="green_certificate_details_menu_item_delete">"Remove"</string> + <!-- XTXT: Detail green certificate diaglog title --> + <string name="green_certificate_details_dialog_remove_test_title">"Do you want to remove the test certificate?"</string> + <!-- XTXT: Detail green certificate diaglog message --> + <string name="green_certificate_details_dialog_remove_test_message">"If you remove the test certificate, you can no longer use it as proof in the app."</string> + <!-- XTXT: Detail green certificate diaglog positive --> + <string name="green_certificate_details_dialog_remove_test_button_positive">"Remove"</string> + <!-- XTXT: Detail green certificate diaglog negative --> + <string name="green_certificate_details_dialog_remove_test_button_negative">"Cancel"</string> + <!-- XTXT: Green certificate main screen title --> <string name="certification_screen_title">"Certificates"</string> <!-- XTXT: Green certificate menu item --> @@ -47,4 +60,43 @@ <string name="info_banner_title_2">"COVID Test Certificate"</string> <!-- XTXT: Green certificate info card body --> <string name="info_banner_body">"Register a test on the home screen and agree to receive a digital test certificate. As soon as the certificate is available, it is displayed here."</string> + + <!-- XTXT: Test certificate time --> + <string name="test_certificate_time">"Test performed on %1$s, %2$s"</string> + <!-- XTXT: Test error label --> + <string name="test_certificate_error_label">"Error during certificate retrieval"</string> + <!-- XBUT: Test error retry button --> + <string name="test_certificate_error_retry_button">"Try Again"</string> + <!-- XTXT: Error text --> + <string name="error_tc_try_again">"Could not establish a connection. Please try again."</string> + <!-- XTXT: Error text --> + <string name="error_tc_dcc_not_supported_by_lab">"You cannot request a test certificate because this testing point does not support the issuing of test certificates. Please remove the certificate or contact the technical hotline via App Information -> Technical Hotline."</string> + <!-- XTXT: Error text --> + <string name="error_tc_no_network">"Your Internet connection was lost. Please check the connection and try again."</string> + <!-- XTXT: Error text --> + <string name="error_tc_e2e_error_call_hotline">"An error occurred. Please try again later or contact the technical hotline via App Information -> Technical Hotline."</string> + <!-- XTXT: Error text --> + <string name="error_tc_try_again_dcc_not_available_yet">"Your certificate it not available yet. Please try again. If the error persists, please contact the technical hotline via App Information -> Technical Hotline."</string> + <!-- XTXT: Error text --> + <string name="error_tc_client_error_call_hotline">"An error occurred. Please try again later or contact the technical hotline via App Information -> Technical Hotline."</string> + <!-- XTXT: Error text --> + <string name="error_tc_dcc_expired">"This certificate is no longer current. You can remove it from the Corona-Warn-App."</string> + <!-- XBUT: Test error delete button --> + <string name="test_certificate_error_delete_button">"Remove Test Certificate"</string> + <!-- XTXT: Test error refresh dialog title --> + <string name="test_certificate_refresh_dialog_title">"There are still problems with the request."</string> + <!-- XBUT: Test error refresh dialog confirm button --> + <string name="test_certificate_refresh_dialog_confirm_button">"OK"</string> + <!-- XTXT: Test error delete dialog title --> + <string name="test_certificate_delete_dialog_title">"Do you want to remove the certificate?"</string> + <!-- XTXT: Test error delete dialog body --> + <string name="test_certificate_delete_dialog_body">"If you remove the certificate, you cannot request it again."</string> + <!-- XBUT: Test error delete dialog confirm button --> + <string name="test_certificate_delete_dialog_confirm_button">"Remove"</string> + <!-- XBUT: Test error delete dialog cancel button --> + <string name="test_certificate_delete_dialog_cancel_button">"Cancel"</string> + <!-- XTXT: Test error card body refreshing --> + <string name="test_certificate_error_label_refreshing">"Your certificate is being created..."</string> + <!-- XTXT: Test error card body refreshing --> + <string name="test_certificate_error_refreshing_status">"Your certificate is being requested. This may take a few minutes..."</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-en/strings.xml b/Corona-Warn-App/src/main/res/values-en/strings.xml index d4eeb5af5155c76c7800fd3ce6fc77badebc2c26..edcb69236fd34be8dcf03ab7bfa8c8edf5354fad 100644 --- a/Corona-Warn-App/src/main/res/values-en/strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/strings.xml @@ -308,7 +308,7 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"This period is included in the calculation."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> -<!-- Dialog part 1--> + <!-- Dialog part 1--> <string name="risk_details_information_body_period_logged">"Your risk of infection can be calculated only for periods during which exposure logging was active. The logging feature should therefore remain active permanently. Exposure logging covers the last 14 days."</string> <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment_under_14_days">"The Corona-Warn-App was installed %s days ago. Your risk of infection is calculated for the periods during which exposure logging was active. If you have encountered other people and exposure logging was active, your risk of infection is calculated."</string> @@ -1143,8 +1143,12 @@ <string name="submission_test_result_dialog_tracing_required_button">"OK"</string> <!-- XHED: Dialog title for test removal --> <string name="submission_test_result_dialog_remove_test_title">"The test can only be scanned once."</string> + <!-- XHED: Dialog title for test removal no submission --> + <string name="submission_test_result_dialog_remove_test_title_no_submission">"Are you sure you want to remove your test?"</string> <!-- YTXT: Dialog text for test removal --> <string name="submission_test_result_dialog_remove_test_message">"If you delete the test, you can no longer retrieve your test result. You will receive your test result from the test center or laboratory regardless of the validity of the QR code. If you are diagnosed with coronavirus, the public health authority will be notified through the legally prescribed channel and will contact you."</string> + <!-- YTXT: Dialog text for test removal no submission --> + <string name="submission_test_result_dialog_remove_test_message_no_submission">"If you do, others will not be warned."</string> <!-- XBUT: Positive button for test removal --> <string name="submission_test_result_dialog_remove_test_button_positive">"Delete"</string> <!-- XBUT: Negative button for test removal --> @@ -1233,6 +1237,8 @@ <string name="submission_test_result_positive_no_consent_text_3">"Please be sure to follow the instructions from your public health authority and stay home, so you don’t infect others."</string> <!-- XBUT: Button for giving consent for key sharing --> <string name="submission_test_result_positive_no_consent_button_warn_others">"Warn Others"</string> + <!-- XBUT: Button for test removal --> + <string name="submission_test_result_positive_no_consent_button_remove_test">"Remove Test"</string> <!-- Submission Country Selector --> <!-- XHED: Page title for the submission country selection page --> diff --git a/Corona-Warn-App/src/main/res/values-en/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values-en/vaccination_strings.xml index e9f2c33cfe132f9ed57c4cc8c892449411360a5d..fa39d43df4de775da395fd08bf2c95b527a0c7b0 100644 --- a/Corona-Warn-App/src/main/res/values-en/vaccination_strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/vaccination_strings.xml @@ -70,7 +70,9 @@ <!-- XBUT: button for Vaccination Certificate Registration Home Card --> <string name="vaccination_card_register">"Add"</string> <!-- XHED: Homescreen vaccination status card title --> - <string name="vaccination_card_status_title">"Digital Proof of Vaccination"</string> + <string name="vaccination_card_status_title_line_1">"Digital"</string> + <!-- XHED: Homescreen vaccination status card title --> + <string name="vaccination_card_status_title_line_2">"Proof of Vaccination"</string> <!-- XHED: Homescreen vaccination status card vaccination name --> <string name="vaccination_card_status_vaccination_name">"SARS-CoV-2 Vaccination"</string> <!-- XTXT: Homescreen card incomplete vaccination status label --> @@ -84,6 +86,8 @@ <item quantity="few">"Full vaccination protection in %1$d days"</item> <item quantity="many">"Full vaccination protection in %1$d days"</item> </plurals> + <!-- XTXT: Homescreen card complete vaccination status label --> + <string name="vaccination_card_status_vaccination_complete">"Valid through %s"</string> <!-- XTXT: Vaccination QR code scan error message--> <string name="error_vc_invalid">"This QR code is not a valid vaccination certificate.\n\nFor more information about how to receive your vaccination certificate, please see our FAQ page."</string> diff --git a/Corona-Warn-App/src/main/res/values-pl/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-pl/antigen_strings.xml index 076c006f193cb06bc1c01751e391bf4f5bb3e6b2..008da831547ec3b3e3b3a3f5c72875e3ac8e8957 100644 --- a/Corona-Warn-App/src/main/res/values-pl/antigen_strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/antigen_strings.xml @@ -43,7 +43,7 @@ <!-- XTXT: homescreen card: body - error --> <string name="ag_homescreen_card_body_error">"Ustalenie wyniku Twojego testu byÅ‚o niemożliwe."</string> <!-- XTXT: homescreen card: body - not valid test --> - <string name="ag_homescreen_card_body_not_valid_test">"Twój test ma wiÄ™cej niż 21 dni i straciÅ‚ ważność. UsuÅ„ test. BÄ™dziesz mieć wówczas możliwość dodania kolejnego. "</string> + <string name="ag_homescreen_card_body_not_valid_test">"Twój test ma wiÄ™cej niż 21 dni i straciÅ‚ ważność. UsuÅ„ test. BÄ™dziesz mieć wówczas możliwość dodania kolejnego."</string> <!-- XTXT: homescreen card: body - negative --> <string name="ag_homescreen_card_body_result_negative">"Nie zdiagnozowano u Ciebie wirusa SARS-CoV-2."</string> <!-- XTXT: homescreen card: body - positive --> @@ -140,6 +140,10 @@ <string name="coronatest_negative_antigen_result_third_info_title">"UsuÅ„ test"</string> <!-- XTXT: coronatest negative antigen result third info body --> <string name="coronatest_negative_antigen_restul_third_info_body">"UsuÅ„ test z aplikacji Corona-Warn-App, aby w razie potrzeby można byÅ‚o zapisać w niej kod nowego testu."</string> + <!-- XTXT: coronatest negative result certificate info title --> + <string name="coronatest_negative_result_certificate_info_title">"Certyfikat testu"</string> + <!-- XTXT: coronatest negative result certificate info body --> + <string name="coronatest_negative_result_certificate_info_body">"Certyfikat testu dostÄ™pny jest w karcie „Certyfikatyâ€."</string> <!-- #################################### Rapid Antigen Test Profile diff --git a/Corona-Warn-App/src/main/res/values-pl/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-pl/green_certificate_strings.xml index 0ab72d844ebb07cf949604ed985321f1a6f6bdb7..1c42258b5bd01afb2a222eb2d78d5eabc754be71 100644 --- a/Corona-Warn-App/src/main/res/values-pl/green_certificate_strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/green_certificate_strings.xml @@ -8,7 +8,7 @@ <!-- XTXT: Request green certificate body section 2 --> <string name="request_green_certificate_body_2">"Certyfikat testu jest ważnym dowodem negatywnego wyniku testu na terenie UE (np. dla celów podróży)."</string> <!-- XTXT: Request green certificate body section 3 --> - <string name="request_green_certificate_body_3">"Certyfikat zawiera ważne dane, które umożliwiajÄ… aplikacji weryfikacyjnej jego walidacjÄ™."</string> + <string name="request_green_certificate_body_3">"Certyfikat testu zawiera dane, które umożliwiajÄ… aplikacji weryfikacyjnej jego walidacjÄ™."</string> <!-- XBUT: Request green certificate agree button --> <string name="request_green_certificate_agree_button">"PoproÅ› o certyfikat testu"</string> <!-- XBUT: Request green certificate disagree button --> @@ -31,10 +31,23 @@ <string name="detail_green_certificate_test_type">"Test SARS-CoV-2"</string> <!-- XTXT: Detail green certificate card title --> <string name="detail_green_certificate_card_title">"Certyfikat testu"</string> + <!-- XTXT: Detail green certificate card subtitle --> + <string name="detail_green_certificate_card_subtitle">"Test wykonany dnia %1$s %2$s"</string> <!-- XTXT: Detail green certificate travel notice link en --> <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string> <!-- XTXT: Detail green certificate travel notice link de --> <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string> + <!-- XTXT: Detail green certificate menu item: delete --> + <string name="green_certificate_details_menu_item_delete">"UsuÅ„"</string> + <!-- XTXT: Detail green certificate diaglog title --> + <string name="green_certificate_details_dialog_remove_test_title">"Czy chcesz usunąć certyfikat testu?"</string> + <!-- XTXT: Detail green certificate diaglog message --> + <string name="green_certificate_details_dialog_remove_test_message">"Po usuniÄ™ciu certyfikatu testu jego użycie jako dowodu w aplikacji nie bÄ™dzie już możliwe."</string> + <!-- XTXT: Detail green certificate diaglog positive --> + <string name="green_certificate_details_dialog_remove_test_button_positive">"UsuÅ„"</string> + <!-- XTXT: Detail green certificate diaglog negative --> + <string name="green_certificate_details_dialog_remove_test_button_negative">"Anuluj"</string> + <!-- XTXT: Green certificate main screen title --> <string name="certification_screen_title">"Certyfikaty"</string> <!-- XTXT: Green certificate menu item --> @@ -47,4 +60,43 @@ <string name="info_banner_title_2">"Certyfikat testu na COVID"</string> <!-- XTXT: Green certificate info card body --> <string name="info_banner_body">"Zarejestruj test na ekranie głównym i wyraź zgodÄ™ na otrzymanie cyfrowego certyfikatu testu. Gdy tylko certyfikat bÄ™dzie dostÄ™pny, pojawi siÄ™ w tym miejscu."</string> + + <!-- XTXT: Test certificate time --> + <string name="test_certificate_time">"Test wykonany dnia %1$s, %2$s"</string> + <!-- XTXT: Test error label --> + <string name="test_certificate_error_label">"BÅ‚Ä…d podczas pobierania certyfikatu"</string> + <!-- XBUT: Test error retry button --> + <string name="test_certificate_error_retry_button">"Spróbuj ponownie"</string> + <!-- XTXT: Error text --> + <string name="error_tc_try_again">"Nie udaÅ‚o siÄ™ nawiÄ…zać poÅ‚Ä…czenia. Spróbuj ponownie."</string> + <!-- XTXT: Error text --> + <string name="error_tc_dcc_not_supported_by_lab">"Nie możesz zażądać wydania certyfikatu testu, ponieważ ten punkt testowania nie wydaje certyfikatów testów. UsuÅ„ certyfikat lub skontaktuj siÄ™ z infoliniÄ… technicznÄ…, korzystajÄ…c z opcji: Informacje o aplikacji -> Infolinia techniczna."</string> + <!-- XTXT: Error text --> + <string name="error_tc_no_network">"PoÅ‚Ä…czenie internetowe zostaÅ‚o utracone. Sprawdź poÅ‚Ä…czenie i spróbuj ponownie."</string> + <!-- XTXT: Error text --> + <string name="error_tc_e2e_error_call_hotline">"WystÄ…piÅ‚ bÅ‚Ä…d. Spróbuj ponownie później lub skontaktuj siÄ™ z infoliniÄ… technicznÄ…, korzystajÄ…c z opcji: Informacje o aplikacji -> Infolinia techniczna."</string> + <!-- XTXT: Error text --> + <string name="error_tc_try_again_dcc_not_available_yet">"Twój certyfikat nie jest jeszcze dostÄ™pny. Spróbuj ponownie. JeÅ›li bÅ‚Ä…d bÄ™dzie nadal wystÄ™powaÅ‚, skontaktuj siÄ™ z infoliniÄ… technicznÄ…, korzystajÄ…c z opcji: Informacje o aplikacji -> Infolinia techniczna."</string> + <!-- XTXT: Error text --> + <string name="error_tc_client_error_call_hotline">"WystÄ…piÅ‚ bÅ‚Ä…d. Spróbuj ponownie później lub skontaktuj siÄ™ z infoliniÄ… technicznÄ…, korzystajÄ…c z opcji: Informacje o aplikacji -> Infolinia techniczna."</string> + <!-- XTXT: Error text --> + <string name="error_tc_dcc_expired">"Ten certyfikat nie jest już aktualny. Możesz go usunąć z aplikacji Corona-Warn-App."</string> + <!-- XBUT: Test error delete button --> + <string name="test_certificate_error_delete_button">"UsuÅ„ certyfikat testu"</string> + <!-- XTXT: Test error refresh dialog title --> + <string name="test_certificate_refresh_dialog_title">"Nadal wystÄ™pujÄ… problemy z żądaniem."</string> + <!-- XBUT: Test error refresh dialog confirm button --> + <string name="test_certificate_refresh_dialog_confirm_button">"OK"</string> + <!-- XTXT: Test error delete dialog title --> + <string name="test_certificate_delete_dialog_title">"Czy chcesz usunąć certyfikat?"</string> + <!-- XTXT: Test error delete dialog body --> + <string name="test_certificate_delete_dialog_body">"Po usuniÄ™ciu certyfikatu nie można zażądać go ponownie."</string> + <!-- XBUT: Test error delete dialog confirm button --> + <string name="test_certificate_delete_dialog_confirm_button">"UsuÅ„"</string> + <!-- XBUT: Test error delete dialog cancel button --> + <string name="test_certificate_delete_dialog_cancel_button">"Anuluj"</string> + <!-- XTXT: Test error card body refreshing --> + <string name="test_certificate_error_label_refreshing">"Certyfikat jest tworzony..."</string> + <!-- XTXT: Test error card body refreshing --> + <string name="test_certificate_error_refreshing_status">"Żądanie dotyczÄ…ce certyfikatu jest przetwarzane. Może to potrwać kilka minut..."</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-pl/strings.xml b/Corona-Warn-App/src/main/res/values-pl/strings.xml index e7ebf5e3ecead984375f3a3f95010e35aaed009c..61ae08c0fcf495641a399ceb395b930066856a14 100644 --- a/Corona-Warn-App/src/main/res/values-pl/strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/strings.xml @@ -308,7 +308,7 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Ten okres jest uwzglÄ™dniony w obliczeniu."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> -<!-- Dialog part 1--> + <!-- Dialog part 1--> <string name="risk_details_information_body_period_logged">"Ryzyko zakażenia można obliczyć tylko dla okresów, w których rejestrowanie narażenia byÅ‚o aktywne. Dlatego też funkcja rejestrowania powinna być stale aktywna. Rejestrowanie narażenia obejmuje 14 ostatnich dni."</string> <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Aplikacja Corona-Warn-App zostaÅ‚a zainstalowana %s dni temu. Ryzyko zakażenia jest obliczane dla okresów, w których aktywne byÅ‚o rejestrowanie narażenia. Oblicza siÄ™ je w przypadku kontaktowania siÄ™ z innymi ludźmi przy aktywnej funkcji rejestrowania narażenia."</string> @@ -1143,8 +1143,12 @@ <string name="submission_test_result_dialog_tracing_required_button">"OK"</string> <!-- XHED: Dialog title for test removal --> <string name="submission_test_result_dialog_remove_test_title">"Test można zeskanować tylko raz."</string> + <!-- XHED: Dialog title for test removal no submission --> + <string name="submission_test_result_dialog_remove_test_title_no_submission">"Czy na pewno chcesz usunąć swój test?"</string> <!-- YTXT: Dialog text for test removal --> <string name="submission_test_result_dialog_remove_test_message">"JeÅ›li usuniesz test, nie bÄ™dziesz mieć możliwoÅ›ci pobrania wyniku swojego testu. Otrzymasz swój wynik testu z oÅ›rodka wykonujÄ…cego testy lub laboratorium niezależnie od ważnoÅ›ci kodu QR. W przypadku zdiagnozowania u Ciebie koronawirusa organ ds. zdrowia publicznego zostanie powiadomiony przez ustanowiony prawnie kanaÅ‚ komunikacyjny i skontaktuje siÄ™ z TobÄ…."</string> + <!-- YTXT: Dialog text for test removal no submission --> + <string name="submission_test_result_dialog_remove_test_message_no_submission">"JeÅ›li to zrobisz, inne osoby nie otrzymajÄ… ostrzeżeÅ„."</string> <!-- XBUT: Positive button for test removal --> <string name="submission_test_result_dialog_remove_test_button_positive">"UsuÅ„"</string> <!-- XBUT: Negative button for test removal --> @@ -1233,6 +1237,8 @@ <string name="submission_test_result_positive_no_consent_text_3">"PostÄ™puj zgodnie z poleceniami organu ds. zdrowia publicznego i zostaÅ„ w domu, aby nie zarażać innych."</string> <!-- XBUT: Button for giving consent for key sharing --> <string name="submission_test_result_positive_no_consent_button_warn_others">"Ostrzegaj innych"</string> + <!-- XBUT: Button for test removal --> + <string name="submission_test_result_positive_no_consent_button_remove_test">"UsuÅ„ test"</string> <!-- Submission Country Selector --> <!-- XHED: Page title for the submission country selection page --> diff --git a/Corona-Warn-App/src/main/res/values-pl/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values-pl/vaccination_strings.xml index fbf8ad38370692383771ad67cd0f82eaf5a0b287..58bcf8c675e9fa32b8d0c9a27d5841a2e89a2931 100644 --- a/Corona-Warn-App/src/main/res/values-pl/vaccination_strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/vaccination_strings.xml @@ -70,7 +70,9 @@ <!-- XBUT: button for Vaccination Certificate Registration Home Card --> <string name="vaccination_card_register">"Dodaj"</string> <!-- XHED: Homescreen vaccination status card title --> - <string name="vaccination_card_status_title">"Cyfrowy dowód szczepienia"</string> + <string name="vaccination_card_status_title_line_1">"Cyfrowy"</string> + <!-- XHED: Homescreen vaccination status card title --> + <string name="vaccination_card_status_title_line_2">"Dowód szczepienia"</string> <!-- XHED: Homescreen vaccination status card vaccination name --> <string name="vaccination_card_status_vaccination_name">"Szczepienie przeciwko SARS-CoV-2"</string> <!-- XTXT: Homescreen card incomplete vaccination status label --> @@ -84,6 +86,8 @@ <item quantity="few">"PeÅ‚na ochrona poszczepienna za %1$d dni"</item> <item quantity="many">"PeÅ‚na ochrona poszczepienna za %1$d dni"</item> </plurals> + <!-- XTXT: Homescreen card complete vaccination status label --> + <string name="vaccination_card_status_vaccination_complete">"Ważne do %s"</string> <!-- XTXT: Vaccination QR code scan error message--> <string name="error_vc_invalid">"Ten kod QR nie jest ważnym Å›wiadectwem szczepienia.\n\nWiÄ™cej informacji na temat tego, jak otrzymać swoje Å›wiadectwo szczepienia, znajduje siÄ™ na naszej stronie „CzÄ™sto zadawane pytaniaâ€."</string> diff --git a/Corona-Warn-App/src/main/res/values-ro/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-ro/antigen_strings.xml index 245102cd46f431beef2ed97554a2d35b0bf68f95..e048a39527f46d269b62167e6cdc35846330a164 100644 --- a/Corona-Warn-App/src/main/res/values-ro/antigen_strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/antigen_strings.xml @@ -140,6 +140,10 @@ <string name="coronatest_negative_antigen_result_third_info_title">"Eliminare test"</string> <!-- XTXT: coronatest negative antigen result third info body --> <string name="coronatest_negative_antigen_restul_third_info_body">"ȘtergeÈ›i testul din Corona-Warn-App pentru a salva un nou cod de test aici dacă este necesar."</string> + <!-- XTXT: coronatest negative result certificate info title --> + <string name="coronatest_negative_result_certificate_info_title">"Certificat de test"</string> + <!-- XTXT: coronatest negative result certificate info body --> + <string name="coronatest_negative_result_certificate_info_body">"Certificatul de test este disponibil pe tabul „Certificateâ€."</string> <!-- #################################### Rapid Antigen Test Profile diff --git a/Corona-Warn-App/src/main/res/values-ro/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-ro/green_certificate_strings.xml index 348c6a1a29591723d6f8606f85c57b92eb9308d2..ac9cc94ace505c368451612aa96704a73282b1f4 100644 --- a/Corona-Warn-App/src/main/res/values-ro/green_certificate_strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/green_certificate_strings.xml @@ -31,10 +31,23 @@ <string name="detail_green_certificate_test_type">"Test SARS-CoV-2"</string> <!-- XTXT: Detail green certificate card title --> <string name="detail_green_certificate_card_title">"Certificat de test"</string> + <!-- XTXT: Detail green certificate card subtitle --> + <string name="detail_green_certificate_card_subtitle">"Test efectuat pe %1$s %2$s"</string> <!-- XTXT: Detail green certificate travel notice link en --> <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string> <!-- XTXT: Detail green certificate travel notice link de --> <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string> + <!-- XTXT: Detail green certificate menu item: delete --> + <string name="green_certificate_details_menu_item_delete">"Eliminare"</string> + <!-- XTXT: Detail green certificate diaglog title --> + <string name="green_certificate_details_dialog_remove_test_title">"DoriÈ›i să eliminaÈ›i certificatul de test?"</string> + <!-- XTXT: Detail green certificate diaglog message --> + <string name="green_certificate_details_dialog_remove_test_message">"Dacă eliminaÈ›i certificatul de test, nu îl mai puteÈ›i utiliza ca dovadă în aplicaÈ›ie."</string> + <!-- XTXT: Detail green certificate diaglog positive --> + <string name="green_certificate_details_dialog_remove_test_button_positive">"Eliminare"</string> + <!-- XTXT: Detail green certificate diaglog negative --> + <string name="green_certificate_details_dialog_remove_test_button_negative">"Anulare"</string> + <!-- XTXT: Green certificate main screen title --> <string name="certification_screen_title">"Certificate"</string> <!-- XTXT: Green certificate menu item --> @@ -47,4 +60,43 @@ <string name="info_banner_title_2">"Certificat de test COVID"</string> <!-- XTXT: Green certificate info card body --> <string name="info_banner_body">"ÃŽnregistraÈ›i un test pe ecranul iniÈ›ial È™i consimÈ›iÈ›i să primiÈ›i un certificat de test digital. De îndată ce este disponibil certificatul, acesta este afiÈ™at aici."</string> + + <!-- XTXT: Test certificate time --> + <string name="test_certificate_time">"Test efectuat pe %1$s, %2$s"</string> + <!-- XTXT: Test error label --> + <string name="test_certificate_error_label">"Eroare la obÈ›inerea certificatului"</string> + <!-- XBUT: Test error retry button --> + <string name="test_certificate_error_retry_button">"ÃŽncercaÈ›i din nou"</string> + <!-- XTXT: Error text --> + <string name="error_tc_try_again">"Nu a putut fi stabilită o conexiune. ÃŽncercaÈ›i din nou."</string> + <!-- XTXT: Error text --> + <string name="error_tc_dcc_not_supported_by_lab">"Nu puteÈ›i solicita un certificat de test deoarece acest punct de testare nu suportă emiterea de certificate de test. EliminaÈ›i certificatul sau contactaÈ›i hotline-ul tehnic prin InformaÈ›ii aplicaÈ›ie -> Hotline tehnic."</string> + <!-- XTXT: Error text --> + <string name="error_tc_no_network">"Conexiunea dvs. la internet a fost pierdută. VerificaÈ›i conexiunea È™i încercaÈ›i din nou."</string> + <!-- XTXT: Error text --> + <string name="error_tc_e2e_error_call_hotline">"A apărut o eroare. ÃŽncercaÈ›i din nou mai târziu sau contactaÈ›i hotline-ul tehnic prin InformaÈ›ii aplicaÈ›ie -> Hotline tehnic."</string> + <!-- XTXT: Error text --> + <string name="error_tc_try_again_dcc_not_available_yet">"Certificatul dvs. nu este disponibil încă. ÃŽncercaÈ›i din nou. Dacă eroarea persistă, contactaÈ›i hotline-ul tehnic prin InformaÈ›ii aplicaÈ›ie -> Hotline tehnic."</string> + <!-- XTXT: Error text --> + <string name="error_tc_client_error_call_hotline">"A apărut o eroare. ÃŽncercaÈ›i din nou mai târziu sau contactaÈ›i hotline-ul tehnic prin InformaÈ›ii aplicaÈ›ie -> Hotline tehnic."</string> + <!-- XTXT: Error text --> + <string name="error_tc_dcc_expired">"Acest certificat nu mai este de actualitate. ÃŽl puteÈ›i elimina din Corona-Warn-App."</string> + <!-- XBUT: Test error delete button --> + <string name="test_certificate_error_delete_button">"Eliminare certificat de test"</string> + <!-- XTXT: Test error refresh dialog title --> + <string name="test_certificate_refresh_dialog_title">"ÃŽncă există probleme cu cererea dvs."</string> + <!-- XBUT: Test error refresh dialog confirm button --> + <string name="test_certificate_refresh_dialog_confirm_button">"OK"</string> + <!-- XTXT: Test error delete dialog title --> + <string name="test_certificate_delete_dialog_title">"DoriÈ›i să eliminaÈ›i certificatul?"</string> + <!-- XTXT: Test error delete dialog body --> + <string name="test_certificate_delete_dialog_body">"Dacă eliminaÈ›i certificatul, nu îl puteÈ›i solicita din nou."</string> + <!-- XBUT: Test error delete dialog confirm button --> + <string name="test_certificate_delete_dialog_confirm_button">"Eliminare"</string> + <!-- XBUT: Test error delete dialog cancel button --> + <string name="test_certificate_delete_dialog_cancel_button">"Anulare"</string> + <!-- XTXT: Test error card body refreshing --> + <string name="test_certificate_error_label_refreshing">"Certificatul dvs. este în curs de creare..."</string> + <!-- XTXT: Test error card body refreshing --> + <string name="test_certificate_error_refreshing_status">"Certificatul dvs. este în curs de solicitare. Acest lucru poate dura câteva minute..."</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-ro/strings.xml b/Corona-Warn-App/src/main/res/values-ro/strings.xml index fc3960ab897d37077eb075fc61d29a13f771909b..d7b4f42d22aa2686d3695be314c00cf4a06cc7a5 100644 --- a/Corona-Warn-App/src/main/res/values-ro/strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/strings.xml @@ -308,7 +308,7 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Această perioadă este inclusă în calcul."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> -<!-- Dialog part 1--> + <!-- Dialog part 1--> <string name="risk_details_information_body_period_logged">"Riscul dvs. de infectare poate fi calculat doar pentru perioadele în care a fost activă înregistrarea în jurnal a expunerilor. Prin urmare, caracteristica de înregistrare în jurnal trebuie să rămână permanent activă. ÃŽnregistrarea în jurnal a expunerilor acoperă ultimele 14 zile."</string> <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment_under_14_days">"AplicaÈ›ia Corona-Warn a fost instalată acum %s zi(le). Riscul dvs. de infectare este calculat pentru perioadele în care înregistrarea în jurnal a expunerilor a fost activă. Dacă v-aÈ›i întâlnit cu alte persoane È™i înregistrarea în jurnal a expunerilor a fost activă, este calculat riscul dvs. de infectare."</string> @@ -1143,8 +1143,12 @@ <string name="submission_test_result_dialog_tracing_required_button">"OK"</string> <!-- XHED: Dialog title for test removal --> <string name="submission_test_result_dialog_remove_test_title">"Testul poate fi scanat o singură dată."</string> + <!-- XHED: Dialog title for test removal no submission --> + <string name="submission_test_result_dialog_remove_test_title_no_submission">"Sigur doriÈ›i să eliminaÈ›i testul dvs.?"</string> <!-- YTXT: Dialog text for test removal --> <string name="submission_test_result_dialog_remove_test_message">"Dacă È™tergeÈ›i testul, nu veÈ›i mai putea afla rezultatul testului. VeÈ›i primi rezultatul testului dvs. de la centrul sau laboratorul de testare, indiferent de valabilitatea codului QR. Dacă sunteÈ›i diagnosticat cu coronavirus, autoritatea de sănătate publică va fi notificată prin canalul de comunicare prevăzut în mod legal È™i vă va contacta."</string> + <!-- YTXT: Dialog text for test removal no submission --> + <string name="submission_test_result_dialog_remove_test_message_no_submission">"ÃŽn caz afirmativ, alte persoane nu vor fi avertizate."</string> <!-- XBUT: Positive button for test removal --> <string name="submission_test_result_dialog_remove_test_button_positive">"Ștergere"</string> <!-- XBUT: Negative button for test removal --> @@ -1233,6 +1237,8 @@ <string name="submission_test_result_positive_no_consent_text_3">"AsiguraÈ›i-vă că urmaÈ›i instrucÈ›iunile autorității dvs. de sănătate publică È™i că staÈ›i acasă, pentru a nu-i infecta pe ceilalÈ›i."</string> <!-- XBUT: Button for giving consent for key sharing --> <string name="submission_test_result_positive_no_consent_button_warn_others">"AvertizaÈ›i-i pe ceilalÈ›i"</string> + <!-- XBUT: Button for test removal --> + <string name="submission_test_result_positive_no_consent_button_remove_test">"Eliminare test"</string> <!-- Submission Country Selector --> <!-- XHED: Page title for the submission country selection page --> diff --git a/Corona-Warn-App/src/main/res/values-ro/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values-ro/vaccination_strings.xml index 276ff68ee28120c6141de936946719d1c965f212..a6fefa697ef42368e6dcbaaa13d9d0c75db14272 100644 --- a/Corona-Warn-App/src/main/res/values-ro/vaccination_strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/vaccination_strings.xml @@ -70,7 +70,9 @@ <!-- XBUT: button for Vaccination Certificate Registration Home Card --> <string name="vaccination_card_register">"Adăugare"</string> <!-- XHED: Homescreen vaccination status card title --> - <string name="vaccination_card_status_title">"Dovada digitală a vaccinării"</string> + <string name="vaccination_card_status_title_line_1">"Dovada vaccinării"</string> + <!-- XHED: Homescreen vaccination status card title --> + <string name="vaccination_card_status_title_line_2">"digitale"</string> <!-- XHED: Homescreen vaccination status card vaccination name --> <string name="vaccination_card_status_vaccination_name">"Vaccinare SARS-CoV-2"</string> <!-- XTXT: Homescreen card incomplete vaccination status label --> @@ -84,6 +86,8 @@ <item quantity="few">"ProtecÈ›ia prin vaccinare este completă în %1$d zile"</item> <item quantity="many">"ProtecÈ›ia prin vaccinare este completă în %1$d zile"</item> </plurals> + <!-- XTXT: Homescreen card complete vaccination status label --> + <string name="vaccination_card_status_vaccination_complete">"Valabil până la %s"</string> <!-- XTXT: Vaccination QR code scan error message--> <string name="error_vc_invalid">"Acest cod QR nu este un certificat de vaccinare valabil.\n\nPentru mai multe informaÈ›ii despre modul în care puteÈ›i primi certificatul dvs. de vaccinare, consultaÈ›i pagina noastră de întrebări frecvente."</string> diff --git a/Corona-Warn-App/src/main/res/values-tr/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-tr/antigen_strings.xml index 3a2d0403eb184b10f202d8ae22e5c541653714b1..056defb948e5e397527e1c5325cee02c28086d8e 100644 --- a/Corona-Warn-App/src/main/res/values-tr/antigen_strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/antigen_strings.xml @@ -140,6 +140,10 @@ <string name="coronatest_negative_antigen_result_third_info_title">"Testi Kaldır"</string> <!-- XTXT: coronatest negative antigen result third info body --> <string name="coronatest_negative_antigen_restul_third_info_body">"Gerekirse yeni bir test kodu kaydedebilmeniz için lütfen testi Corona-Warn-App\'ten silin."</string> + <!-- XTXT: coronatest negative result certificate info title --> + <string name="coronatest_negative_result_certificate_info_title">"Test Sertifikası"</string> + <!-- XTXT: coronatest negative result certificate info body --> + <string name="coronatest_negative_result_certificate_info_body">"Test sertifikasını \"Sertifikalar\" sekmesinde bulabilirsiniz."</string> <!-- #################################### Rapid Antigen Test Profile diff --git a/Corona-Warn-App/src/main/res/values-tr/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values-tr/green_certificate_strings.xml index fc5992cfe8200a789105192fd25303a52d7474e0..2fdfd113ef26b3330a2f44859b3bd25faa9f0bc0 100644 --- a/Corona-Warn-App/src/main/res/values-tr/green_certificate_strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/green_certificate_strings.xml @@ -8,7 +8,7 @@ <!-- XTXT: Request green certificate body section 2 --> <string name="request_green_certificate_body_2">"Test sertifikası, AB’de geçerli bir negatif test sonucu kanıtıdır (ör. seyahat için)."</string> <!-- XTXT: Request green certificate body section 3 --> - <string name="request_green_certificate_body_3">"Test sertifikası, doÄŸrulama uygulamasının sertifikanızı doÄŸrulamasını saÄŸlayan geçerli deÄŸer içerir."</string> + <string name="request_green_certificate_body_3">"Test sertifikası, doÄŸrulama uygulamasının sertifikanızı doÄŸrulamasını saÄŸlayan verileri içerir."</string> <!-- XBUT: Request green certificate agree button --> <string name="request_green_certificate_agree_button">"Test Sertifikası Talep Et"</string> <!-- XBUT: Request green certificate disagree button --> @@ -31,10 +31,23 @@ <string name="detail_green_certificate_test_type">"SARS-CoV-2 Testi"</string> <!-- XTXT: Detail green certificate card title --> <string name="detail_green_certificate_card_title">"Test Sertifikası"</string> + <!-- XTXT: Detail green certificate card subtitle --> + <string name="detail_green_certificate_card_subtitle">"Testin gerçekleÅŸtirildiÄŸi tarih: %1$s %2$s"</string> <!-- XTXT: Detail green certificate travel notice link en --> <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string> <!-- XTXT: Detail green certificate travel notice link de --> <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string> + <!-- XTXT: Detail green certificate menu item: delete --> + <string name="green_certificate_details_menu_item_delete">"Kaldır"</string> + <!-- XTXT: Detail green certificate diaglog title --> + <string name="green_certificate_details_dialog_remove_test_title">"Test sertifikasını kaldırmak istiyor musunuz?"</string> + <!-- XTXT: Detail green certificate diaglog message --> + <string name="green_certificate_details_dialog_remove_test_message">"Test sertifikasını kaldırırsanız artık bunu uygulamada kanıt olarak kullanamazsınız."</string> + <!-- XTXT: Detail green certificate diaglog positive --> + <string name="green_certificate_details_dialog_remove_test_button_positive">"Kaldır"</string> + <!-- XTXT: Detail green certificate diaglog negative --> + <string name="green_certificate_details_dialog_remove_test_button_negative">"Ä°ptal Et"</string> + <!-- XTXT: Green certificate main screen title --> <string name="certification_screen_title">"Sertifikalar"</string> <!-- XTXT: Green certificate menu item --> @@ -47,4 +60,43 @@ <string name="info_banner_title_2">"COVID Test Sertifikası"</string> <!-- XTXT: Green certificate info card body --> <string name="info_banner_body">"Ana ekranda bir testi kaydedin ve dijital test sertifikası almayı kabul edin. Sertifika düzenlenir düzenlenmez burada görüntülenir."</string> + + <!-- XTXT: Test certificate time --> + <string name="test_certificate_time">"Testin gerçekleÅŸtirildiÄŸi tarih: %1$s, %2$s"</string> + <!-- XTXT: Test error label --> + <string name="test_certificate_error_label">"Sertifika alınırken hata oluÅŸtu"</string> + <!-- XBUT: Test error retry button --> + <string name="test_certificate_error_retry_button">"Tekrar Dene"</string> + <!-- XTXT: Error text --> + <string name="error_tc_try_again">"BaÄŸlantı kurulamadı. Lütfen tekrar deneyin."</string> + <!-- XTXT: Error text --> + <string name="error_tc_dcc_not_supported_by_lab">"Bu test noktasında test sertifikalarının düzenlenmesi desteklenmediÄŸinden bir test sertifikası talep edemezsiniz. Lütfen sertifikayı kaldırın veya Uygulama Bilgileri -> Teknik Yardım Hattı üzerinden teknik yardım hattı ile iletiÅŸime geçin."</string> + <!-- XTXT: Error text --> + <string name="error_tc_no_network">"Ä°nternet baÄŸlantınız kesildi. Lütfen baÄŸlantıyı kontrol edin ve tekrar deneyin."</string> + <!-- XTXT: Error text --> + <string name="error_tc_e2e_error_call_hotline">"Bir hata oluÅŸtu. Lütfen daha sonra tekrar deneyin veya Uygulama Bilgileri -> Teknik Yardım Hattı üzerinden teknik yardım hattı ile iletiÅŸime geçin."</string> + <!-- XTXT: Error text --> + <string name="error_tc_try_again_dcc_not_available_yet">"Sertifikanız henüz çıkmadı. Lütfen tekrar deneyin. Hata devam ederse lütfen Uygulama Bilgileri -> Teknik Yardım Hattı üzerinden teknik yardım hattı ile iletiÅŸime geçin."</string> + <!-- XTXT: Error text --> + <string name="error_tc_client_error_call_hotline">"Bir hata oluÅŸtu. Lütfen daha sonra tekrar deneyin veya Uygulama Bilgileri -> Teknik Yardım Hattı üzerinden teknik yardım hattı ile iletiÅŸime geçin."</string> + <!-- XTXT: Error text --> + <string name="error_tc_dcc_expired">"Bu sertifika artık güncel deÄŸil. Sertifikayı Corona-Warn-App\'ten kaldırabilirsiniz."</string> + <!-- XBUT: Test error delete button --> + <string name="test_certificate_error_delete_button">"Test Sertifikasını Kaldır"</string> + <!-- XTXT: Test error refresh dialog title --> + <string name="test_certificate_refresh_dialog_title">"Yine de talebinizle ilgili problemler var."</string> + <!-- XBUT: Test error refresh dialog confirm button --> + <string name="test_certificate_refresh_dialog_confirm_button">"Tamam"</string> + <!-- XTXT: Test error delete dialog title --> + <string name="test_certificate_delete_dialog_title">"Sertifikayı kaldırmak istiyor musunuz?"</string> + <!-- XTXT: Test error delete dialog body --> + <string name="test_certificate_delete_dialog_body">"Sertifikayı kaldırırsanız tekrar talep edemezsiniz."</string> + <!-- XBUT: Test error delete dialog confirm button --> + <string name="test_certificate_delete_dialog_confirm_button">"Kaldır"</string> + <!-- XBUT: Test error delete dialog cancel button --> + <string name="test_certificate_delete_dialog_cancel_button">"Ä°ptal Et"</string> + <!-- XTXT: Test error card body refreshing --> + <string name="test_certificate_error_label_refreshing">"Sertifikanız oluÅŸturuluyor..."</string> + <!-- XTXT: Test error card body refreshing --> + <string name="test_certificate_error_refreshing_status">"Sertifikanız talep ediliyor. Bu iÅŸlem birkaç dakika sürebilir..."</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml b/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml index 1c074fcc7a663cedc3220ff7ef59d5ccf87cf57a..ae2bd972f7fefc5434b605d4895adea28e209db1 100644 --- a/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/legal_strings.xml @@ -170,13 +170,13 @@ <!-- XHED: Title for privacy card --> <string name="vaccination_privacy_card_title_text" translatable="false">"Veri gizliliÄŸi ve veri güvenliÄŸi"</string> <!-- XTXT: First bulletpoint title for privacy card --> - <string name="vaccination_privacy_card_first_bulletpoint_title_text" translatable="false">"Dijital aşı sertifikasının kullanımı isteÄŸe baÄŸlıdır. Tam aşı korumasının gerçekleÅŸtirildiÄŸine iliÅŸkin kanıt, diÄŸer yöntemlerle de saÄŸlanabilir (örn. sarı aşı kartı ile)."</string> + <string name="vaccination_privacy_card_first_bulletpoint_title_text" translatable="false">"Dijital COVID sertifikasının kullanımı isteÄŸe baÄŸlıdır. Tam aşı korumasının gerçekleÅŸtirildiÄŸine veya test sonucunun negatif çıktığına iliÅŸkin kanıt, diÄŸer yöntemlerle de saÄŸlanabilir."</string> <!-- XTXT: First bulletpoint title for privacy card --> - <string name="vaccination_privacy_card_second_bulletpoint_title_text" translatable="false">"Aşı sertifikası, yaptırdığınız Korona aşılarınıza iliÅŸkin veriler içerir. Yasal açıdan zorunlu durumlarda aşı koruması yaptırdığınızı kanıtlamak için Uygulamada QR kodu göstermeniz yeterli olacaktır. Söz konusu verilerin okunmasını istemiyorsanız, aşı sertifikasını ve QR kodu hiç kimseye göstermeyin."</string> + <string name="vaccination_privacy_card_second_bulletpoint_title_text" translatable="false">"COVID sertifikaları (aşılar veya test sonuçlarınıza iliÅŸkin) kiÅŸisel verilerinizi içerir. Yasal açıdan zorunlu durumlarda, Uygulamadaki QR kodu göstermeniz yeterli olacaktır. Söz konusu verilerin okunmasını istemiyorsanız, sertifikalarınızı ve QR kodu hiç kimseye göstermeyin."</string> <!-- XTXT: First bulletpoint title for privacy card --> - <string name="vaccination_privacy_card_third_bulletpoint_title_text" translatable="false">"QR kodu Uygulamada gösterirseniz ve bu kod, kontrol uygulamasıyla taranırsa, diÄŸer kiÅŸiler sizin tam aşı koruması aldığınızı anlayabilir. Kontrol iÅŸlemi sırasında adınız ve doÄŸum tarihiniz de resmi kontrol uygulamasında görüntülenecektir."</string> + <string name="vaccination_privacy_card_third_bulletpoint_title_text" translatable="false">"Bir kontrol sırasında Uygulamanızdaki bir sertifikanın QR kodunu gösterirseniz, orada bulunan diÄŸer kiÅŸiler test sonucunuzu veya aşı korumanızın tamamlanıp tamamlanmadığını öğrenebilir. Kontrol iÅŸlemi sırasında adınız ve doÄŸum tarihiniz de resmi kontrol uygulamasında görüntülenecektir."</string> <!-- XTXT: First bulletpoint title for privacy card --> - <string name="vaccination_privacy_card_fourth_bulletpoint_title_text" translatable="false">"Aşı sertifikasını istediÄŸiniz zaman Uygulamadan kaldırma seçeneÄŸiniz vardır. Bunu yapıncaya kadar aşı sertifikaları akıllı telefonunuzda kayıtlı kalacaktır."</string> + <string name="vaccination_privacy_card_fourth_bulletpoint_title_text" translatable="false">"COVID sertifikalarınızı istediÄŸiniz zaman Uygulamadan kaldırma seçeneÄŸiniz vardır. Bunu yapıncaya kadar bu sertifikalar akıllı telefonunuzda kayıtlı kalacaktır."</string> <!-- Green Certificate --> <!-- XTXT: Request GC title for privacy card --> diff --git a/Corona-Warn-App/src/main/res/values-tr/strings.xml b/Corona-Warn-App/src/main/res/values-tr/strings.xml index f8752dd07863f43e4e9e0343df0acac4d73b68ff..8e6480e07b70290fd577ddc015940808f82e19a8 100644 --- a/Corona-Warn-App/src/main/res/values-tr/strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/strings.xml @@ -308,7 +308,7 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"Bu dönem hesaplamaya dahil edildi."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> -<!-- Dialog part 1--> + <!-- Dialog part 1--> <string name="risk_details_information_body_period_logged">"Enfeksiyon riskiniz yalnızca maruz kalma günlüğünün etkin olduÄŸu dönemler için hesaplanabilir. Bu nedenle günlüğe kaydetme özelliÄŸinin sürekli etkin kalması gerekir. Maruz kalma günlüğü son 14 günü kapsar."</string> <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Corona-Warn-App %s gün önce yüklendi. Enfeksiyon riskiniz, maruz kalma günlüğünün etkin olduÄŸu dönemler için hesaplanır. BaÅŸka insanlarla karşılaÅŸmışsanız ve bu sırada maruz kalma günlüğü etkindiyse enfeksiyon riskiniz hesaplanır."</string> @@ -1143,8 +1143,12 @@ <string name="submission_test_result_dialog_tracing_required_button">"Tamam"</string> <!-- XHED: Dialog title for test removal --> <string name="submission_test_result_dialog_remove_test_title">"Test yalnızca bir kez taranabilir."</string> + <!-- XHED: Dialog title for test removal no submission --> + <string name="submission_test_result_dialog_remove_test_title_no_submission">"Testi kaldırmak istediÄŸinizden emin misiniz?"</string> <!-- YTXT: Dialog text for test removal --> <string name="submission_test_result_dialog_remove_test_message">"Testi silerseniz test sonucunuzu alamazsınız. QR kod geçerli olsun veya olmasın test sonucunuzu test merkezinden veya laboratuvardan alacaksınız. Koronavirüs tanısı alırsanız kamu saÄŸlığı yetkilisi yasal olarak belirlenen kanal üzerinden bilgilendirilecektir ve kamu saÄŸlığı yetkilisi sizinle iletiÅŸime geçecektir."</string> + <!-- YTXT: Dialog text for test removal no submission --> + <string name="submission_test_result_dialog_remove_test_message_no_submission">"Bu iÅŸlemi gerçekleÅŸtirirseniz diÄŸer kullanıcılar uyarılmaz."</string> <!-- XBUT: Positive button for test removal --> <string name="submission_test_result_dialog_remove_test_button_positive">"Sil"</string> <!-- XBUT: Negative button for test removal --> @@ -1233,6 +1237,8 @@ <string name="submission_test_result_positive_no_consent_text_3">"BaÅŸka insanların enfeksiyona yakalanmaması için lütfen kamu saÄŸlığı yetkilinizin talimatlarına uyun ve evde kalın."</string> <!-- XBUT: Button for giving consent for key sharing --> <string name="submission_test_result_positive_no_consent_button_warn_others">"DiÄŸer Kullanıcıları Uyarın"</string> + <!-- XBUT: Button for test removal --> + <string name="submission_test_result_positive_no_consent_button_remove_test">"Testi Kaldır"</string> <!-- Submission Country Selector --> <!-- XHED: Page title for the submission country selection page --> diff --git a/Corona-Warn-App/src/main/res/values-tr/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values-tr/vaccination_strings.xml index b0657b9bd66120e133e6c702353adb9a6e66e791..111e60df3030ac0922886621b24f2229c52ceb8f 100644 --- a/Corona-Warn-App/src/main/res/values-tr/vaccination_strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/vaccination_strings.xml @@ -70,7 +70,9 @@ <!-- XBUT: button for Vaccination Certificate Registration Home Card --> <string name="vaccination_card_register">"Ekle"</string> <!-- XHED: Homescreen vaccination status card title --> - <string name="vaccination_card_status_title">"Dijital Aşı Kanıtı"</string> + <string name="vaccination_card_status_title_line_1">"Dijital"</string> + <!-- XHED: Homescreen vaccination status card title --> + <string name="vaccination_card_status_title_line_2">"Aşı Kanıtı"</string> <!-- XHED: Homescreen vaccination status card vaccination name --> <string name="vaccination_card_status_vaccination_name">"SARS-CoV-2 Aşısı"</string> <!-- XTXT: Homescreen card incomplete vaccination status label --> @@ -84,6 +86,8 @@ <item quantity="few">"%1$d gün içinde tam aşı koruması"</item> <item quantity="many">"%1$d gün içinde tam aşı koruması"</item> </plurals> + <!-- XTXT: Homescreen card complete vaccination status label --> + <string name="vaccination_card_status_vaccination_complete">"Son geçerlilik tarihi: %s"</string> <!-- XTXT: Vaccination QR code scan error message--> <string name="error_vc_invalid">"Bu QR kodu geçerli bir aşı sertifikası deÄŸildir.\n\nAşı sertifikanızı nasıl alacağınız hakkında daha fazla bilgi için lütfen SSS sayfamıza bakın."</string> diff --git a/Corona-Warn-App/src/main/res/values/antigen_strings.xml b/Corona-Warn-App/src/main/res/values/antigen_strings.xml index 525e5937055eca395d017b6a1745b89c8754106f..9b78fb440d21879e533c1213c19a069e122fbfda 100644 --- a/Corona-Warn-App/src/main/res/values/antigen_strings.xml +++ b/Corona-Warn-App/src/main/res/values/antigen_strings.xml @@ -141,6 +141,10 @@ <string name="coronatest_negative_antigen_result_third_info_title">"Remove Test"</string> <!-- XTXT: coronatest negative antigen result third info body --> <string name="coronatest_negative_antigen_restul_third_info_body">"Please delete the test from the Corona-Warn-App, so that you can save a new test code here if necessary."</string> + <!-- XTXT: coronatest negative result certificate info title --> + <string name="coronatest_negative_result_certificate_info_title">"Test Certificate"</string> + <!-- XTXT: coronatest negative result certificate info body --> + <string name="coronatest_negative_result_certificate_info_body">"The test certificate is available on the “Certificates†tab."</string> <!-- #################################### Rapid Antigen Test Profile diff --git a/Corona-Warn-App/src/main/res/values/green_certificate_strings.xml b/Corona-Warn-App/src/main/res/values/green_certificate_strings.xml index ece39a2784ecdbf679e5cbfca640fee0a8a12d3d..be47fa7e524f0f247498ed274e8647639a7a9727 100644 --- a/Corona-Warn-App/src/main/res/values/green_certificate_strings.xml +++ b/Corona-Warn-App/src/main/res/values/green_certificate_strings.xml @@ -9,7 +9,7 @@ <!-- XTXT: Request green certificate body section 2 --> <string name="request_green_certificate_body_2">"The test certificate is valid proof of a negative test result within the EU (e.g. for travel)."</string> <!-- XTXT: Request green certificate body section 3 --> - <string name="request_green_certificate_body_3">"The test certificate contains valid that enables the verification app to validate your certificate."</string> + <string name="request_green_certificate_body_3">"The test certificate contains data that enables the verification app to validate your certificate."</string> <!-- XBUT: Request green certificate agree button --> <string name="request_green_certificate_agree_button">"Request Test Certificate"</string> <!-- XBUT: Request green certificate disagree button --> @@ -33,21 +33,21 @@ <!-- XTXT: Detail green certificate card title --> <string name="detail_green_certificate_card_title">"Test Certificate"</string> <!-- XTXT: Detail green certificate card subtitle --> - <string name="detail_green_certificate_card_subtitle">"Test durchgeführt am %1$s %2$s"</string> + <string name="detail_green_certificate_card_subtitle">"Test performed on %1$s %2$s"</string> <!-- XTXT: Detail green certificate travel notice link en --> <string name="green_certificate_travel_notice_link_en">"https://reopen.europa.eu/en"</string> <!-- XTXT: Detail green certificate travel notice link de --> <string name="green_certificate_travel_notice_link_de">"https://reopen.europa.eu/de"</string> <!-- XTXT: Detail green certificate menu item: delete --> - <string name="green_certificate_details_menu_item_delete">"Entfernen"</string> + <string name="green_certificate_details_menu_item_delete">"Remove"</string> <!-- XTXT: Detail green certificate diaglog title --> - <string name="green_certificate_details_dialog_remove_test_title">"Wollen Sie das Testzertifikat wirklich entfernen?"</string> + <string name="green_certificate_details_dialog_remove_test_title">"Do you want to remove the test certificate?"</string> <!-- XTXT: Detail green certificate diaglog message --> - <string name="green_certificate_details_dialog_remove_test_message">"Wenn Sie das Testzertifikat entfernen, können Sie es nicht mehr als Nachweis in der App verwenden."</string> + <string name="green_certificate_details_dialog_remove_test_message">"If you remove the test certificate, you can no longer use it as proof in the app."</string> <!-- XTXT: Detail green certificate diaglog positive --> - <string name="green_certificate_details_dialog_remove_test_button_positive">"Abbrechen"</string> + <string name="green_certificate_details_dialog_remove_test_button_positive">"Remove"</string> <!-- XTXT: Detail green certificate diaglog negative --> - <string name="green_certificate_details_dialog_remove_test_button_negative">"Entfernen"</string> + <string name="green_certificate_details_dialog_remove_test_button_negative">"Cancel"</string> <!-- XTXT: Green certificate main screen title --> <string name="certification_screen_title">"Certificates"</string> @@ -61,42 +61,47 @@ <string name="info_banner_title_2">"COVID Test Certificate"</string> <!-- XTXT: Green certificate info card body --> <string name="info_banner_body">"Register a test on the home screen and agree to receive a digital test certificate. As soon as the certificate is available, it is displayed here."</string> + <!-- XTXT: Test certificate time --> - <string name="test_certificate_time">Test durchgeführt am %1$s, %2$s</string> + <string name="test_certificate_time">"Test performed on %1$s, %2$s"</string> <!-- XTXT: Test error label --> - <string name="test_certificate_error_label">Fehler bei der Zertifikatsabfrage</string> + <string name="test_certificate_error_label">"Error during certificate retrieval"</string> <!-- XBUT: Test error retry button --> - <string name="test_certificate_error_retry_button">Nochmal versuchen</string> + <string name="test_certificate_error_retry_button">"Try Again"</string> <!-- XTXT: Error text --> - <string name="error_tc_try_again">Es konnte keine Verbindung hergestellt werden. Bitte versuchen Sie es erneut.</string> + <string name="error_tc_try_again">"Could not establish a connection. Please try again."</string> <!-- XTXT: Error text --> - <string name="error_tc_dcc_not_supported_by_lab">Ein Testzertifikat kann nicht angefordert werden, da diese Teststelle die Ausstellung von Testzertifikaten nicht unterstützt. Bitte entfernen Sie das Zertifikat oder kontaktieren Sie die technische Hotline über App-Informationen -> Technische Hotline.</string> + <string name="error_tc_dcc_not_supported_by_lab">"You cannot request a test certificate because this testing point does not support the issuing of test certificates. Please remove the certificate or contact the technical hotline via App Information -> Technical Hotline."</string> <!-- XTXT: Error text --> - <string name="error_tc_no_network">Ihre Internetverbindung wurde unterbrochen. Bitte prüfen Sie die Verbindung und versuchen Sie es erneut.</string> + <string name="error_tc_no_network">"Your Internet connection was lost. Please check the connection and try again."</string> <!-- XTXT: Error text --> - <string name="error_tc_e2e_error_call_hotline">Ein Fehler ist aufgetreten. Bitte versuchen Sie es später noch einmal oder kontaktieren Sie die technische Hotline über App-Informationen -> Technische Hotline.</string> + <string name="error_tc_e2e_error_call_hotline">"An error occurred. Please try again later or contact the technical hotline via App Information -> Technical Hotline."</string> <!-- XTXT: Error text --> - <string name="error_tc_try_again_dcc_not_available_yet">Ihr Zertifikat liegt noch nicht vor. Bitte versuchen Sie es noch einmal. Sollte der Fehler weiterhin bestehen, kontaktieren Sie bitte die technische Hotline über App-Informationen -> Technische Hotline.</string> + <string name="error_tc_try_again_dcc_not_available_yet">"Your certificate it not available yet. Please try again. If the error persists, please contact the technical hotline via App Information -> Technical Hotline."</string> <!-- XTXT: Error text --> - <string name="error_tc_client_error_call_hotline">Ein Fehler ist aufgetreten. Bitte versuchen Sie es später noch einmal oder kontaktieren Sie die technische Hotline über App-Informationen -> Technische Hotline.></string> + <string name="error_tc_client_error_call_hotline">"An error occurred. Please try again later or contact the technical hotline via App Information -> Technical Hotline."</string> <!-- XTXT: Error text --> - <string name="error_tc_dcc_expired">Das Zertifikat ist nicht mehr aktuell, Sie können es aus der Corona-App entfernen.</string> + <string name="error_tc_dcc_expired">"This certificate is no longer current. You can remove it from the Corona-Warn-App."</string> <!-- XBUT: Test error delete button --> - <string name="test_certificate_error_delete_button">Testzertifikat entfernen</string> + <string name="test_certificate_error_delete_button">"Remove Test Certificate"</string> <!-- XTXT: Test error refresh dialog title --> - <string name="test_certificate_refresh_dialog_title">"Es gibt weiterhin Probleme bei der Abfrage"</string> + <string name="test_certificate_refresh_dialog_title">"There are still problems with the request."</string> <!-- XBUT: Test error refresh dialog confirm button --> <string name="test_certificate_refresh_dialog_confirm_button">"OK"</string> <!-- XTXT: Test error delete dialog title --> - <string name="test_certificate_delete_dialog_title">"Wollen Sie das Zertifikat wirklich entfernen?"</string> + <string name="test_certificate_delete_dialog_title">"Do you want to remove the certificate?"</string> <!-- XTXT: Test error delete dialog body --> - <string name="test_certificate_delete_dialog_body">"Wenn das Zertifikat enfernt wird, kann es nicht noch einmal angefordert werden."</string> + <string name="test_certificate_delete_dialog_body">"If you remove the certificate, you cannot request it again."</string> <!-- XBUT: Test error delete dialog confirm button --> - <string name="test_certificate_delete_dialog_confirm_button">"Entfernen"</string> + <string name="test_certificate_delete_dialog_confirm_button">"Remove"</string> <!-- XBUT: Test error delete dialog cancel button --> - <string name="test_certificate_delete_dialog_cancel_button">"Abbrechen"</string> + <string name="test_certificate_delete_dialog_cancel_button">"Cancel"</string> <!-- XTXT: Test error card body refreshing --> - <string name="test_certificate_error_label_refreshing">"Ihr Zertifikat wird gerade erstellt…"</string> + <string name="test_certificate_error_label_refreshing">"Your certificate is being created..."</string> <!-- XTXT: Test error card body refreshing --> - <string name="test_certificate_error_refreshing_status">"Ihr Zertifikat wird gerade angefragt, dies kann einige Minuten dauern…"</string> -</resources> + <string name="test_certificate_error_refreshing_status">"Your certificate is being requested. This may take a few minutes..."</string> + <!-- XBUT: Text for invalid test certificate error button, linking to FAQ--> + <string name="test_certificate_error_invalid_labid_faq"></string> + <!-- XTXT: Explains user about test certificate: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#vac_cert_invalid --> + <string name="test_certificate_error_invalid_labid_faq_link"></string> +</resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values/legal_strings.xml b/Corona-Warn-App/src/main/res/values/legal_strings.xml index 876b0c4771416cfb31aa3f09c1a63789bf0ceba4..4ade14fe1a8c80fa96042d6eeeed1f2cb8b679f2 100644 --- a/Corona-Warn-App/src/main/res/values/legal_strings.xml +++ b/Corona-Warn-App/src/main/res/values/legal_strings.xml @@ -171,13 +171,13 @@ <!-- XHED: Title for privacy card --> <string name="vaccination_privacy_card_title_text" translatable="false">"Datenschutz und Datensicherheit"</string> <!-- XTXT: First bulletpoint title for privacy card --> - <string name="vaccination_privacy_card_first_bulletpoint_title_text" translatable="false">"Die Verwendung der digitalen COVID-Zertifikate ist freiwillig. Die Nachweise des vollständigen Impfschutzes, eines negativen Testergebnisses oder einer überstandenen Corona-Infektion können auch auf andere Weise erbracht werden."</string> + <string name="vaccination_privacy_card_first_bulletpoint_title_text" translatable="false">"Using the digital COVID-19 certificates is voluntary. There are other ways to prove that you are fully vaccinated or have a negative test result."</string> <!-- XTXT: First bulletpoint title for privacy card --> - <string name="vaccination_privacy_card_second_bulletpoint_title_text" translatable="false">"Die COVID-Zertifikate enthalten personenbezogene Daten (zu Impfungen, Testergebnissen oder einer überstandenen Infektion). Zum Nachweis in den gesetzlich vorgesehenen Fällen genügt das Vorzeigen des QR-Codes des Zertifikats in der App. Bei der Prüfung ist gegebenenfalls ein Ausweisdokument vorzulegen. Stellen Sie die Zertifikate und die QR-Codes niemandem zur Verfügung, wenn Sie nicht wollen, dass die Daten ausgelesen werden."</string> + <string name="vaccination_privacy_card_second_bulletpoint_title_text" translatable="false">"The COVID-19 certificates contain your personal data (concerning vaccinations or your test result). In cases where you are required by law to prove your vaccination status or test result, it is sufficient to show the QR code of the certificate in the app. Do not provide your certificates or the QR codes to anyone if you do not want the data to be read."</string> <!-- XTXT: First bulletpoint title for privacy card --> - <string name="vaccination_privacy_card_third_bulletpoint_title_text" translatable="false">"Wenn Sie den QR-Code eines Zertifikats in der App zur Prüfung vorzeigen, können andere Personen nachvollziehen, dass ein negatives Testergebnis, ein vollständiger Impfschutz oder ein Nachweis über eine überstandene Infektion vorliegt. Bei der Prüfung werden in der offiziellen Prüf-App auch der Name und das Geburtsdatum angezeigt. Bei Testzertifikaten wird zusätzlich auch das Datum der Probenahme angezeigt."</string> + <string name="vaccination_privacy_card_third_bulletpoint_title_text" translatable="false">"When you present the QR code of a certificate in the app for verification, others will be able to find out your test result or whether you are fully vaccinated. During verification, the official verification app will also display your name and date of birth."</string> <!-- XTXT: First bulletpoint title for privacy card --> - <string name="vaccination_privacy_card_fourth_bulletpoint_title_text" translatable="false">"Sie haben jederzeit die Möglichkeit, COVID-Zertifikate in der App wieder zu entfernen. Bis dahin bleiben die Zertifikate auf Ihrem Smartphone gespeichert."</string> + <string name="vaccination_privacy_card_fourth_bulletpoint_title_text" translatable="false">"You have the possibility to delete COVID-19 certificates in the app at any time. Until then, the certificates will be stored on your smartphone."</string> <!-- Green Certificate --> <!-- XTXT: Request GC title for privacy card --> diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index 4e9ce9aeabce50b87ad4a6fbdb1c58889e1f57f1..bbe46a803d93da0c18070d0d0196e90eb7ae68bb 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -1145,11 +1145,11 @@ <!-- XHED: Dialog title for test removal --> <string name="submission_test_result_dialog_remove_test_title">"The test can only be scanned once."</string> <!-- XHED: Dialog title for test removal no submission --> - <string name="submission_test_result_dialog_remove_test_title_no_submission">"Sind Sie sicher, dass Sie Ihren Test entfernen wollen?"</string> + <string name="submission_test_result_dialog_remove_test_title_no_submission">"Are you sure you want to remove your test?"</string> <!-- YTXT: Dialog text for test removal --> <string name="submission_test_result_dialog_remove_test_message">"If you delete the test, you can no longer retrieve your test result. You will receive your test result from the test center or laboratory regardless of the validity of the QR code. If you are diagnosed with coronavirus, the public health authority will be notified through the legally prescribed channel and will contact you."</string> <!-- YTXT: Dialog text for test removal no submission --> - <string name="submission_test_result_dialog_remove_test_message_no_submission">"Dadurch werden andere nicht gewarnt."</string> + <string name="submission_test_result_dialog_remove_test_message_no_submission">"If you do, others will not be warned."</string> <!-- XBUT: Positive button for test removal --> <string name="submission_test_result_dialog_remove_test_button_positive">"Delete"</string> <!-- XBUT: Negative button for test removal --> @@ -1239,7 +1239,7 @@ <!-- XBUT: Button for giving consent for key sharing --> <string name="submission_test_result_positive_no_consent_button_warn_others">"Warn Others"</string> <!-- XBUT: Button for test removal --> - <string name="submission_test_result_positive_no_consent_button_remove_test">Test entfernen</string> + <string name="submission_test_result_positive_no_consent_button_remove_test">"Remove Test"</string> <!-- Submission Country Selector --> <!-- XHED: Page title for the submission country selection page --> diff --git a/Corona-Warn-App/src/main/res/values/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values/vaccination_strings.xml index 7aa5ac42c7436ee03bacb7b5eca9c6748f669702..83b31ca56ea4b650ac899d9679bdd37c874209fc 100644 --- a/Corona-Warn-App/src/main/res/values/vaccination_strings.xml +++ b/Corona-Warn-App/src/main/res/values/vaccination_strings.xml @@ -71,11 +71,9 @@ <!-- XBUT: button for Vaccination Certificate Registration Home Card --> <string name="vaccination_card_register">"Add"</string> <!-- XHED: Homescreen vaccination status card title --> - <string name="vaccination_card_status_title">"Digital Proof of Vaccination"</string> + <string name="vaccination_card_status_title_line_1">"Digital"</string> <!-- XHED: Homescreen vaccination status card title --> - <string name="vaccination_card_status_title_line_1">"Digitaler"</string> - <!-- XHED: Homescreen vaccination status card title --> - <string name="vaccination_card_status_title_line_2">"Impfnachweis"</string> + <string name="vaccination_card_status_title_line_2">"Proof of Vaccination"</string> <!-- XHED: Homescreen vaccination status card vaccination name --> <string name="vaccination_card_status_vaccination_name">"SARS-CoV-2 Vaccination"</string> <!-- XTXT: Homescreen card incomplete vaccination status label --> @@ -90,7 +88,8 @@ <item quantity="many">"Full vaccination protection in %1$d days"</item> </plurals> <!-- XTXT: Homescreen card complete vaccination status label --> - <string name="vaccination_card_status_vaccination_complete">"Gültig bis einschließlich %s"</string> + <string name="vaccination_card_status_vaccination_complete">"Valid through %s"</string> + <!-- XTXT: Vaccination QR code scan error message--> <string name="error_vc_invalid">"This QR code is not a valid vaccination certificate.\n\nFor more information about how to receive your vaccination certificate, please see our FAQ page."</string> <!-- XTXT: Vaccination QR code scan error message--> @@ -118,4 +117,4 @@ <string name="vaccination_certificate_subtitle">"EU Digitales COVID-Zertifikat"</string> -</resources> +</resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt index 329aed6fb188a493fc6a7197f41e29b5ee9628f9..20309b770c3034c9cfe29faaf8f245e013842c6e 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.bugreporting.debuglog import android.app.Application +import android.content.pm.PackageManager import dagger.Lazy import de.rki.coronawarnapp.bugreporting.censors.BugCensor import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLogTree @@ -10,11 +11,14 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldEndWith import io.kotest.matchers.string.shouldStartWith +import io.mockk.Called import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.mockk import io.mockk.mockkObject +import io.mockk.verify import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.test.runBlockingTest @@ -31,6 +35,7 @@ import java.io.File class DebugLoggerTest : BaseIOTest() { @MockK lateinit var application: Application + @MockK lateinit var packageManager: PackageManager @MockK lateinit var component: ApplicationComponent @MockK lateinit var coronaTestCensor1: BugCensor @MockK lateinit var coronaTestCensor2: BugCensor @@ -51,6 +56,8 @@ class DebugLoggerTest : BaseIOTest() { testDir.exists() shouldBe true every { application.cacheDir } returns cacheDir + every { application.packageManager } returns packageManager + every { component.inject(any<DebugLogger>()) } answers { val logger = arg<DebugLogger>(0) logger.bugCensors = Lazy { setOf(coronaTestCensor1, coronaTestCensor2) } @@ -102,9 +109,13 @@ class DebugLoggerTest : BaseIOTest() { } @Test - fun `init calls start if it is a tester build`() = runBlockingTest { + fun `init calls start if it is a tester build and autologger pkg is installed`() = runBlockingTest { every { CWADebug.isDeviceForTestersBuild } returns true + every { + packageManager.getPackageInfo("de.rki.coronawarnapp.els.autologger", 0) + } returns mockk() + val instance = createInstance(scope = this).apply { init() setInjectionIsReady(component) @@ -116,6 +127,59 @@ class DebugLoggerTest : BaseIOTest() { instance.stop() } + @Test + fun `init does not call start on tester builds without the autologger pkg`() = runBlockingTest { + every { CWADebug.isDeviceForTestersBuild } returns true + + every { application.packageManager } returns mockk<PackageManager>().apply { + every { getPackageInfo(any<String>(), any()) } throws PackageManager.NameNotFoundException() + } + + val instance = createInstance(scope = this).apply { + init() + setInjectionIsReady(component) + isLogging.value shouldBe false + } + + runningLog.exists() shouldBe false + + instance.stop() + } + + @Test + fun `init does not call start on tester builds with ROM issues`() = runBlockingTest { + every { CWADebug.isDeviceForTestersBuild } returns true + + every { application.packageManager } throws SecurityException() + + val instance = createInstance(scope = this).apply { + init() + setInjectionIsReady(component) + isLogging.value shouldBe false + } + + runningLog.exists() shouldBe false + + instance.stop() + } + + @Test + fun `package check is not executed in PROD`() = runBlockingTest { + every { CWADebug.isDeviceForTestersBuild } returns false + + val instance = createInstance(scope = this).apply { + init() + setInjectionIsReady(component) + isLogging.value shouldBe false + } + + runningLog.exists() shouldBe false + + instance.stop() + + verify { packageManager wasNot Called } + } + @Test fun `start plants a tree and starts a logging coroutine`() = runBlockingTest { val instance = createInstance(scope = this).apply { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt index 7eb566a58f9810a6458f0c3499b0d2d6243e4a1a..3ec942256f9095bacf839def1c1ed7a44e6b5f64 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt @@ -1,7 +1,9 @@ package de.rki.coronawarnapp.coronatest import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.coronatest.errors.DuplicateCoronaTestException import de.rki.coronawarnapp.coronatest.migration.PCRTestMigration +import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.storage.CoronaTestStorage import de.rki.coronawarnapp.coronatest.type.CoronaTest @@ -9,6 +11,8 @@ import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest import de.rki.coronawarnapp.coronatest.type.pcr.PCRTestProcessor import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest import de.rki.coronawarnapp.coronatest.type.rapidantigen.RATestProcessor +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.coEvery @@ -29,20 +33,28 @@ class CoronaTestRepositoryTest : BaseTest() { @MockK lateinit var legacyMigration: PCRTestMigration @MockK lateinit var contactDiaryRepository: ContactDiaryRepository + @MockK lateinit var pcrProcessor: PCRTestProcessor + @MockK lateinit var raProcessor: RATestProcessor + private var coronaTestsInStorage = mutableSetOf<CoronaTest>() + private val pcrRegistrationRequest = CoronaTestQRCode.PCR( + qrCodeGUID = "pcr-guid" + ) private val pcrTest = PCRCoronaTest( - identifier = "pcr-identifier", + identifier = pcrRegistrationRequest.identifier, lastUpdatedAt = Instant.EPOCH, registeredAt = Instant.EPOCH, registrationToken = "token", testResult = CoronaTestResult.PCR_REDEEMED, ) - @MockK lateinit var pcrProcessor: PCRTestProcessor - @MockK lateinit var raProcessor: RATestProcessor + private val raRegistrationRequest = CoronaTestQRCode.RapidAntigen( + hash = "ra-hash", + createdAt = Instant.EPOCH + ) private val raTest = RACoronaTest( - identifier = "ra-identifier", + identifier = raRegistrationRequest.identifier, lastUpdatedAt = Instant.EPOCH, registeredAt = Instant.EPOCH, registrationToken = "token", @@ -54,9 +66,6 @@ class CoronaTestRepositoryTest : BaseTest() { fun setup() { MockKAnnotations.init(this) - coronaTestsInStorage.add(pcrTest) - coronaTestsInStorage.add(raTest) - legacyMigration.apply { coEvery { startMigration() } returns emptySet() coEvery { finishMigration() } just Runs @@ -75,11 +84,13 @@ class CoronaTestRepositoryTest : BaseTest() { } pcrProcessor.apply { + coEvery { create(pcrRegistrationRequest) } returns pcrTest coEvery { updateSubmissionConsent(any(), any()) } answers { arg<PCRCoronaTest>(0) } every { type } returns CoronaTest.Type.PCR } raProcessor.apply { + coEvery { create(raRegistrationRequest) } returns raTest coEvery { updateSubmissionConsent(any(), any()) } answers { arg<RACoronaTest>(0) } every { type } returns CoronaTest.Type.RAPID_ANTIGEN } @@ -96,8 +107,31 @@ class CoronaTestRepositoryTest : BaseTest() { @Test fun `give submission consent`() = runBlockingTest2(ignoreActive = true) { + coronaTestsInStorage.add(pcrTest) + createInstance(this).updateSubmissionConsent(pcrTest.identifier, true) coVerify { pcrProcessor.updateSubmissionConsent(pcrTest, true) } } + + @Test + fun `test registration with default conditions`() = runBlockingTest2(ignoreActive = true) { + coronaTestsInStorage.clear() + val negativePcr = pcrTest.copy(testResult = CoronaTestResult.PCR_NEGATIVE) + coEvery { pcrProcessor.create(pcrRegistrationRequest) } returns negativePcr + val instance = createInstance(this) + + instance.registerTest(pcrRegistrationRequest) shouldBe negativePcr + } + + @Test + fun `test registration with default conditions and existing test`() = runBlockingTest2(ignoreActive = true) { + coronaTestsInStorage.add(pcrTest) + + val instance = createInstance(this) + + shouldThrow<DuplicateCoronaTestException> { + instance.registerTest(pcrRegistrationRequest) shouldBe pcrTest + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt index 9076ba3b7c9e0cda0069939b58e8889d959f2a90..bc48dcd234cb9e7c0bf0c6b43a30560bd3bd1d0f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt @@ -169,7 +169,8 @@ class VerificationApiV1Test : BaseIOTest() { requestBody ) shouldBe VerificationApiV1.TestResultResponse( testResult = 1, - sampleCollectedAt = null + sampleCollectedAt = null, + labId = null, ) webServer.takeRequest(5, TimeUnit.SECONDS)!!.apply { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt index 07f8cbc45e513d5797afe10e4a6fbe92dc193e0a..1f68b62c1311a2ec9e3fa64ab6bd967082c30b09 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt @@ -166,12 +166,13 @@ class VerificationServerTest : BaseIOTest() { registrationToken shouldBe "testRegistrationToken" requestPadding.length shouldBe 170 } - VerificationApiV1.TestResultResponse(testResult = 2, sampleCollectedAt = null) + VerificationApiV1.TestResultResponse(testResult = 2, sampleCollectedAt = null, labId = null) } server.pollTestResult("testRegistrationToken") shouldBe CoronaTestResultResponse( coronaTestResult = CoronaTestResult.PCR_POSITIVE, - sampleCollectedAt = null + sampleCollectedAt = null, + labId = null, ) coVerify { verificationApi.getTestResult(any(), any(), any()) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt index 04e5a7110f6f46ccf7ebac591793fc89b0892b98..d11874da7c5eebd5dd5b51d64c768446a6d66716 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt @@ -67,6 +67,7 @@ class PCRProcessorTest : BaseTest() { coEvery { checkTestResult(any()) } returns CoronaTestResultResponse( coronaTestResult = PCR_OR_RAT_PENDING, sampleCollectedAt = null, + labId = null, ) coEvery { registerTest(any()) } answers { val request = arg<RegistrationRequest>(0) @@ -76,6 +77,7 @@ class PCRProcessorTest : BaseTest() { testResultResponse = CoronaTestResultResponse( coronaTestResult = PCR_OR_RAT_PENDING, sampleCollectedAt = null, + labId = null, ), ) } @@ -131,6 +133,7 @@ class PCRProcessorTest : BaseTest() { testResultResponse = CoronaTestResultResponse( coronaTestResult = PCR_OR_RAT_PENDING, sampleCollectedAt = null, + labId = null, ) ) coEvery { submissionService.registerTest(any()) } answers { registrationData } @@ -144,6 +147,7 @@ class PCRProcessorTest : BaseTest() { testResultResponse = CoronaTestResultResponse( coronaTestResult = it, sampleCollectedAt = null, + labId = null, ) ) when (it) { @@ -170,6 +174,7 @@ class PCRProcessorTest : BaseTest() { CoronaTestResultResponse( coronaTestResult = pollResult, sampleCollectedAt = null, + labId = null, ) } @@ -222,6 +227,7 @@ class PCRProcessorTest : BaseTest() { CoronaTestResultResponse( coronaTestResult = PCR_POSITIVE, sampleCollectedAt = null, + labId = null, ) } @@ -284,6 +290,7 @@ class PCRProcessorTest : BaseTest() { testResultResponse = CoronaTestResultResponse( coronaTestResult = PCR_OR_RAT_PENDING, sampleCollectedAt = null, + labId = null, ) ) coEvery { submissionService.registerTest(any()) } answers { registrationData } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessorTest.kt index 2846d0f54b9f17b104b72c59e83d4295f94f8f4f..c9c60d3474d0b078bf79ca3596f3af9d6e4ab95b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessorTest.kt @@ -67,6 +67,7 @@ class RAProcessorTest : BaseTest() { coEvery { checkTestResult(any()) } returns CoronaTestResultResponse( coronaTestResult = PCR_OR_RAT_PENDING, sampleCollectedAt = null, + labId = null, ) coEvery { registerTest(any()) } answers { @@ -77,6 +78,7 @@ class RAProcessorTest : BaseTest() { testResultResponse = CoronaTestResultResponse( coronaTestResult = PCR_OR_RAT_PENDING, sampleCollectedAt = null, + labId = null, ) ) } @@ -121,6 +123,7 @@ class RAProcessorTest : BaseTest() { coEvery { submissionService.checkTestResult(any()) } returns CoronaTestResultResponse( coronaTestResult = PCR_OR_RAT_PENDING, sampleCollectedAt = nowUTC, + labId = null, ) (instance.pollServer(raTest) as RACoronaTest).sampleCollectedAt shouldBe nowUTC @@ -171,6 +174,7 @@ class RAProcessorTest : BaseTest() { testResultResponse = CoronaTestResultResponse( coronaTestResult = PCR_OR_RAT_PENDING, sampleCollectedAt = null, + labId = null, ), ) coEvery { submissionService.registerTest(any()) } answers { registrationData } @@ -187,6 +191,7 @@ class RAProcessorTest : BaseTest() { testResultResponse = CoronaTestResultResponse( coronaTestResult = it, sampleCollectedAt = null, + labId = null, ) ) when (it) { @@ -212,6 +217,7 @@ class RAProcessorTest : BaseTest() { CoronaTestResultResponse( coronaTestResult = pollResult, sampleCollectedAt = null, + labId = null, ) } @@ -245,6 +251,7 @@ class RAProcessorTest : BaseTest() { CoronaTestResultResponse( coronaTestResult = RAT_POSITIVE, sampleCollectedAt = null, + labId = null, ) } @@ -312,6 +319,7 @@ class RAProcessorTest : BaseTest() { testResultResponse = CoronaTestResultResponse( coronaTestResult = PCR_OR_RAT_PENDING, sampleCollectedAt = null, + labId = null, ) ) coEvery { submissionService.registerTest(any()) } answers { registrationData } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateTestData.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateTestData.kt index e6da753eb92a102b7916032147d183d729219e3d..3082a41b586ef74bbeed644dd8f272421d2d9869 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateTestData.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/TestCertificateTestData.kt @@ -61,7 +61,8 @@ class TestCertificateTestData @Inject constructor( rsaPrivateKey = privateKey, certificateReceivedAt = Instant.ofEpochMilli(123456789), encryptedDataEncryptionkey = "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk=".decodeBase64()!!, - testCertificateQrCode = personATest2CertQRCodeString + testCertificateQrCode = personATest2CertQRCodeString, + certificateSeenByUser = true ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/storage/TestCertificateStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/storage/TestCertificateStorageTest.kt index 9b486cef84ffea31dab4c38fb137d0b0c034678b..983f9549a849c04cde7d0ee2b517b9557f1a0070 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/storage/TestCertificateStorageTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/test/storage/TestCertificateStorageTest.kt @@ -79,7 +79,8 @@ class TestCertificateStorageTest : BaseTest() { "rsaPrivateKey": "MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQDb5YIK/LRI2pkzL9XUVhgyQFMan/kFC383FRbWciSPzi7p26o01CHgaO6CgNzx6nKX/KLcPrUm3uoCvCT55Nydbj+MQ57FPmkJptEqsWbogyd1J59is/cJ5oPzplTju6GdG4IbdNw+EipQ3YTfsRs6wj1nn0ldzm2aYHvULFMXRuMTQ80uBtNTayeew7r6jP8kujsPafDoT3LFInwkGMzmP4ULixgu47adiL8HMAZkv/CwqtqODaGMDd/yvVyCy3wd3MAmMxQb3SlvdQbYrYpVFJxu8Xym9GvMsK8n+CoAYNdf8zTSOvjTu9TxXg7YD1h1Jq9oLTaJEVM1qKXrlxs5b3qnem2N56Oh+PyXk87l4FhYRGVrw2uUP0Rxb3f5pKazCfjD2JJKr6PfwYzKFi6Utw6WEpN5a6ZzmCgONwfKuLjwqLvR10bOvEXGCBoL8WqUIbzkDr/jd9VpYf9aLa48M/NpEY1DNdHLgsK4T929TOnxOWSlxPRAXL9Dgp7S7V0CAwEAAQKCAYBaazDh2682FczQ42aFfTFN2G1TkVwP2v5gY+eUHjMyfpGDz7NZLbEQWZVZTCuNvd2I6XT+IzrR1O9cWIjLyHN+uIqg3l02tcbzFQkFCRVLnkJnRfef2mhGRecUFNzrF4gI1frV12OIkmecALpWULjlnGErbq/4Rp2C0RGZ2PABrkBI96QyvNPAhVsxSUJlK/zt2TXXzLQmkiSbMubg4OG/+3Z1nKhA/5ljhYsnJXQ7kUEjI93ic3Bt6naflYWosop/jUa1QksEMv0HL2if8PIBymgTGKmU79MeQOuBJN0ggrmttk41df+lPzWQY0EFnBC7Kf1AtbenllDm8zCoqldwu5OBTp8pZs7vFbOaRp1zBdtQS9OeTy22HvRU14CMwJ7HXOUC4RuVhXXeNLqLjLEkXJPRGvUem0Wq+ppBliDDoq9ljHiqvR/LgnaH0OqxM6o4fo1OgKvgVhJ1ItPeTdYxu2ikuJUNzwFf32feectjncXUf18wF1OExlwgVpvTinECgcEA7lnLCAdufw18Moe2VqudLmU2vUsJl1SR2nLlIYNfM7bHlbXqT/Ido2odKXX8WVDZi/ChV43OAw2PKUgcVPIxSGmEiDg8bj+K+v8hZ/VFbQAjnfD9+olikRbNmFMOued2IazfFv2ydbZADjPDMcfK1W3+7qcHT2LxigEWB8XNA5NDBaMYU+EN+tATOcG0QZr3fNPxfUT4m4TKOY00jhBdOhubfyF5pU5rQQCZvkVqVIffcq6J1x7Jh7CGLwQ53lQ7AoHBAOwt5N4/GY/pFiIE/V85MlJN37HfBhB8K29CEPzqOdHfICnYZ3dqNXtXIAQqVE0lG+49O5moQjU/dTAr39kJwzDydzJaFsCGsR/rzxo+Ishz2SrjJ8+97g8B6Oxgy9qwMs9X5A+EvrWw5Lb3woZDjaZ0pPl4yb7y5IEDlYnNM/9QcmHFP0IFK2h6S3Qmm0XjboeVe1POz0oPD3z+xYruCnKr1Vj/X5eiLIbt2hxWlVQ9N+tvufFusR+OdgsBhxijRwKBwQCvfaN0ZOxhVY91MOD6zV5sc48rLl2Ac370JRY5Z52n2NL4krlTZYOW9yFDjqBfLp0OYPyaF0lwjAI1NefOT4gjtbUkCqvLzLNKfKCfB0K3r5uJxY9qcM8G3pA/sB+ulxIuVzbmmaJU8vwUuN3mACGCpXtHQemq9MG8h3IuBOAe2sVFGEFoONLvMVaGdu1+RFgmK3KpdifJcarnVuU0GC5cA0mo//+ty6BCeuu34SoZ1PSbXpEUt5FQe5NAeM8WuFMCgcEAuaA0kqz7dU1YVPKhBZeZwnBsUYudY5WEOdSuL2oUeawpxlnMsGFsmX1Xr45pZZy2ACBmWJWTO/CdNXg2Xoo6vJzFLHD8EuOKETGwO8r8YZoT5I5WuwNnOKpinG5TqpTzyl0k5UGK9piKmnfOjuJHUb258E2MGyUijXf4ry72IEPlMozp9ATGIj6EUU0Kmvpu4+eL38nayDVgEfjX4CLJWWlOrL1CL5aJ8p6836r5gRUAf233shcy5T997ZaMzMN/AoHADfrS372Vuovx21p8txO2w+VFTEUoR80XGGdy30NrIdweY2bfz4XYpGSiyXE41TWzpNBfrSZNCyBXzvJ7d3dBXhlruFZi3Ji3IR+fe+KpEz4FTssKLEWm+gSmbGIjFxGe0nAIy77jCMYjqfOjoFdhksQN1On1tcq3Y3XauAc4L82wDU30rOgxWt8kdbblJKCSdOaYPXm/D+4c+8ROvlcxY4afl+FDcroHNMvD3jjZ1TMd1Bef1E0qFN/oJJU2Pc2/", "certificateReceivedAt": 123456789, "encryptedDataEncryptionkey": "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk\u003d", - "testCertificateQrCode": "${certificateTestData.personATest1CertQRCodeString}" + "testCertificateQrCode": "${certificateTestData.personATest1CertQRCodeString}", + "certificateSeenByUser": false } ] """.toComparableJsonPretty() @@ -95,7 +96,8 @@ class TestCertificateStorageTest : BaseTest() { "rsaPrivateKey": "MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCesnw9uZs6YQSYcH/zKDVueRRPuNwXhd/LYx7mKYmAtVhDRwv1Q0/3Vx8VSuAwVXd8c5zRS5rLXROasEtoR3OoMRlAfgqPS5n6FUK3Oe1UoW+K5SiHTzDEWWiC1zlLLQ2G5MbILVb+LYBfVIO963bExxzV8TjishNAQQCUbz6d8alumkUd7NQ7G5mOz1cFPFq8ONDJZ4XAwPfRQAkZqQ+E446PqKFcDX069D+1i5hMnkL1DN4r3QL+thuDZ+f6izcQaVvuIlnZXcoas0t0xwS2SIbkiIeJxo0Kfw5L9wjd3b2IkTgQJoXvXV/IXwwbUfa1uSNQtLduw81S/K8d8zZyM49GF6NrI6H4LFCRzfApqvwCm9AKOJVFbTmc7S58d08xxYmFSVyFZg7mY4lNI7y8b/1iqmdO4NzYZ6VziAwrsoBN0fe/UWwAI8o0l2m3rwh9leAZifku0nvMkHpjDxF7CPVJoX/AUpelh37knIrXzAlMtVhKmIPOJ2HgM/5HplMCAwEAAQKCAYEAhw8Bu4pduFZfEdkUm31J0+YJyjtaXE6cAr0ty9Xn5vjuz/sEC0ypHqgvlPBvUdM66FiASoMcjxx8lbaZxnqgzLBUfFWIaSF/Pp2fdM5A1Di79CpIzrcvmrs4vbmrUfZav8WuAyjLE3DoArmrkRN2tct7F/y+W/gPeCyZ8LmoQcUsXCvAzNIEYPWBP0/oEFWoJu33iqCm7T+M6LGlzQfbZE5BwrNR+ESmomjCW6AdEn/SHjlAT3Y9mUakrbXdcJXPAI+RleS90kn8AHiQuyjotlb32xhBVw6SOtfd0xkMyY67AbCo9R1f0ir54PayA38xs4yQ0O2OgNUSLTWYXV1T/mSQSQMaxNw556IEXWVQWRWIc91QwOI2TD/N+vIxLPbNtuW5lEyMCzrmBdxq7wIOGIpy62B11TW6UYU26GOkhHTXEnn7pmHGVtbCXPGoKzncxxKNhRFuGOPcd+yQkM5eYAf7dad2NySrOokMQ2eIacPwKxKFfA/QN0v9aPLj7Qa5AoHBAOynNxSKErA25ndt/xBaLwSdzPynkE5zkO3gedgqvO6/6bAGOsRkawTNGalkVTwhXEnGBUIidPqhW7ex5/ad1QPUsWT8YeeYzXoM+Gqgu6M8awpFK4cwUMCrpRJwaUBFUCNzYNDZgJoOZAOX2TKvSNH+9zFrmGrKY0KRZ0aK9T0Ksbxn8KrErvXYj05nG4A3MrsKRA8mwBtZm0/17bBtg0nYH6/Omt787LBO/sxCicwTZioJlUgndzAIwTw7BtFDzwKBwQCrq8Wx/MK+LA+HUzDmdTK04sgIebulBSTV95aSsWoN+MoNwmi9wVt3OJfpq4L7g1NVv0vNSIajk9BDIFeKvgmDcde8RV51LRCObQ9enCOQUH7e0eC3XI9Nxg3nIhQiYJuggG6QAtt07bybx3dWpYEXL4ZOPOEkTVXR5JRkx9MWw8VDTbLKTCfZJ34PPF4AdCrs3yId9FXk9pUmS68oJVRsFhnI+dSdky32Bc01G6kk0SlGOudKLzqx4fbr0itHIj0CgcAHDWCd0xOFfs1VZ8i/EwDtsUoniVLKk7UQ8ayP3Y4tyzhKj5T2v0tVJEuMebn0hcX7SNRlSSOVSHO0QK/58HAlohP7P24nea094t8QRmPxFF7YOoF2kOEHLNZJe2IXkTk3JTwQXTrw3FbsqHzHfuO7pk51gZBUNl3I4Q5j0sZGIGh1hd9tJ1lTaDW1D2uJYZu4aTDoBq6Y4g23z0tbA5hy/ebL1WtWE9F125TKP31dwII95HU3Zj2uB8TCZ7vnRo8CgcBfeUiZlFk6Kob4W+v2P3fT4cwd6pXRUOsLlIbJTqIM4zB8NoLKBZ84zuCttBVEi+Ts61bc9Fjs4GgS7QnCv63KzKWOr4W45Tcv/rdthqjAugPVKCQx1ehc+KkCwpEwDUqAGO1kajJi9VTPzj8wkRsaKfQnzvPnnJr+AIIHCpr7LiWnKK8mkvQWcUBKeOhOmEzHL9Fpl1mt3PVWNwFS8m/hLOlqPIdim1gUW2WlA50uPKUXyeqX92xNQb5xqJEpHoECgcEAw4FGJb47FivG25fD+e61GxzG/KrzQL0eVS3T2YRAiN5ZB7QyInm6vMTi0QKCScCRJjOjRyoI3VtCO7G8vUnm0UiCW4l11WqW9G4vVh5VuR0HJ+kH1CQcq1aheqF7bbZGjjK47iyZskehfa6kcEOfThE6n6G7mIE/oe5k8A6+wHoLGmBbdxwE2xuG3PorH0PgbAgva1KAgC57rTBJhHnm6ntT21vlPLev9QvrE5syo+LEDbagr5zHMC14qAwMH2fi", "certificateReceivedAt": 123456789, "encryptedDataEncryptionkey": "ZW5jcnlwdGVkRGF0YUVuY3J5cHRpb25rZXk\u003d", - "testCertificateQrCode": "${certificateTestData.personATest2CertQRCodeString}" + "testCertificateQrCode": "${certificateTestData.personATest2CertQRCodeString}", + "certificateSeenByUser": true } ] """.toComparableJsonPretty() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPersonTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPersonTest.kt index bd02d7face41c0a7a78cd705544b2e474caba389..f6b1d884f3e86404ec0b1f77a27b5c76ebabf91b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPersonTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/vaccination/core/VaccinatedPersonTest.kt @@ -85,14 +85,22 @@ class VaccinatedPersonTest : BaseTest() { ) vaccinatedPerson.apply { + // Less than 14 days getVaccinationStatus( Instant.parse("2021-04-27T12:00:00.000Z") ) shouldBe VaccinatedPerson.Status.COMPLETE getVaccinationStatus( Instant.parse("2021-05-10T12:00:00.000Z") ) shouldBe VaccinatedPerson.Status.COMPLETE + + // 14 days exactly getVaccinationStatus( Instant.parse("2021-05-11T12:00:00.000Z") + ) shouldBe VaccinatedPerson.Status.COMPLETE + + // More than 14 days + getVaccinationStatus( + Instant.parse("2021-05-12T12:00:00.000Z") ) shouldBe VaccinatedPerson.Status.IMMUNITY } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt index dd870ce117d4d635b42aefaba8000c70443d9e47..2ed483adc823e40dbf465713789e2530e52a7f22 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt @@ -46,7 +46,8 @@ class DefaultPlaybookTest : BaseTest() { coEvery { verificationServer.retrieveRegistrationToken(any()) } returns "token" coEvery { verificationServer.pollTestResult(any()) } returns CoronaTestResultResponse( coronaTestResult = CoronaTestResult.PCR_OR_RAT_PENDING, - sampleCollectedAt = null + sampleCollectedAt = null, + labId = null, ) coEvery { verificationServer.retrieveTanFake() } returns mockk() coEvery { verificationServer.retrieveTan(any()) } returns "tan" @@ -214,7 +215,8 @@ class DefaultPlaybookTest : BaseTest() { val expectedResult = CoronaTestResult.PCR_OR_RAT_PENDING coEvery { verificationServer.pollTestResult(expectedToken) } returns CoronaTestResultResponse( coronaTestResult = expectedResult, - sampleCollectedAt = null + sampleCollectedAt = null, + labId = null, ) coEvery { submissionServer.submitFakePayload() } throws TestException() @@ -224,7 +226,8 @@ class DefaultPlaybookTest : BaseTest() { registrationToken shouldBe expectedToken testResult shouldBe CoronaTestResultResponse( coronaTestResult = expectedResult, - sampleCollectedAt = null + sampleCollectedAt = null, + labId = null, ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt index 7207e5d906348755d0391d887d1ba26c67c8103e..f57087d56331d08052724adc50dfa99c6e9fc151 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.main import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings +import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository import de.rki.coronawarnapp.covidcertificate.vaccination.core.VaccinationSettings import de.rki.coronawarnapp.environment.EnvironmentSetup import de.rki.coronawarnapp.playbook.BackgroundNoise @@ -16,6 +17,7 @@ import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockkObject import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -36,6 +38,7 @@ class MainActivityViewModelTest : BaseTest() { @MockK lateinit var traceLocationSettings: TraceLocationSettings @MockK lateinit var checkInRepository: CheckInRepository @MockK lateinit var vaccinationSettings: VaccinationSettings + @MockK lateinit var testCertificateRepository: TestCertificateRepository @BeforeEach fun setup() { @@ -50,6 +53,7 @@ class MainActivityViewModelTest : BaseTest() { ) every { onboardingSettings.isBackgroundCheckDone } returns true every { checkInRepository.checkInsWithinRetention } returns MutableStateFlow(listOf()) + every { testCertificateRepository.certificates } returns emptyFlow() } private fun createInstance(): MainActivityViewModel = MainActivityViewModel( @@ -62,6 +66,7 @@ class MainActivityViewModelTest : BaseTest() { checkInRepository = checkInRepository, traceLocationSettings = traceLocationSettings, vaccinationSettings = vaccinationSettings, + testCertificateRepository = testCertificateRepository, ) @Test diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/CoronaTestServiceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/CoronaTestServiceTest.kt index b75849bb4e33f6cfa2b4f7a3e9e373e21d745e4c..cc973f05d4e2fcf3f3b050824c4025d6ea0c7a6d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/CoronaTestServiceTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/CoronaTestServiceTest.kt @@ -45,7 +45,8 @@ class CoronaTestServiceTest : BaseTest() { registrationToken = registrationToken, testResultResponse = CoronaTestResultResponse( coronaTestResult = CoronaTestResult.PCR_OR_RAT_PENDING, - sampleCollectedAt = null + sampleCollectedAt = null, + labId = null, ) ) } @@ -85,13 +86,15 @@ class CoronaTestServiceTest : BaseTest() { fun requestTestResultSucceeds() { coEvery { mockPlaybook.testResult(registrationToken) } returns CoronaTestResultResponse( coronaTestResult = CoronaTestResult.PCR_NEGATIVE, - sampleCollectedAt = null + sampleCollectedAt = null, + labId = null, ) runBlocking { createInstance().checkTestResult(registrationToken) shouldBe CoronaTestResultResponse( coronaTestResult = CoronaTestResult.PCR_NEGATIVE, sampleCollectedAt = null, + labId = null, ) } coVerify(exactly = 1) { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt index ae77a7e3ca94f391438e9a5f0aedc71cbcc48fe8..1a03d4997566f27f6f93997df101a9bfb352b7a4 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt @@ -1,15 +1,30 @@ package de.rki.coronawarnapp.storage import de.rki.coronawarnapp.coronatest.CoronaTestRepository +import de.rki.coronawarnapp.coronatest.errors.AlreadyRedeemedException +import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.type.CoronaTest +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.slot import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.runBlockingTest +import org.joda.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import testhelpers.preferences.mockFlowPreference class SubmissionRepositoryTest : BaseTest() { @@ -17,9 +32,29 @@ class SubmissionRepositoryTest : BaseTest() { @MockK lateinit var tekHistoryStorage: TEKHistoryStorage @MockK lateinit var coronaTestRepository: CoronaTestRepository + private val pcrRegistrationRequest = CoronaTestQRCode.PCR( + qrCodeGUID = "pcr-guid" + ) + private val pcrTest = PCRCoronaTest( + identifier = pcrRegistrationRequest.identifier, + lastUpdatedAt = Instant.EPOCH, + registeredAt = Instant.EPOCH, + registrationToken = "token", + testResult = CoronaTestResult.PCR_REDEEMED, + ) + @BeforeEach fun setUp() { MockKAnnotations.init(this) + + coronaTestRepository.apply { + every { coronaTests } returns emptyFlow() + coEvery { registerTest(pcrRegistrationRequest, any(), any()) } returns pcrTest + } + + submissionSettings.apply { + every { symptoms } returns mockFlowPreference(null) + } } fun createInstance(scope: CoroutineScope) = SubmissionRepository( @@ -30,7 +65,23 @@ class SubmissionRepositoryTest : BaseTest() { ) @Test - fun todo() { - // TODO + fun `tryReplaceTest overrides register test conditions`() = runBlockingTest { + val precondition = slot<(Collection<CoronaTest>) -> Boolean>() + val postcondition = slot<(CoronaTest) -> Boolean>() + + val instance = createInstance(scope = this) + + instance.tryReplaceTest(pcrRegistrationRequest) + + coVerify { coronaTestRepository.registerTest(any(), capture(precondition), capture(postcondition)) } + + precondition.captured(emptyList()) shouldBe true + precondition.captured(listOf(pcrTest)) shouldBe true + + shouldThrow<AlreadyRedeemedException> { + postcondition.captured(pcrTest) + } + + postcondition.captured(pcrTest.copy(testResult = CoronaTestResult.PCR_NEGATIVE)) shouldBe true } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..39256d8213d830e06978280ad7d381407f12b7b2 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/TestRegistrationStateProcessorTest.kt @@ -0,0 +1,303 @@ +package de.rki.coronawarnapp.submission + +import de.rki.coronawarnapp.coronatest.TestRegistrationRequest +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.exception.http.BadRequestException +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.first +import kotlinx.coroutines.test.runBlockingTest +import org.joda.time.Instant +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class TestRegistrationStateProcessorTest : BaseTest() { + + @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector + @MockK lateinit var registeredTestRA: CoronaTest + @MockK lateinit var registeredTestPCR: CoronaTest + + private val raRequest: TestRegistrationRequest = CoronaTestQRCode.RapidAntigen( + hash = "ra-hash", + createdAt = Instant.EPOCH, + ) + private val pcrRequest: TestRegistrationRequest = CoronaTestQRCode.PCR( + qrCodeGUID = "pcr-guid" + ) + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + submissionRepository.apply { + coEvery { registerTest(raRequest) } returns registeredTestRA + coEvery { registerTest(pcrRequest) } returns registeredTestPCR + + coEvery { tryReplaceTest(raRequest) } returns registeredTestRA + coEvery { tryReplaceTest(pcrRequest) } returns registeredTestPCR + + coEvery { giveConsentToSubmission(any()) } just Runs + } + + registeredTestRA.apply { + every { type } returns CoronaTest.Type.RAPID_ANTIGEN + } + registeredTestPCR.apply { + every { type } returns CoronaTest.Type.PCR + } + + every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs + } + + private fun createInstance() = TestRegistrationStateProcessor( + submissionRepository = submissionRepository, + analyticsKeySubmissionCollector = analyticsKeySubmissionCollector, + ) + + @Test + fun `register new RA test - with consent`() = runBlockingTest { + val instance = createInstance() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle + + instance.startRegistration( + request = raRequest, + isSubmissionConsentGiven = true, + allowReplacement = false + ) + + advanceUntilIdle() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered( + test = registeredTestRA + ) + + coVerify { + submissionRepository.registerTest(raRequest) + submissionRepository.giveConsentToSubmission(CoronaTest.Type.RAPID_ANTIGEN) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN) + } + } + + @Test + fun `register new RA test - without consent`() = runBlockingTest { + val instance = createInstance() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle + + instance.startRegistration( + request = raRequest, + isSubmissionConsentGiven = false, + allowReplacement = false + ) + + advanceUntilIdle() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered( + test = registeredTestRA + ) + + coVerify { + submissionRepository.registerTest(raRequest) + } + coVerify(exactly = 0) { + submissionRepository.giveConsentToSubmission(any()) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + + @Test + fun `replace RA test - with consent`() = runBlockingTest { + val instance = createInstance() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle + + instance.startRegistration( + request = raRequest, + isSubmissionConsentGiven = true, + allowReplacement = true + ) + + advanceUntilIdle() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered( + test = registeredTestRA + ) + + coVerify { + submissionRepository.tryReplaceTest(raRequest) + submissionRepository.giveConsentToSubmission(CoronaTest.Type.RAPID_ANTIGEN) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN) + } + } + + @Test + fun `replace RA new test - without consent`() = runBlockingTest { + val instance = createInstance() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle + + instance.startRegistration( + request = raRequest, + isSubmissionConsentGiven = false, + allowReplacement = true + ) + + advanceUntilIdle() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered( + test = registeredTestRA + ) + + coVerify { + submissionRepository.tryReplaceTest(raRequest) + } + coVerify(exactly = 0) { + submissionRepository.giveConsentToSubmission(any()) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + + @Test + fun `register new PCR test - with consent`() = runBlockingTest { + val instance = createInstance() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle + + instance.startRegistration( + request = pcrRequest, + isSubmissionConsentGiven = true, + allowReplacement = false + ) + + advanceUntilIdle() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered( + test = registeredTestPCR + ) + + coVerify { + submissionRepository.registerTest(pcrRequest) + submissionRepository.giveConsentToSubmission(CoronaTest.Type.PCR) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR) + } + } + + @Test + fun `register new PCR test - without consent`() = runBlockingTest { + val instance = createInstance() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle + + instance.startRegistration( + request = pcrRequest, + isSubmissionConsentGiven = false, + allowReplacement = false + ) + + advanceUntilIdle() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered( + test = registeredTestPCR + ) + + coVerify { + submissionRepository.registerTest(pcrRequest) + } + coVerify(exactly = 0) { + submissionRepository.giveConsentToSubmission(any()) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + + @Test + fun `replace PCR test - with consent`() = runBlockingTest { + val instance = createInstance() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle + + instance.startRegistration( + request = pcrRequest, + isSubmissionConsentGiven = true, + allowReplacement = true + ) + + advanceUntilIdle() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered( + test = registeredTestPCR + ) + + coVerify { + submissionRepository.tryReplaceTest(pcrRequest) + submissionRepository.giveConsentToSubmission(CoronaTest.Type.PCR) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR) + } + } + + @Test + fun `replace PCR new test - without consent`() = runBlockingTest { + val instance = createInstance() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle + + instance.startRegistration( + request = pcrRequest, + isSubmissionConsentGiven = false, + allowReplacement = true + ) + + advanceUntilIdle() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.TestRegistered( + test = registeredTestPCR + ) + + coVerify { + submissionRepository.tryReplaceTest(pcrRequest) + } + coVerify(exactly = 0) { + submissionRepository.giveConsentToSubmission(any()) + analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) + } + } + + @Test + fun `errors are mapped to state`() = runBlockingTest { + val instance = createInstance() + + val expectedException = BadRequestException("") + coEvery { submissionRepository.registerTest(any()) } throws expectedException + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.Idle + + instance.startRegistration( + request = raRequest, + isSubmissionConsentGiven = true, + allowReplacement = false + ) + + advanceUntilIdle() + + instance.state.first() shouldBe TestRegistrationStateProcessor.State.Error( + exception = expectedException + ) + + coVerify { + submissionRepository.registerTest(raRequest) + } + coVerify(exactly = 0) { + submissionRepository.giveConsentToSubmission(any()) + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModelTest.kt index 009f839c5c79d3bfed61a95225a0f6cea8335c89..61c9d776d4e008c867526a9e26b03ca1a6191b5b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/covidcertificate/RequestCovidCertificateViewModelTest.kt @@ -1,21 +1,15 @@ package de.rki.coronawarnapp.ui.submission.covidcertificate -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 de.rki.coronawarnapp.submission.TestRegistrationStateProcessor 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 io.mockk.mockk import kotlinx.coroutines.flow.flowOf import org.joda.time.Instant import org.joda.time.LocalDate @@ -30,10 +24,7 @@ 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 testRegistrationStateProcessor: TestRegistrationStateProcessor @MockK lateinit var coronaTest: CoronaTest private val date = LocalDate.parse( @@ -48,24 +39,25 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() { 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() + testRegistrationStateProcessor.apply { + coEvery { startRegistration(any(), any(), any()) } returns mockk() + coEvery { state } returns flowOf(TestRegistrationStateProcessor.State.Idle) } - 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 } + private fun createInstance( + coronaTestQRCode: CoronaTestQRCode = pcrQRCode, + coronTestConsent: Boolean = true, + deleteOldTest: Boolean = false + ) = RequestCovidCertificateViewModel( + testRegistrationRequest = coronaTestQRCode, + coronaTestConsent = coronTestConsent, + deleteOldTest = deleteOldTest, + registrationStateProcessor = testRegistrationStateProcessor, + ) + @Test fun birthDateChanged() { createInstance().apply { @@ -81,13 +73,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() { onAgreeGC() coVerify { - submissionRepository.testForType(any()) - coronaTestRepository.removeTest(any()) - qrCodeRegistrationStateProcessor.startQrCodeRegistration( - pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date), - any() + testRegistrationStateProcessor.startRegistration( + request = pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date), + isSubmissionConsentGiven = any(), + allowReplacement = true ) - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } } } @@ -99,16 +89,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() { onAgreeGC() coVerify { - qrCodeRegistrationStateProcessor.startQrCodeRegistration( - pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date), - any() + testRegistrationStateProcessor.startRegistration( + request = pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date), + isSubmissionConsentGiven = any(), + allowReplacement = false ) - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) - } - - coVerify(exactly = 0) { - submissionRepository.testForType(any()) - coronaTestRepository.removeTest(any()) } } } @@ -119,13 +104,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() { onDisagreeGC() coVerify { - submissionRepository.testForType(any()) - coronaTestRepository.removeTest(any()) - qrCodeRegistrationStateProcessor.startQrCodeRegistration( - pcrQRCode.copy(isDccConsentGiven = false), - any() + testRegistrationStateProcessor.startRegistration( + request = pcrQRCode.copy(isDccConsentGiven = false), + isSubmissionConsentGiven = any(), + allowReplacement = true ) - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } } } @@ -136,16 +119,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() { onDisagreeGC() coVerify { - qrCodeRegistrationStateProcessor.startQrCodeRegistration( - pcrQRCode.copy(isDccConsentGiven = false), - any() + testRegistrationStateProcessor.startRegistration( + request = pcrQRCode.copy(isDccConsentGiven = false), + isSubmissionConsentGiven = any(), + allowReplacement = false ) - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) - } - - coVerify(exactly = 0) { - submissionRepository.testForType(any()) - coronaTestRepository.removeTest(any()) } } } @@ -156,13 +134,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() { onAgreeGC() coVerify { - submissionRepository.testForType(any()) - coronaTestRepository.removeTest(any()) - qrCodeRegistrationStateProcessor.startQrCodeRegistration( - ratQRCode.copy(isDccConsentGiven = true, dateOfBirth = date), - any() + testRegistrationStateProcessor.startRegistration( + request = ratQRCode.copy(isDccConsentGiven = true, dateOfBirth = date), + isSubmissionConsentGiven = any(), + allowReplacement = true ) - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } } } @@ -173,16 +149,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() { onAgreeGC() coVerify { - qrCodeRegistrationStateProcessor.startQrCodeRegistration( - ratQRCode.copy(isDccConsentGiven = true), - any() + testRegistrationStateProcessor.startRegistration( + request = ratQRCode.copy(isDccConsentGiven = true), + isSubmissionConsentGiven = any(), + allowReplacement = false ) - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) - } - - coVerify(exactly = 0) { - submissionRepository.testForType(any()) - coronaTestRepository.removeTest(any()) } } } @@ -193,13 +164,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() { onDisagreeGC() coVerify { - submissionRepository.testForType(any()) - coronaTestRepository.removeTest(any()) - qrCodeRegistrationStateProcessor.startQrCodeRegistration( - ratQRCode.copy(isDccConsentGiven = false), - any() + testRegistrationStateProcessor.startRegistration( + request = ratQRCode.copy(isDccConsentGiven = false), + isSubmissionConsentGiven = any(), + allowReplacement = true ) - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } } } @@ -210,16 +179,11 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() { onDisagreeGC() coVerify { - qrCodeRegistrationStateProcessor.startQrCodeRegistration( - ratQRCode.copy(isDccConsentGiven = false), - any() + testRegistrationStateProcessor.startRegistration( + request = ratQRCode.copy(isDccConsentGiven = false), + isSubmissionConsentGiven = any(), + allowReplacement = false ) - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) - } - - coVerify(exactly = 0) { - submissionRepository.testForType(any()) - coronaTestRepository.removeTest(any()) } } } @@ -247,58 +211,4 @@ internal class RequestCovidCertificateViewModelTest : BaseTest() { 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/consent/SubmissionConsentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt index 41eb4280816b12a78fe0c6ba1e830ae58188aeb9..36730890014ffbc629c94692b9501c5c47c2d550 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt @@ -1,16 +1,13 @@ package de.rki.coronawarnapp.ui.submission.qrcode.consent -import androidx.lifecycle.MutableLiveData import com.google.android.gms.common.api.ApiException import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQrCodeValidator import de.rki.coronawarnapp.nearby.modules.tekhistory.TEKHistoryProvider import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor import de.rki.coronawarnapp.ui.Country -import de.rki.coronawarnapp.ui.submission.ApiRequestState -import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents -import de.rki.coronawarnapp.util.ui.SingleLiveEvent import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs @@ -21,6 +18,7 @@ import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -34,7 +32,7 @@ class SubmissionConsentViewModelTest { @MockK lateinit var submissionRepository: SubmissionRepository @MockK lateinit var interoperabilityRepository: InteroperabilityRepository @MockK lateinit var tekHistoryProvider: TEKHistoryProvider - @MockK lateinit var qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor + @MockK lateinit var testRegistrationStateProcessor: TestRegistrationStateProcessor @MockK lateinit var qrCodeValidator: CoronaTestQrCodeValidator lateinit var viewModel: SubmissionConsentViewModel @@ -46,16 +44,17 @@ class SubmissionConsentViewModelTest { MockKAnnotations.init(this) every { interoperabilityRepository.countryList } returns MutableStateFlow(countryList) coEvery { submissionRepository.giveConsentToSubmission(any()) } just Runs - coEvery { qrCodeRegistrationStateProcessor.showRedeemedTokenWarning } returns SingleLiveEvent() - coEvery { qrCodeRegistrationStateProcessor.registrationState } returns MutableLiveData( - QrCodeRegistrationStateProcessor.RegistrationState(ApiRequestState.IDLE) - ) - coEvery { qrCodeRegistrationStateProcessor.registrationError } returns SingleLiveEvent() + + testRegistrationStateProcessor.apply { + every { state } returns flowOf(TestRegistrationStateProcessor.State.Idle) + coEvery { startRegistration(any(), any(), any()) } returns mockk() + } + viewModel = SubmissionConsentViewModel( interoperabilityRepository, dispatcherProvider = TestDispatcherProvider(), tekHistoryProvider, - qrCodeRegistrationStateProcessor, + testRegistrationStateProcessor, submissionRepository, qrCodeValidator ) 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 18b2c8425196c280a168f20a048ac15bb3e5bd9a..a15c1e8d97c7ed2453c7245640fd11199fc95710 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 @@ -1,28 +1,21 @@ package de.rki.coronawarnapp.ui.submission.qrcode.scan -import androidx.lifecycle.MutableLiveData import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQrCodeValidator import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException 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.ApiRequestState -import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor -import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor.ValidationState +import de.rki.coronawarnapp.submission.TestRegistrationStateProcessor import de.rki.coronawarnapp.util.permission.CameraSettings -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.every import io.mockk.impl.annotations.MockK -import io.mockk.just +import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.runBlockingTest -import org.joda.time.Instant +import kotlinx.coroutines.flow.flowOf import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -37,29 +30,27 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() { @MockK lateinit var submissionRepository: SubmissionRepository @MockK lateinit var cameraSettings: CameraSettings @MockK lateinit var qrCodeValidator: CoronaTestQrCodeValidator - @MockK lateinit var qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor - @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector + @MockK lateinit var testRegistrationStateProcessor: TestRegistrationStateProcessor @BeforeEach fun setUp() { MockKAnnotations.init(this) every { submissionRepository.testForType(any()) } returns MutableStateFlow<CoronaTest?>(null) - coEvery { qrCodeRegistrationStateProcessor.showRedeemedTokenWarning } returns SingleLiveEvent() - coEvery { qrCodeRegistrationStateProcessor.registrationState } returns MutableLiveData( - QrCodeRegistrationStateProcessor.RegistrationState(ApiRequestState.IDLE) - ) - coEvery { qrCodeRegistrationStateProcessor.registrationError } returns SingleLiveEvent() + + testRegistrationStateProcessor.apply { + every { state } returns flowOf(TestRegistrationStateProcessor.State.Idle) + coEvery { startRegistration(any(), any(), any()) } returns mockk() + } } private fun createViewModel() = SubmissionQRCodeScanViewModel( isConsentGiven = true, dispatcherProvider = TestDispatcherProvider(), cameraSettings = cameraSettings, - qrCodeRegistrationStateProcessor = qrCodeRegistrationStateProcessor, + registrationStateProcessor = testRegistrationStateProcessor, submissionRepository = submissionRepository, qrCodeValidator = qrCodeValidator, - analyticsKeySubmissionCollector = analyticsKeySubmissionCollector ) @Test @@ -74,22 +65,22 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() { val invalidQrCode = "https://no-guid-here" every { qrCodeValidator.validate(validQrCode) } returns coronaTestQRCode - every { qrCodeValidator.validate(invalidQrCode) } throws InvalidQRCodeException() + + val expectedError = InvalidQRCodeException() + every { qrCodeValidator.validate(invalidQrCode) } throws expectedError val viewModel = createViewModel() + viewModel.qrCodeErrorEvent.observeForever {} // start - viewModel.qrCodeValidationState.value = ValidationState.STARTED - - viewModel.qrCodeValidationState.value shouldBe ValidationState.STARTED + viewModel.qrCodeErrorEvent.value shouldBe null viewModel.registerCoronaTest(validQrCode) - viewModel.qrCodeValidationState.observeForever {} - viewModel.qrCodeValidationState.value shouldBe ValidationState.SUCCESS + viewModel.qrCodeErrorEvent.value shouldBe null // invalid guid viewModel.registerCoronaTest(invalidQrCode) - viewModel.qrCodeValidationState.value shouldBe ValidationState.INVALID + viewModel.qrCodeErrorEvent.value shouldBe expectedError } @Test @@ -99,81 +90,4 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() { verify { cameraSettings.isCameraDeniedPermanently } } - - @Test - 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 - - createViewModel().registerCoronaTest(rawResult = "") - - verify(exactly = 0) { - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR) - analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN) - } - } - - @Test - 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 - - createViewModel().registerCoronaTest(rawResult = "") - - 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) - } - } }