diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index f8ffb33b2ae96d524de4addad7bae3bf4e6d66ec..bd3980e016d94a4a247d65c42f9ab7355bf17d0d 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -322,6 +322,11 @@ dependencies { implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.work:work-runtime-ktx:2.5.0' + def activity_version = "1.2.3" + implementation "androidx.activity:activity-ktx:$activity_version" + def fragment_version = "1.3.5" + implementation "androidx.fragment:fragment-ktx:$fragment_version" + implementation 'androidx.lifecycle:lifecycle-common-java8:2.3.1' implementation 'androidx.lifecycle:lifecycle-process:2.3.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' @@ -362,7 +367,8 @@ dependencies { testImplementation 'org.hamcrest:hamcrest-library:2.2' // Testing - jUnit4 - testImplementation 'junit:junit:4.13.1' + def junit_version = "4.13.1" + testImplementation "junit:junit:$junit_version" testImplementation "org.junit.vintage:junit-vintage-engine:5.7.0" testImplementation "androidx.test:core-ktx:1.3.0" @@ -383,7 +389,7 @@ dependencies { testImplementation "io.github.classgraph:classgraph:4.8.90" // Testing - Instrumentation - androidTestImplementation 'junit:junit:4.13.1' + androidTestImplementation "junit:junit:$junit_version" androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CovidCertificateDetailsFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CovidCertificateDetailsFragmentTest.kt index 124ba8e741f7dd6c8c8adaa600eb8bc0b3cf5617..aab4a8c7249b5998f16f85e1ede7a8f87dfde908 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CovidCertificateDetailsFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/covidcertificate/test/ui/CovidCertificateDetailsFragmentTest.kt @@ -17,9 +17,9 @@ import de.rki.coronawarnapp.covidcertificate.common.certificate.CertificatePerso import de.rki.coronawarnapp.covidcertificate.common.qrcode.QrCodeString import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificate import de.rki.coronawarnapp.covidcertificate.test.core.storage.TestCertificateIdentifier -import de.rki.coronawarnapp.covidcertificate.test.ui.details.CovidCertificateDetailsFragment -import de.rki.coronawarnapp.covidcertificate.test.ui.details.CovidCertificateDetailsFragmentArgs import de.rki.coronawarnapp.covidcertificate.test.ui.details.CovidCertificateDetailsViewModel +import de.rki.coronawarnapp.covidcertificate.test.ui.details.TestCertificateDetailsFragment +import de.rki.coronawarnapp.covidcertificate.test.ui.details.TestCertificateDetailsFragmentArgs import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK @@ -43,7 +43,7 @@ class VaccinationDetailsFragmentTest : BaseUITest() { @MockK lateinit var vaccinationDetailsViewModel: CovidCertificateDetailsViewModel @MockK lateinit var certificatePersonIdentifier: CertificatePersonIdentifier - private val args = CovidCertificateDetailsFragmentArgs("testCertificateIdentifier").toBundle() + private val args = TestCertificateDetailsFragmentArgs("testCertificateIdentifier").toBundle() @Before fun setUp() { @@ -61,17 +61,17 @@ class VaccinationDetailsFragmentTest : BaseUITest() { @Test fun launch_fragment() { - launchFragment2<CovidCertificateDetailsFragment>(fragmentArgs = args) + launchFragment2<TestCertificateDetailsFragment>(fragmentArgs = args) } @Screenshot @Test fun capture_screenshot_incomplete() { every { vaccinationDetailsViewModel.covidCertificate } returns vaccinationDetailsData() - launchFragmentInContainer2<CovidCertificateDetailsFragment>(fragmentArgs = args) - takeScreenshot<CovidCertificateDetailsFragment>() + launchFragmentInContainer2<TestCertificateDetailsFragment>(fragmentArgs = args) + takeScreenshot<TestCertificateDetailsFragment>() onView(withId(R.id.coordinator_layout)).perform(swipeUp()) - takeScreenshot<CovidCertificateDetailsFragment>("_2") + takeScreenshot<TestCertificateDetailsFragment>("_2") } private fun bitmapLiveDate(): LiveData<Bitmap> { @@ -143,5 +143,5 @@ class VaccinationDetailsFragmentTest : BaseUITest() { @Module abstract class CovidCertificateDetailsFragmentTestModule { @ContributesAndroidInjector - abstract fun covidCertificateDetailsFragment(): CovidCertificateDetailsFragment + abstract fun covidCertificateDetailsFragment(): TestCertificateDetailsFragment } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/DigitalCovidCertificateUIModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/DigitalCovidCertificateUIModule.kt index 8e02465742e84562950ac2af89d0d9fda6845466..fdeb2f4205806ff55f0da7b4fd4fe4808bbc1f89 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/DigitalCovidCertificateUIModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/DigitalCovidCertificateUIModule.kt @@ -1,6 +1,9 @@ package de.rki.coronawarnapp.covidcertificate import dagger.Module +import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.covidcertificate.common.scan.DccQrCodeScanFragment +import de.rki.coronawarnapp.covidcertificate.common.scan.DccQrCodeScanModule import de.rki.coronawarnapp.covidcertificate.recovery.ui.RecoveryCertificateUIModule import de.rki.coronawarnapp.covidcertificate.test.ui.TestCertificateUIModule import de.rki.coronawarnapp.covidcertificate.vaccination.ui.VaccinationCertificateUIModule @@ -12,4 +15,8 @@ import de.rki.coronawarnapp.covidcertificate.vaccination.ui.VaccinationCertifica RecoveryCertificateUIModule::class, ] ) -abstract class DigitalCovidCertificateUIModule +abstract class DigitalCovidCertificateUIModule { + + @ContributesAndroidInjector(modules = [DccQrCodeScanModule::class]) + abstract fun dccQrCodeScanFragment(): DccQrCodeScanFragment +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/scan/VaccinationQrCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/scan/DccQrCodeScanFragment.kt similarity index 72% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/scan/VaccinationQrCodeScanFragment.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/scan/DccQrCodeScanFragment.kt index 5fc1ecbdd0e643141a2baca8be7e111dd8aed511..66a4c1c28f9dc541537c14749c78829a86d63bf8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/scan/VaccinationQrCodeScanFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/scan/DccQrCodeScanFragment.kt @@ -1,10 +1,10 @@ -package de.rki.coronawarnapp.covidcertificate.vaccination.ui.scan +package de.rki.coronawarnapp.covidcertificate.common.scan import android.Manifest -import android.content.pm.PackageManager import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT +import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment import com.google.zxing.BarcodeFormat import com.journeyapps.barcodescanner.DefaultDecoderFactory @@ -23,16 +23,32 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModels import javax.inject.Inject -class VaccinationQrCodeScanFragment : +class DccQrCodeScanFragment : Fragment(R.layout.fragment_scan_qr_code), AutoInject { @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory - private val viewModel: VaccinationQrCodeScanViewModel by cwaViewModels { viewModelFactory } + private val viewModel: DccQrCodeScanViewModel by cwaViewModels { viewModelFactory } private val binding: FragmentScanQrCodeBinding by viewBinding() private var showsPermissionDialog = false + val requestPermissionLauncher = + registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (!isGranted) { + if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { + showCameraPermissionRationaleDialog() + viewModel.setCameraDeniedPermanently(false) + } else { + // User permanently denied access to the camera + showCameraPermissionDeniedDialog() + viewModel.setCameraDeniedPermanently(true) + } + } + } + override fun onViewCreated( view: View, savedInstanceState: Bundle? @@ -50,16 +66,27 @@ class VaccinationQrCodeScanFragment : viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { - is VaccinationQrCodeScanViewModel.Event.QrCodeScanSucceeded -> { + is DccQrCodeScanViewModel.Event.VaccinationQrCodeScanSucceeded -> { binding.qrCodeScanSpinner.hide() doNavigate( - VaccinationQrCodeScanFragmentDirections - .actionVaccinationQrCodeScanFragmentToVaccinationListFragment( - event.personIdentifierCodeSha256 + DccQrCodeScanFragmentDirections + .actionDccQrCodeScanFragmentToVaccinationDetailsFragment( + event.certificateId ) ) } - VaccinationQrCodeScanViewModel.Event.QrCodeScanInProgress -> { + is DccQrCodeScanViewModel.Event.RecoveryQrCodeScanSucceeded -> { // TODO + } + is DccQrCodeScanViewModel.Event.TestQrCodeScanSucceeded -> { + binding.qrCodeScanSpinner.hide() + doNavigate( + DccQrCodeScanFragmentDirections + .actionDccQrCodeScanFragmentToTestCertificateDetailsFragment( + event.certificateId + ) + ) + } + DccQrCodeScanViewModel.Event.QrCodeScanInProgress -> { binding.qrCodeScanSpinner.show() } } @@ -91,25 +118,6 @@ class VaccinationQrCodeScanFragment : requestCameraPermission() } - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array<String>, - grantResults: IntArray - ) { - if (requestCode == REQUEST_CAMERA_PERMISSION_CODE && - grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_DENIED - ) { - if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { - showCameraPermissionRationaleDialog() - viewModel.setCameraDeniedPermanently(false) - } else { - // User permanently denied access to the camera - showCameraPermissionDeniedDialog() - viewModel.setCameraDeniedPermanently(true) - } - } - } - private fun startDecode() = binding.qrCodeScanPreview .decodeSingle { barcodeResult -> viewModel.onScanResult(barcodeResult) @@ -151,10 +159,7 @@ class VaccinationQrCodeScanFragment : DialogHelper.showDialog(cameraPermissionRationaleDialogInstance) } - private fun requestCameraPermission() = requestPermissions( - arrayOf(Manifest.permission.CAMERA), - REQUEST_CAMERA_PERMISSION_CODE - ) + private fun requestCameraPermission() = requestPermissionLauncher.launch(Manifest.permission.CAMERA) private fun leave() { showsPermissionDialog = false @@ -165,8 +170,4 @@ class VaccinationQrCodeScanFragment : super.onPause() binding.qrCodeScanPreview.pause() } - - companion object { - private const val REQUEST_CAMERA_PERMISSION_CODE = 4000 - } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/scan/VaccinationQrCodeScanModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/scan/DccQrCodeScanModule.kt similarity index 54% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/scan/VaccinationQrCodeScanModule.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/scan/DccQrCodeScanModule.kt index bc581306a761d7bdfd9e46489b4ca2f98e401dc8..0954b8f29fd35fe26ab23c3c6fd4abba9fc6dd8a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/scan/VaccinationQrCodeScanModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/scan/DccQrCodeScanModule.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.covidcertificate.vaccination.ui.scan +package de.rki.coronawarnapp.covidcertificate.common.scan import dagger.Binds import dagger.Module @@ -8,11 +8,11 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey @Module -abstract class VaccinationQrCodeScanModule { +abstract class DccQrCodeScanModule { @Binds @IntoMap - @CWAViewModelKey(VaccinationQrCodeScanViewModel::class) - abstract fun vaccinationQrCodeScanFragment( - factory: VaccinationQrCodeScanViewModel.Factory + @CWAViewModelKey(DccQrCodeScanViewModel::class) + abstract fun dccQrCodeScanFragment( + factory: DccQrCodeScanViewModel.Factory ): CWAViewModelFactory<out CWAViewModel> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/scan/DccQrCodeScanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/scan/DccQrCodeScanViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..75835156028e515fa19d8021ecdffa1796d82ff5 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/common/scan/DccQrCodeScanViewModel.kt @@ -0,0 +1,77 @@ +package de.rki.coronawarnapp.covidcertificate.common.scan + +import com.journeyapps.barcodescanner.BarcodeResult +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.covidcertificate.recovery.core.RecoveryCertificateRepository +import de.rki.coronawarnapp.covidcertificate.recovery.core.qrcode.RecoveryCertificateQRCode +import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository +import de.rki.coronawarnapp.covidcertificate.test.core.qrcode.TestCertificateQRCode +import de.rki.coronawarnapp.covidcertificate.vaccination.core.qrcode.DccQrCodeValidator +import de.rki.coronawarnapp.covidcertificate.vaccination.core.qrcode.VaccinationCertificateQRCode +import de.rki.coronawarnapp.covidcertificate.vaccination.core.repository.VaccinationRepository +import de.rki.coronawarnapp.util.permission.CameraSettings +import de.rki.coronawarnapp.util.ui.SingleLiveEvent +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory +import timber.log.Timber + +class DccQrCodeScanViewModel @AssistedInject constructor( + private val cameraSettings: CameraSettings, + private val qrCodeValidator: DccQrCodeValidator, + private val vaccinationRepository: VaccinationRepository, + private val testCertificateRepository: TestCertificateRepository, + private val recoveryCertificateRepository: RecoveryCertificateRepository +) : CWAViewModel() { + + val event = SingleLiveEvent<Event>() + + val errorEvent = SingleLiveEvent<Throwable>() + + fun onScanResult(barcodeResult: BarcodeResult) = launch { + try { + event.postValue(Event.QrCodeScanInProgress) + when (val qrCode = qrCodeValidator.validate(barcodeResult.text)) { + is VaccinationCertificateQRCode -> registerVaccinationCertificate(qrCode) + is TestCertificateQRCode -> registerTestCertificate(qrCode) + is RecoveryCertificateQRCode -> registerRecoveryCertificate(qrCode) + } + } catch (e: Throwable) { + errorEvent.postValue(e) + } + } + + private suspend fun registerVaccinationCertificate(qrCode: VaccinationCertificateQRCode) { + val certificate = vaccinationRepository.registerVaccination(qrCode) + event.postValue(Event.VaccinationQrCodeScanSucceeded(certificate.certificateId)) + } + + private suspend fun registerTestCertificate(qrCode: TestCertificateQRCode) { + throw NotImplementedError("Test certificate found") + // TODO +// val certificate = testCertificateRepository.requestCertificate(qrCode) +// event.postValue(Event.TestQrCodeScanSucceeded(certificate.certificateId)) + } + + private suspend fun registerRecoveryCertificate(qrCode: RecoveryCertificateQRCode) { + throw NotImplementedError("Recovery certificate found") + // TODO +// val certificate = recoveryCertificateRepository.requestCertificate(qrCode) +// event.postValue(Event.RecoveryQrCodeScanSucceeded(certificate.certificateId)) + } + + fun setCameraDeniedPermanently(denied: Boolean) { + Timber.d("setCameraDeniedPermanently(denied=$denied)") + cameraSettings.isCameraDeniedPermanently.update { denied } + } + + sealed class Event { + object QrCodeScanInProgress : Event() + data class VaccinationQrCodeScanSucceeded(val certificateId: String) : Event() + data class TestQrCodeScanSucceeded(val certificateId: String) : Event() + data class RecoveryQrCodeScanSucceeded(val certificateId: String) : Event() + } + + @AssistedFactory + interface Factory : SimpleCWAViewModelFactory<DccQrCodeScanViewModel> +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonOverviewFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonOverviewFragment.kt index 106e4a95c6da1df1950dc3e07c610244aaf60c6d..475f3e4701ae2805f7a35d99f7580b915caeac48 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonOverviewFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonOverviewFragment.kt @@ -2,7 +2,6 @@ package de.rki.coronawarnapp.covidcertificate.person.ui.overview import android.os.Bundle import android.view.View -import android.widget.Toast import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -14,8 +13,8 @@ import de.rki.coronawarnapp.covidcertificate.common.exception.TestCertificateSer import de.rki.coronawarnapp.covidcertificate.person.ui.overview.items.CameraPermissionCard import de.rki.coronawarnapp.covidcertificate.person.ui.overview.items.CertificatesItem import de.rki.coronawarnapp.databinding.PersonOverviewFragmentBinding -import de.rki.coronawarnapp.util.ExternalActionHelper.openUrl import de.rki.coronawarnapp.util.ExternalActionHelper.openAppDetailsSettings +import de.rki.coronawarnapp.util.ExternalActionHelper.openUrl import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.lists.decorations.TopBottomPaddingDecorator import de.rki.coronawarnapp.util.lists.diffutil.update @@ -81,11 +80,9 @@ class PersonOverviewFragment : Fragment(R.layout.person_overview_fragment), Auto }.show() } - ScanQrCode -> Toast.makeText( - requireContext(), - "TODO \uD83D\uDEA7 Tomorrow maybe?!", - Toast.LENGTH_LONG - ).show() + ScanQrCode -> doNavigate( + PersonOverviewFragmentDirections.actionPersonOverviewFragmentToDccQrCodeScanFragment() + ) OpenAppDeviceSettings -> openAppDetailsSettings() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonOverviewViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonOverviewViewModel.kt index b6d64f62878343cdab9128b31a0900db64900dbe..7ed8a797b39145d23bf75f2f81ffb7c924b2dc34 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonOverviewViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonOverviewViewModel.kt @@ -139,6 +139,7 @@ class PersonOverviewViewModel @AssistedInject constructor( private fun Set<PersonCertificates>.filterNotPending() = this .filter { !it.hasPendingTestCertificate() } + .sortedBy { it.highestPriorityCertificate.fullName } .sortedByDescending { it.isCwaUser } private suspend fun generateQrCode(qrCode: QrCodeString): Bitmap? = try { @@ -148,7 +149,7 @@ class PersonOverviewViewModel @AssistedInject constructor( null } - private fun refreshCertificate(identifier: TestCertificateIdentifier) = + fun refreshCertificate(identifier: TestCertificateIdentifier) = launch { val error = testCertificateRepository.refresh(identifier).mapNotNull { it.error }.singleOrNull() error?.let { events.postValue(ShowRefreshErrorDialog(error)) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/TestCertificateUIModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/TestCertificateUIModule.kt index 23e9fa4035cc3f7fa118c2e5421ce768e3251e5f..d1d1a28cf47b61e0e321df9c397fae0913b5d9f6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/TestCertificateUIModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/TestCertificateUIModule.kt @@ -4,8 +4,8 @@ import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.covidcertificate.person.ui.overview.PersonOverviewFragment import de.rki.coronawarnapp.covidcertificate.person.ui.overview.PersonOverviewFragmentModule -import de.rki.coronawarnapp.covidcertificate.test.ui.details.CovidCertificateDetailsFragment import de.rki.coronawarnapp.covidcertificate.test.ui.details.CovidCertificateDetailsModule +import de.rki.coronawarnapp.covidcertificate.test.ui.details.TestCertificateDetailsFragment @Module abstract class TestCertificateUIModule { @@ -14,7 +14,7 @@ abstract class TestCertificateUIModule { abstract fun certificatesFragment(): CertificatesFragment @ContributesAndroidInjector(modules = [CovidCertificateDetailsModule::class]) - abstract fun certificateDetailsFragment(): CovidCertificateDetailsFragment + abstract fun certificateDetailsFragment(): TestCertificateDetailsFragment @ContributesAndroidInjector(modules = [PersonOverviewFragmentModule::class]) abstract fun personOverviewFragment(): PersonOverviewFragment 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/TestCertificateDetailsFragment.kt similarity index 87% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/details/CovidCertificateDetailsFragment.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/test/ui/details/TestCertificateDetailsFragment.kt index c1e6501dab3b07d90de7a771569f387d51d95e89..3b25fb31f99e957f744e2ba8e275fac862311d13 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/TestCertificateDetailsFragment.kt @@ -13,7 +13,7 @@ import com.google.android.material.appbar.AppBarLayout import de.rki.coronawarnapp.R import de.rki.coronawarnapp.bugreporting.ui.toErrorDialogBuilder import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificate -import de.rki.coronawarnapp.databinding.FragmentCovidCertificateDetailsBinding +import de.rki.coronawarnapp.databinding.FragmentTestCertificateDetailsBinding import de.rki.coronawarnapp.ui.qrcode.fullscreen.QrCodeFullScreenFragmentArgs import de.rki.coronawarnapp.ui.view.onOffsetChange import de.rki.coronawarnapp.util.DialogHelper @@ -28,11 +28,11 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted import javax.inject.Inject -class CovidCertificateDetailsFragment : Fragment(R.layout.fragment_covid_certificate_details), AutoInject { +class TestCertificateDetailsFragment : Fragment(R.layout.fragment_test_certificate_details), AutoInject { @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory - private val binding by viewBinding<FragmentCovidCertificateDetailsBinding>() - private val args by navArgs<CovidCertificateDetailsFragmentArgs>() + private val binding by viewBinding<FragmentTestCertificateDetailsBinding>() + private val args by navArgs<TestCertificateDetailsFragmentArgs>() private val viewModel: CovidCertificateDetailsViewModel by cwaViewModelsAssisted( factoryProducer = { viewModelFactory }, constructorCall = { factory, _ -> @@ -58,7 +58,7 @@ class CovidCertificateDetailsFragment : Fragment(R.layout.fragment_covid_certifi viewModel.covidCertificate.observe(viewLifecycleOwner) { it?.let { onCertificateReady(it) } } } - private fun FragmentCovidCertificateDetailsBinding.onCertificateReady( + private fun FragmentTestCertificateDetailsBinding.onCertificateReady( testCertificate: TestCertificate ) { val testDateTime = testCertificate.sampleCollectedAt.toUserTimeZone() @@ -80,7 +80,7 @@ class CovidCertificateDetailsFragment : Fragment(R.layout.fragment_covid_certifi certificateId.text = testCertificate.certificateId } - private fun FragmentCovidCertificateDetailsBinding.onQrCodeReady(bitmap: Bitmap?) { + private fun FragmentTestCertificateDetailsBinding.onQrCodeReady(bitmap: Bitmap?) { qrCodeCard.apply { image.setImageBitmap(bitmap) progressBar.hide() @@ -88,12 +88,12 @@ class CovidCertificateDetailsFragment : Fragment(R.layout.fragment_covid_certifi } } - private fun FragmentCovidCertificateDetailsBinding.onError(error: Throwable) { + private fun FragmentTestCertificateDetailsBinding.onError(error: Throwable) { qrCodeCard.progressBar.hide() error.toErrorDialogBuilder(requireContext()).show() } - private fun FragmentCovidCertificateDetailsBinding.onNavEvent(event: CovidCertificateDetailsNavigation) { + private fun FragmentTestCertificateDetailsBinding.onNavEvent(event: CovidCertificateDetailsNavigation) { when (event) { CovidCertificateDetailsNavigation.Back -> popBackStack() is CovidCertificateDetailsNavigation.FullQrCode -> findNavController().navigate( @@ -105,7 +105,7 @@ class CovidCertificateDetailsFragment : Fragment(R.layout.fragment_covid_certifi } } - private fun FragmentCovidCertificateDetailsBinding.bindToolbar() = toolbar.apply { + private fun FragmentTestCertificateDetailsBinding.bindToolbar() = toolbar.apply { setNavigationOnClickListener { popBackStack() } setOnMenuItemClickListener { when (it.itemId) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/VaccinationCertificateUIModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/VaccinationCertificateUIModule.kt index 961a18479dd77f5d4a91411fd79a69c9f8f79851..f2778682c41b272c1970234dd93bfe259638ed93 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/VaccinationCertificateUIModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/VaccinationCertificateUIModule.kt @@ -8,8 +8,6 @@ import de.rki.coronawarnapp.covidcertificate.vaccination.ui.details.VaccinationD import de.rki.coronawarnapp.covidcertificate.vaccination.ui.details.VaccinationDetailsFragmentModule import de.rki.coronawarnapp.covidcertificate.vaccination.ui.list.VaccinationListFragment import de.rki.coronawarnapp.covidcertificate.vaccination.ui.list.VaccinationListFragmentModule -import de.rki.coronawarnapp.covidcertificate.vaccination.ui.scan.VaccinationQrCodeScanFragment -import de.rki.coronawarnapp.covidcertificate.vaccination.ui.scan.VaccinationQrCodeScanModule @Module abstract class VaccinationCertificateUIModule { @@ -20,9 +18,6 @@ abstract class VaccinationCertificateUIModule { @ContributesAndroidInjector(modules = [VaccinationDetailsFragmentModule::class]) abstract fun vaccinationDetailsFragment(): VaccinationDetailsFragment - @ContributesAndroidInjector(modules = [VaccinationQrCodeScanModule::class]) - abstract fun vaccinationQrCodeScanFragment(): VaccinationQrCodeScanFragment - @ContributesAndroidInjector(modules = [VaccinationConsentFragmentModule::class]) abstract fun vaccinationConsentFragment(): VaccinationConsentFragment } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/list/VaccinationListFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/list/VaccinationListFragment.kt index 0960f36b80d642062e182601974c309571e11358..9fbf7a9d937c768f78d67dd5676ceef83c213df4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/list/VaccinationListFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/list/VaccinationListFragment.kt @@ -17,8 +17,8 @@ import de.rki.coronawarnapp.bugreporting.ui.toErrorDialogBuilder import de.rki.coronawarnapp.covidcertificate.vaccination.core.VaccinatedPerson import de.rki.coronawarnapp.covidcertificate.vaccination.ui.list.VaccinationListViewModel.Event.DeleteVaccinationEvent import de.rki.coronawarnapp.covidcertificate.vaccination.ui.list.VaccinationListViewModel.Event.NavigateBack +import de.rki.coronawarnapp.covidcertificate.vaccination.ui.list.VaccinationListViewModel.Event.NavigateToQrCodeScanScreen import de.rki.coronawarnapp.covidcertificate.vaccination.ui.list.VaccinationListViewModel.Event.NavigateToVaccinationCertificateDetails -import de.rki.coronawarnapp.covidcertificate.vaccination.ui.list.VaccinationListViewModel.Event.NavigateToVaccinationQrCodeScanScreen import de.rki.coronawarnapp.covidcertificate.vaccination.ui.list.adapter.VaccinationListAdapter import de.rki.coronawarnapp.databinding.FragmentVaccinationListBinding import de.rki.coronawarnapp.ui.qrcode.fullscreen.QrCodeFullScreenFragmentArgs @@ -74,8 +74,8 @@ class VaccinationListFragment : Fragment(R.layout.fragment_vaccination_list), Au VaccinationListFragmentDirections .actionVaccinationListFragmentToVaccinationDetailsFragment(event.vaccinationCertificateId) ) - is NavigateToVaccinationQrCodeScanScreen -> doNavigate( - VaccinationListFragmentDirections.actionVaccinationListFragmentToVaccinationQrCodeScanFragment() + is NavigateToQrCodeScanScreen -> doNavigate( + VaccinationListFragmentDirections.actionVaccinationListFragmentToDccQrCodeScanFragment() ) is VaccinationListViewModel.Event.NavigateToQrCodeFullScreen -> { val navigatorExtras = diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/list/VaccinationListViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/list/VaccinationListViewModel.kt index 9dea2c3500fe560abdba6ef75945cd14967b6087..60fec58922449917e8efcc01eaa36ef4778a38eb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/list/VaccinationListViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/list/VaccinationListViewModel.kt @@ -142,7 +142,7 @@ class VaccinationListViewModel @AssistedInject constructor( }.toList() fun onRegisterNewVaccinationClick() { - events.postValue(Event.NavigateToVaccinationQrCodeScanScreen) + events.postValue(Event.NavigateToQrCodeScanScreen) } fun deleteVaccination(vaccinationCertificateId: String) { @@ -163,7 +163,7 @@ class VaccinationListViewModel @AssistedInject constructor( sealed class Event { data class NavigateToVaccinationCertificateDetails(val vaccinationCertificateId: String) : Event() - object NavigateToVaccinationQrCodeScanScreen : Event() + object NavigateToQrCodeScanScreen : Event() data class NavigateToQrCodeFullScreen(val qrCode: String, val positionInList: Int) : Event() data class DeleteVaccinationEvent(val vaccinationCertificateId: String, val position: Int? = null) : Event() object NavigateBack : Event() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/scan/VaccinationQrCodeScanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/scan/VaccinationQrCodeScanViewModel.kt deleted file mode 100644 index 64dfd89b0c4abfb147ee57d8bb2ae511f0f0a58e..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/covidcertificate/vaccination/ui/scan/VaccinationQrCodeScanViewModel.kt +++ /dev/null @@ -1,53 +0,0 @@ -package de.rki.coronawarnapp.covidcertificate.vaccination.ui.scan - -import com.journeyapps.barcodescanner.BarcodeResult -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.covidcertificate.common.exception.InvalidHealthCertificateException.ErrorCode.NO_VACCINATION_ENTRY -import de.rki.coronawarnapp.covidcertificate.common.exception.InvalidVaccinationCertificateException -import de.rki.coronawarnapp.covidcertificate.vaccination.core.qrcode.DccQrCodeValidator -import de.rki.coronawarnapp.covidcertificate.vaccination.core.qrcode.VaccinationCertificateQRCode -import de.rki.coronawarnapp.covidcertificate.vaccination.core.repository.VaccinationRepository -import de.rki.coronawarnapp.util.permission.CameraSettings -import de.rki.coronawarnapp.util.ui.SingleLiveEvent -import de.rki.coronawarnapp.util.viewmodel.CWAViewModel -import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory -import timber.log.Timber - -class VaccinationQrCodeScanViewModel @AssistedInject constructor( - private val cameraSettings: CameraSettings, - private val vaccinationQRCodeValidator: DccQrCodeValidator, - private val vaccinationRepository: VaccinationRepository -) : CWAViewModel() { - - val event = SingleLiveEvent<Event>() - - val errorEvent = SingleLiveEvent<Throwable>() - - fun onScanResult(barcodeResult: BarcodeResult) = launch { - try { - event.postValue(Event.QrCodeScanInProgress) - val qrCode = vaccinationQRCodeValidator.validate(barcodeResult.text) - if (qrCode !is VaccinationCertificateQRCode) { - throw InvalidVaccinationCertificateException(NO_VACCINATION_ENTRY) - } - val vaccinationCertificate = vaccinationRepository.registerVaccination(qrCode) - event.postValue(Event.QrCodeScanSucceeded(vaccinationCertificate.personIdentifier.codeSHA256)) - } catch (e: Throwable) { - errorEvent.postValue(e) - } - } - - fun setCameraDeniedPermanently(denied: Boolean) { - Timber.d("setCameraDeniedPermanently(denied=$denied)") - cameraSettings.isCameraDeniedPermanently.update { denied } - } - - sealed class Event { - object QrCodeScanInProgress : Event() - data class QrCodeScanSucceeded(val personIdentifierCodeSha256: String) : Event() - } - - @AssistedFactory - interface Factory : SimpleCWAViewModelFactory<VaccinationQrCodeScanViewModel> -} diff --git a/Corona-Warn-App/src/main/res/layout/fragment_covid_certificate_details.xml b/Corona-Warn-App/src/main/res/layout/fragment_test_certificate_details.xml similarity index 100% rename from Corona-Warn-App/src/main/res/layout/fragment_covid_certificate_details.xml rename to Corona-Warn-App/src/main/res/layout/fragment_test_certificate_details.xml diff --git a/Corona-Warn-App/src/main/res/navigation/certificate_graph.xml b/Corona-Warn-App/src/main/res/navigation/certificate_graph.xml index 8e21587d789c80a60f3b3f84e4d139ad168dca59..c9e3e74b14b1e9b519c224430baaaf541f106374 100644 --- a/Corona-Warn-App/src/main/res/navigation/certificate_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/certificate_graph.xml @@ -15,6 +15,9 @@ <action android:id="@+id/action_personOverviewFragment_to_vaccinationListFragment" app:destination="@id/vaccinationListFragment" /> + <action + android:id="@+id/action_personOverviewFragment_to_dccQrCodeScanFragment" + app:destination="@id/dccQrCodeScanFragment" /> </fragment> <fragment @@ -43,10 +46,10 @@ tools:layout="@layout/fragment_information_privacy" /> <fragment - android:id="@+id/covidCertificateDetailsFragment" - android:name="de.rki.coronawarnapp.covidcertificate.test.ui.details.CovidCertificateDetailsFragment" + android:id="@+id/testCertificateDetailsFragment" + android:name="de.rki.coronawarnapp.covidcertificate.test.ui.details.TestCertificateDetailsFragment" android:label="CovidCertificateDetailsFragment" - tools:layout="@layout/fragment_covid_certificate_details"> + tools:layout="@layout/fragment_test_certificate_details"> <argument android:name="testCertificateIdentifier" @@ -54,16 +57,20 @@ </fragment> <fragment - android:id="@+id/vaccinationQrCodeScanFragment" - android:name="de.rki.coronawarnapp.covidcertificate.vaccination.ui.scan.VaccinationQrCodeScanFragment" - android:label="VaccinationQrCodeScanFragment" + android:id="@+id/dccQrCodeScanFragment" + android:name="de.rki.coronawarnapp.covidcertificate.common.scan.DccQrCodeScanFragment" + android:label="DccQrCodeScanFragment" tools:layout="@layout/fragment_scan_qr_code"> <action - android:id="@+id/action_vaccinationQrCodeScanFragment_to_vaccinationListFragment" - app:destination="@id/vaccinationListFragment" - app:launchSingleTop="true" - app:popUpTo="@id/vaccinationQrCodeScanFragment" - app:popUpToInclusive="true"/> + android:id="@+id/action_dccQrCodeScanFragment_to_vaccinationDetailsFragment" + app:destination="@id/vaccinationDetailsFragment" + app:popUpTo="@id/dccQrCodeScanFragment" + app:popUpToInclusive="true" /> + <action + android:id="@+id/action_dccQrCodeScanFragment_to_testCertificateDetailsFragment" + app:destination="@id/testCertificateDetailsFragment" + app:popUpTo="@id/dccQrCodeScanFragment" + app:popUpToInclusive="true" /> </fragment> <fragment @@ -79,11 +86,11 @@ app:destination="@id/vaccinationDetailsFragment" /> <deepLink app:uri="coronawarnapp://vaccination-list/{personIdentifierCodeSha256}" /> <action - android:id="@+id/action_vaccinationListFragment_to_vaccinationQrCodeScanFragment" - app:destination="@id/vaccinationQrCodeScanFragment" /> + android:id="@+id/action_vaccinationListFragment_to_dccQrCodeScanFragment" + app:destination="@id/dccQrCodeScanFragment" /> <action android:id="@+id/action_vaccinationListFragment_to_covidCertificateDetailsFragment" - app:destination="@id/covidCertificateDetailsFragment" /> + app:destination="@id/testCertificateDetailsFragment" /> </fragment> <fragment @@ -95,4 +102,4 @@ android:name="vaccinationCertificateId" app:argType="string" /> </fragment> -</navigation> \ No newline at end of file +</navigation> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonCertificatesData.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonCertificatesData.kt new file mode 100644 index 0000000000000000000000000000000000000000..49097d8ba0f92992a5c585a1b8ed98056fb94473 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonCertificatesData.kt @@ -0,0 +1,71 @@ +package de.rki.coronawarnapp.covidcertificate.person.ui.overview + +import de.rki.coronawarnapp.covidcertificate.common.certificate.CertificatePersonIdentifier +import de.rki.coronawarnapp.covidcertificate.common.qrcode.QrCodeString +import de.rki.coronawarnapp.covidcertificate.person.core.PersonCertificates +import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificate +import org.joda.time.Instant +import org.joda.time.LocalDate + +object PersonCertificatesData { + val certificatesWithPending = mutableSetOf<PersonCertificates>() + .apply { + add(PersonCertificates(listOf(testCertificate(fullName = "Andrea Schneider")))) + add(PersonCertificates(listOf(testCertificate(fullName = "Max Mustermann", isPending = true)))) + add(PersonCertificates(listOf(testCertificate(fullName = "Zeebee")), isCwaUser = true)) + } + val certificatesWithUpdating = mutableSetOf<PersonCertificates>().apply { + add(PersonCertificates(listOf(testCertificate(fullName = "Andrea Schneider")))) + add(PersonCertificates(listOf(testCertificate(fullName = "Zeebee")), isCwaUser = true)) + add( + PersonCertificates( + listOf(testCertificate(fullName = "Max Mustermann", isPending = true, isUpdating = true)) + ) + ) + } + val certificatesWithCwaUser = mutableSetOf<PersonCertificates>().apply { + add(PersonCertificates(listOf(testCertificate(fullName = "Max Mustermann")))) + add(PersonCertificates(listOf(testCertificate(fullName = "Erika Musterfrau")))) + add(PersonCertificates(listOf(testCertificate(fullName = "Andrea Schneider")))) + add(PersonCertificates(listOf(testCertificate(fullName = "Zeebee")), isCwaUser = true)) + add(PersonCertificates(listOf(testCertificate(fullName = "Zeebee A")))) + } + val certificatesWithoutCwaUser = mutableSetOf<PersonCertificates>().apply { + add(PersonCertificates(listOf(testCertificate("Max Mustermann")))) + add(PersonCertificates(listOf(testCertificate("Erika Musterfrau")))) + add(PersonCertificates(listOf(testCertificate("Andrea Schneider")))) + } +} + +fun testCertificate( + fullName: String, + isPending: Boolean = false, + isUpdating: Boolean = false +) = object : TestCertificate { + override val targetName: String = "targetName" + override val testType: String = "testType" + override val testResult: String = "testResult" + override val testName: String = "testName" + override val testNameAndManufacturer: String = "testNameAndManufacturer" + override val sampleCollectedAt: Instant = Instant.EPOCH + override val testCenter: String = "" + override val registeredAt: Instant = Instant.EPOCH + override val isUpdatingData: Boolean = isUpdating + override val isCertificateRetrievalPending: Boolean = isPending + override val issuer: String = "issuer" + override val issuedAt: Instant = Instant.EPOCH + override val expiresAt: Instant = Instant.EPOCH + override val qrCode: QrCodeString = "qrCode" + override val firstName: String = "firstName" + override val lastName: String = "lastName" + override val fullName: String = fullName + override val dateOfBirth: LocalDate = LocalDate.now() + override val personIdentifier = CertificatePersonIdentifier( + dateOfBirth = LocalDate(System.currentTimeMillis()), + lastNameStandardized = "lastNameStandardized", + firstNameStandardized = "firstNameStandardized" + ) + override val certificateIssuer: String = "certificateIssuer" + override val certificateCountry: String = "certificateCountry" + override val certificateId: String = "certificateId" +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonOverviewViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonOverviewViewModelTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..5aea178460619087a437010c9aed7daddddf7431 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/covidcertificate/person/ui/overview/PersonOverviewViewModelTest.kt @@ -0,0 +1,149 @@ +package de.rki.coronawarnapp.covidcertificate.person.ui.overview + +import android.content.Context +import de.rki.coronawarnapp.contactdiary.util.getLocale +import de.rki.coronawarnapp.covidcertificate.person.core.PersonCertificatesProvider +import de.rki.coronawarnapp.covidcertificate.person.ui.overview.items.CovidTestCertificatePendingCard +import de.rki.coronawarnapp.covidcertificate.person.ui.overview.items.PersonCertificateCard +import de.rki.coronawarnapp.covidcertificate.test.core.TestCertificateRepository +import de.rki.coronawarnapp.covidcertificate.valueset.ValueSetsRepository +import de.rki.coronawarnapp.presencetracing.checkins.qrcode.QrCodeGenerator +import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.permission.CameraPermissionProvider +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 io.mockk.mockkStatic +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import testhelpers.BaseTest +import testhelpers.TestDispatcherProvider +import testhelpers.extensions.InstantExecutorExtension +import testhelpers.extensions.getOrAwaitValue +import java.util.Locale + +@ExtendWith(InstantExecutorExtension::class) +class PersonOverviewViewModelTest : BaseTest() { + + @MockK lateinit var qrCodeGenerator: QrCodeGenerator + @MockK lateinit var personCertificatesProvider: PersonCertificatesProvider + @MockK lateinit var testCertificateRepository: TestCertificateRepository + @MockK lateinit var refreshResult: TestCertificateRepository.RefreshResult + @MockK lateinit var valueSetsRepository: ValueSetsRepository + @MockK lateinit var context: Context + @MockK lateinit var cameraPermissionProvider: CameraPermissionProvider + + @BeforeEach + fun setup() { + MockKAnnotations.init(this, true) + mockkStatic("de.rki.coronawarnapp.contactdiary.util.ContactDiaryExtensionsKt") + coEvery { testCertificateRepository.refresh(any()) } returns setOf(refreshResult) + coEvery { qrCodeGenerator.createQrCode(any(), any(), any(), any(), any()) } returns mockk() + every { personCertificatesProvider.personCertificates } returns emptyFlow() + every { refreshResult.error } returns null + every { testCertificateRepository.certificates } returns emptyFlow() + every { context.getLocale() } returns Locale.GERMAN + every { valueSetsRepository.triggerUpdateValueSet(any()) } just Runs + every { cameraPermissionProvider.deniedPermanently } returns flowOf(false) + } + + @Test + fun `refreshCertificate causes an error dialog event`() { + val error = mockk<Exception>() + every { refreshResult.error } returns error + + instance.apply { + refreshCertificate("Identifier") + events.getOrAwaitValue() shouldBe ShowRefreshErrorDialog(error) + } + } + + @Test + fun `refreshCertificate triggers refresh operation in repo`() { + instance.refreshCertificate("Identifier") + coVerify { testCertificateRepository.refresh(any()) } + } + + @Test + fun `deleteTestCertificate deletes certificates from repo`() { + coEvery { testCertificateRepository.deleteCertificate(any()) } just Runs + instance.apply { + deleteTestCertificate("Identifier") + } + + coEvery { testCertificateRepository.deleteCertificate(any()) } + } + + @Test + fun onScanQrCode() { + instance.apply { + onScanQrCode() + events.getOrAwaitValue() shouldBe ScanQrCode + } + } + + @Test + fun `Sorting - List has pending certificate`() { + every { personCertificatesProvider.personCertificates } returns + flowOf(PersonCertificatesData.certificatesWithPending) + instance.personCertificates.getOrAwaitValue().apply { + (get(0) as CovidTestCertificatePendingCard.Item).apply { certificate.fullName shouldBe "Max Mustermann" } + (get(1) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Zeebee" } + (get(2) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Andrea Schneider" } + } + } + + @Test + fun `Sorting - List has pending & updating certificate`() { + every { personCertificatesProvider.personCertificates } returns + flowOf(PersonCertificatesData.certificatesWithUpdating) + instance.personCertificates.getOrAwaitValue().apply { + (get(0) as CovidTestCertificatePendingCard.Item).apply { certificate.fullName shouldBe "Max Mustermann" } + (get(1) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Zeebee" } + (get(2) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Andrea Schneider" } + } + } + + @Test + fun `Sorting - List has no CWA user`() { + every { personCertificatesProvider.personCertificates } returns + flowOf(PersonCertificatesData.certificatesWithoutCwaUser) + instance.personCertificates.getOrAwaitValue().apply { + (get(0) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Andrea Schneider" } + (get(1) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Erika Musterfrau" } + (get(2) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Max Mustermann" } + } + } + + @Test + fun `Sorting - List has CWA user`() { + every { personCertificatesProvider.personCertificates } returns + flowOf(PersonCertificatesData.certificatesWithCwaUser) + instance.personCertificates.getOrAwaitValue().apply { + (get(0) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Zeebee" } // CWA user + (get(1) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Andrea Schneider" } + (get(2) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Erika Musterfrau" } + (get(3) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Max Mustermann" } + (get(4) as PersonCertificateCard.Item).apply { certificate.fullName shouldBe "Zeebee A" } + } + } + + private val instance + get() = PersonOverviewViewModel( + dispatcherProvider = TestDispatcherProvider(), + testCertificateRepository = testCertificateRepository, + certificatesProvider = personCertificatesProvider, + qrCodeGenerator = qrCodeGenerator, + valueSetsRepository = valueSetsRepository, + context = context, + cameraPermissionProvider = cameraPermissionProvider + ) +}