From defb56a5bc43e48d521496819d645b4494fe358c Mon Sep 17 00:00:00 2001
From: AlexanderAlferov <64849422+AlexanderAlferov@users.noreply.github.com>
Date: Mon, 7 Jun 2021 15:14:10 +0300
Subject: [PATCH] Test certificates success and error cards (EXPOSUREAPP-7439,
 -7454, -7452) (#3356)

* Test certificates success and error cards

* update layout

* rename cards

* fix detekt

* use TestCertificateRepository

* update CertificatesViewModel

* code updates

* Refactoring

* Fix conflicts

Co-authored-by: Juraj Kusnier <jurajkusnier@gmail.com>
Co-authored-by: Juraj Kusnier <jurajkusnier@users.noreply.github.com>
Co-authored-by: Mohamed Metwalli <mohamed.metwalli@sap.com>
---
 .../ui/certificates/CertificatesAdapter.kt    | 10 +-
 .../ui/certificates/CertificatesViewModel.kt  | 61 ++++++++++--
 .../cards/CovidCertificateTestItem.kt         | 11 +++
 .../cards/CovidTestCertificateCard.kt         | 45 +++++++++
 .../cards/CovidTestCertificateErrorCard.kt    | 45 +++++++++
 ...Card.kt => NoCovidTestCertificatesCard.kt} |  4 +-
 .../src/main/res/drawable/ic_eu_stars.xml     | 76 +++++++++++++++
 .../vaccination_test_success_gradient.xml     |  7 ++
 .../main/res/layout/covid_test_error_card.xml | 82 ++++++++++++++++
 .../res/layout/covid_test_success_card.xml    | 96 +++++++++++++++++++
 .../values-de/green_certificate_strings.xml   |  7 ++
 .../res/values/green_certificate_strings.xml  |  6 ++
 12 files changed, 436 insertions(+), 14 deletions(-)
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidCertificateTestItem.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidTestCertificateCard.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidTestCertificateErrorCard.kt
 rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/cards/{BottomInfoVaccinationCard.kt => NoCovidTestCertificatesCard.kt} (83%)
 create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_eu_stars.xml
 create mode 100644 Corona-Warn-App/src/main/res/drawable/vaccination_test_success_gradient.xml
 create mode 100644 Corona-Warn-App/src/main/res/layout/covid_test_error_card.xml
 create mode 100644 Corona-Warn-App/src/main/res/layout/covid_test_success_card.xml

diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/CertificatesAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/CertificatesAdapter.kt
index aabec57cb..7fc2da079 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/CertificatesAdapter.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/CertificatesAdapter.kt
@@ -11,11 +11,13 @@ import de.rki.coronawarnapp.util.lists.modular.ModularAdapter
 import de.rki.coronawarnapp.util.lists.modular.mods.DataBinderMod
 import de.rki.coronawarnapp.util.lists.modular.mods.StableIdMod
 import de.rki.coronawarnapp.util.lists.modular.mods.TypedVHCreatorMod
-import de.rki.coronawarnapp.vaccination.ui.cards.BottomInfoVaccinationCard
+import de.rki.coronawarnapp.vaccination.ui.cards.NoCovidTestCertificatesCard
 import de.rki.coronawarnapp.vaccination.ui.cards.CreateVaccinationCard
 import de.rki.coronawarnapp.vaccination.ui.cards.HeaderInfoVaccinationCard
 import de.rki.coronawarnapp.vaccination.ui.cards.ImmuneVaccinationCard
 import de.rki.coronawarnapp.vaccination.ui.cards.VaccinationCard
+import de.rki.coronawarnapp.greencertificate.ui.certificates.cards.CovidTestCertificateErrorCard
+import de.rki.coronawarnapp.greencertificate.ui.certificates.cards.CovidTestCertificateCard
 
 class CertificatesAdapter :
     ModularAdapter<CertificatesAdapter.CertificatesItemVH<CertificatesItem, ViewBinding>>(),
@@ -36,7 +38,11 @@ class CertificatesAdapter :
                 },
                 TypedVHCreatorMod({ data[it] is CreateVaccinationCard.Item }) { CreateVaccinationCard(it) },
                 TypedVHCreatorMod({ data[it] is HeaderInfoVaccinationCard.Item }) { HeaderInfoVaccinationCard(it) },
-                TypedVHCreatorMod({ data[it] is BottomInfoVaccinationCard.Item }) { BottomInfoVaccinationCard(it) },
+                TypedVHCreatorMod({ data[it] is NoCovidTestCertificatesCard.Item }) { NoCovidTestCertificatesCard(it) },
+                TypedVHCreatorMod({ data[it] is CovidTestCertificateCard.Item }) { CovidTestCertificateCard(it) },
+                TypedVHCreatorMod({ data[it] is CovidTestCertificateErrorCard.Item }) {
+                    CovidTestCertificateErrorCard(it)
+                },
             )
         )
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/CertificatesViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/CertificatesViewModel.kt
index be67be933..096e18dd5 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/CertificatesViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/CertificatesViewModel.kt
@@ -4,6 +4,9 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.asLiveData
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.TestCertificateRepository
+import de.rki.coronawarnapp.coronatest.type.TestCertificateContainer
+import de.rki.coronawarnapp.coronatest.type.TestCertificateIdentifier
 import de.rki.coronawarnapp.greencertificate.ui.certificates.items.CertificatesItem
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
@@ -11,28 +14,38 @@ import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
 import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson
 import de.rki.coronawarnapp.vaccination.core.VaccinationSettings
 import de.rki.coronawarnapp.vaccination.core.repository.VaccinationRepository
-import de.rki.coronawarnapp.vaccination.ui.cards.BottomInfoVaccinationCard
+import de.rki.coronawarnapp.vaccination.ui.cards.NoCovidTestCertificatesCard
 import de.rki.coronawarnapp.vaccination.ui.cards.CreateVaccinationCard
 import de.rki.coronawarnapp.vaccination.ui.cards.HeaderInfoVaccinationCard
 import de.rki.coronawarnapp.vaccination.ui.cards.ImmuneVaccinationCard
 import de.rki.coronawarnapp.vaccination.ui.cards.VaccinationCard
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
+import de.rki.coronawarnapp.greencertificate.ui.certificates.cards.CovidTestCertificateErrorCard
+import de.rki.coronawarnapp.greencertificate.ui.certificates.cards.CovidTestCertificateCard
 
 class CertificatesViewModel @AssistedInject constructor(
     vaccinationRepository: VaccinationRepository,
-    private val vaccinationSettings: VaccinationSettings
+    private val vaccinationSettings: VaccinationSettings,
+    private val testCertificateRepository: TestCertificateRepository
 ) : CWAViewModel() {
 
     val events = SingleLiveEvent<CertificatesFragmentEvents>()
 
+    private fun refreshTestCertificate(identifier: TestCertificateIdentifier) {
+        launch {
+            testCertificateRepository.refresh(identifier)
+        }
+    }
+
     val screenItems: LiveData<List<CertificatesItem>> =
-        vaccinationRepository.vaccinationInfos.map { vaccinatedPersons ->
-            mutableListOf<CertificatesItem>().apply {
-                add(HeaderInfoVaccinationCard.Item)
-                addVaccinationCards(vaccinatedPersons)
-                add(BottomInfoVaccinationCard.Item)
-            }
-        }.asLiveData()
+        vaccinationRepository.vaccinationInfos
+            .combine(testCertificateRepository.certificates) { vaccinatedPersons, certificates ->
+                mutableListOf<CertificatesItem>().apply {
+                    add(HeaderInfoVaccinationCard.Item)
+                    addVaccinationCards(vaccinatedPersons)
+                    addTestCertificateCards(certificates)
+                }
+            }.asLiveData()
 
     private fun MutableList<CertificatesItem>.addVaccinationCards(vaccinatedPersons: Set<VaccinatedPerson>) {
         vaccinatedPersons.forEach { vaccinatedPerson ->
@@ -76,6 +89,34 @@ class CertificatesViewModel @AssistedInject constructor(
         }
     }
 
+    private fun MutableList<CertificatesItem>.addTestCertificateCards(certificates: Set<TestCertificateContainer>) {
+        certificates.forEach { certificate ->
+            if (certificate.isCertificateRetrievalPending) {
+                add(
+                    CovidTestCertificateErrorCard.Item(
+                        testDate = certificate.registeredAt,
+                        onClickAction = {
+                            refreshTestCertificate(certificate.identifier)
+                        }
+                    )
+                )
+            } else {
+                add(
+                    CovidTestCertificateCard.Item(
+                        testDate = certificate.registeredAt,
+                        testPerson =
+                        certificate.toTestCertificate(null)?.firstName + " " +
+                            certificate.toTestCertificate(null)?.lastName
+                    )
+                )
+            }
+        }
+
+        if (certificates.isEmpty()) {
+            add(NoCovidTestCertificatesCard.Item)
+        }
+    }
+
     @AssistedFactory
     interface Factory : SimpleCWAViewModelFactory<CertificatesViewModel>
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidCertificateTestItem.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidCertificateTestItem.kt
new file mode 100644
index 000000000..b84241da2
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidCertificateTestItem.kt
@@ -0,0 +1,11 @@
+package de.rki.coronawarnapp.greencertificate.ui.certificates.cards
+
+import de.rki.coronawarnapp.greencertificate.ui.certificates.items.CertificatesItem
+import org.joda.time.Instant
+
+interface CovidCertificateTestItem : CertificatesItem {
+    val testDate: Instant
+
+    override val stableId: Long
+        get() = testDate.hashCode().toLong()
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidTestCertificateCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidTestCertificateCard.kt
new file mode 100644
index 000000000..bcdf4a77e
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidTestCertificateCard.kt
@@ -0,0 +1,45 @@
+package de.rki.coronawarnapp.greencertificate.ui.certificates.cards
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.CovidTestSuccessCardBinding
+import de.rki.coronawarnapp.greencertificate.ui.certificates.CertificatesAdapter
+import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+import org.joda.time.Instant
+import org.joda.time.format.DateTimeFormat
+
+class CovidTestCertificateCard(parent: ViewGroup) :
+    CertificatesAdapter.CertificatesItemVH<CovidTestCertificateCard.Item, CovidTestSuccessCardBinding>(
+        R.layout.home_card_container_layout,
+        parent
+    ) {
+
+    override val viewBinding = lazy {
+        CovidTestSuccessCardBinding.inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
+    }
+
+    override val onBindData: CovidTestSuccessCardBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { item, _ ->
+
+        val dateTime = item.testDate.toDateTime()
+        val dateFormat = DateTimeFormat.shortDate()
+        val timeFormat = DateTimeFormat.shortTime()
+
+        testTime.text = context.getString(
+            R.string.test_certificate_time,
+            dateTime.toString(dateFormat),
+            dateTime.toString(timeFormat),
+        )
+
+        personName.text = item.testPerson
+    }
+
+    data class Item(
+        override val testDate: Instant,
+        val testPerson: String,
+    ) : CovidCertificateTestItem, HasPayloadDiffer {
+        override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidTestCertificateErrorCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidTestCertificateErrorCard.kt
new file mode 100644
index 000000000..52f0fbc9c
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/greencertificate/ui/certificates/cards/CovidTestCertificateErrorCard.kt
@@ -0,0 +1,45 @@
+package de.rki.coronawarnapp.greencertificate.ui.certificates.cards
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.CovidTestErrorCardBinding
+import de.rki.coronawarnapp.greencertificate.ui.certificates.CertificatesAdapter
+import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
+import org.joda.time.Instant
+import org.joda.time.format.DateTimeFormat
+
+class CovidTestCertificateErrorCard(parent: ViewGroup) :
+    CertificatesAdapter.CertificatesItemVH<CovidTestCertificateErrorCard.Item, CovidTestErrorCardBinding>(
+        R.layout.home_card_container_layout,
+        parent
+    ) {
+
+    override val viewBinding = lazy {
+        CovidTestErrorCardBinding.inflate(layoutInflater, itemView.findViewById(R.id.card_container), true)
+    }
+
+    override val onBindData: CovidTestErrorCardBinding.(
+        item: Item,
+        payloads: List<Any>
+    ) -> Unit = { item, _ ->
+
+        val dateTime = item.testDate.toDateTime()
+        val dateFormat = DateTimeFormat.shortDate()
+        val timeFormat = DateTimeFormat.shortTime()
+
+        testTime.text = context.getString(
+            R.string.test_certificate_time,
+            dateTime.toString(dateFormat),
+            dateTime.toString(timeFormat),
+        )
+
+        retryButton.setOnClickListener { item.onClickAction(item) }
+    }
+
+    data class Item(
+        override val testDate: Instant,
+        val onClickAction: (Item) -> Unit,
+    ) : CovidCertificateTestItem, HasPayloadDiffer {
+        override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/cards/BottomInfoVaccinationCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/cards/NoCovidTestCertificatesCard.kt
similarity index 83%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/cards/BottomInfoVaccinationCard.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/cards/NoCovidTestCertificatesCard.kt
index c2b5fe491..a1da82315 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/cards/BottomInfoVaccinationCard.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/cards/NoCovidTestCertificatesCard.kt
@@ -6,8 +6,8 @@ import de.rki.coronawarnapp.databinding.VaccinationBottomInfoCardBinding
 import de.rki.coronawarnapp.greencertificate.ui.certificates.CertificatesAdapter
 import de.rki.coronawarnapp.greencertificate.ui.certificates.items.CertificatesItem
 
-class BottomInfoVaccinationCard(parent: ViewGroup) :
-    CertificatesAdapter.CertificatesItemVH<BottomInfoVaccinationCard.Item, VaccinationBottomInfoCardBinding>(
+class NoCovidTestCertificatesCard(parent: ViewGroup) :
+    CertificatesAdapter.CertificatesItemVH<NoCovidTestCertificatesCard.Item, VaccinationBottomInfoCardBinding>(
         R.layout.dashed_line_container_layout,
         parent
     ) {
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_eu_stars.xml b/Corona-Warn-App/src/main/res/drawable/ic_eu_stars.xml
new file mode 100644
index 000000000..04d246e2d
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_eu_stars.xml
@@ -0,0 +1,76 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="105dp"
+    android:height="200dp"
+    android:viewportWidth="105"
+    android:viewportHeight="200">
+  <path
+      android:pathData="M112.879,-9L110.99,-3.328H104.902L109.94,0.453L108.051,6.125L112.879,2.554L117.918,6.125L116.028,0.243L120.857,-3.118H114.769L112.879,-9Z"
+      android:fillColor="#ffffff"
+      android:fillAlpha="0.2"/>
+  <path
+      android:strokeWidth="1"
+      android:pathData="M113.356,-9.153L112.888,-10.608L112.405,-9.158L110.63,-3.828H104.902H103.403L104.602,-2.928L109.352,0.637L107.577,5.967L107.077,7.467L108.348,6.527L112.886,3.171L117.629,6.533L118.853,7.4L118.394,5.972L116.618,0.442L121.143,-2.708L122.45,-3.618H120.857H115.133L113.356,-9.153Z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M62.496,4.654L60.607,10.326H54.519L59.557,14.107L57.668,19.779L62.496,16.208L67.325,19.779L65.435,13.897L70.474,10.536H64.386L62.496,4.654Z"
+      android:fillColor="#ffffff"
+      android:fillAlpha="0.2"/>
+  <path
+      android:strokeWidth="1"
+      android:pathData="M62.972,4.501L62.505,3.046L62.022,4.496L60.246,9.826H54.519H53.02L54.219,10.726L58.969,14.291L57.193,19.621L56.694,21.122L57.965,20.181L62.496,16.83L67.027,20.181L68.275,21.104L67.801,19.626L66.027,14.104L70.751,10.952L72.124,10.036H70.474H64.75L62.972,4.501Z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M25.758,41.626L23.869,47.508H17.781L22.819,51.079L20.93,56.751L25.758,53.18L30.587,56.751L28.697,51.079L33.736,47.508H27.647L25.758,41.626Z"
+      android:fillColor="#ffffff"
+      android:fillAlpha="0.2"/>
+  <path
+      android:strokeWidth="1"
+      android:pathData="M26.234,41.474L25.758,39.992L25.282,41.474L23.504,47.008H17.781H16.211L17.492,47.916L22.228,51.273L20.455,56.593L19.955,58.094L21.227,57.153L25.758,53.802L30.289,57.153L31.561,58.094L31.061,56.593L29.289,51.273L34.025,47.916L35.306,47.008H33.736H28.012L26.234,41.474Z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M11.903,92.043L10.013,97.714H3.925L8.754,101.285L6.864,107.167L11.903,103.596L16.731,107.167L14.842,101.285L19.67,97.924H13.792L11.903,92.043Z"
+      android:fillColor="#ffffff"
+      android:fillAlpha="0.2"/>
+  <path
+      android:strokeWidth="1"
+      android:pathData="M12.379,91.89L11.911,90.435L11.428,91.884L9.653,97.214H3.925H2.408L3.628,98.116L8.168,101.474L6.388,107.014L5.929,108.443L7.153,107.575L11.896,104.213L16.434,107.569L17.682,108.492L17.207,107.014L15.431,101.485L19.956,98.335L21.264,97.424H19.67H14.157L12.379,91.89Z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M25.548,142.669L23.659,148.341H17.571L22.399,151.912L20.51,157.584L25.548,154.013L30.377,157.584L28.487,151.912L33.316,148.341H27.438L25.548,142.669Z"
+      android:fillColor="#ffffff"
+      android:fillAlpha="0.2"/>
+  <path
+      android:strokeWidth="1"
+      android:pathData="M26.023,142.511L25.548,141.087L25.074,142.511L23.298,147.841H17.571H16.054L17.274,148.743L21.81,152.098L20.035,157.426L19.553,158.875L20.799,157.992L25.542,154.63L30.079,157.986L31.351,158.926L30.851,157.426L29.076,152.098L33.613,148.743L34.833,147.841H33.316H27.798L26.023,142.511Z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M62.496,179.641L60.607,185.313H54.519L59.347,188.884L57.458,194.766L62.496,191.195L67.325,194.766L65.435,188.884L70.264,185.313H64.386L62.496,179.641Z"
+      android:fillColor="#ffffff"
+      android:fillAlpha="0.2"/>
+  <path
+      android:strokeWidth="1"
+      android:pathData="M62.971,179.483L62.496,178.059L62.022,179.483L60.246,184.813H54.519H53.002L54.222,185.715L58.762,189.073L56.982,194.613L56.523,196.042L57.747,195.174L62.49,191.812L67.027,195.168L68.275,196.091L67.801,194.613L66.021,189.073L70.561,185.715L71.781,184.813H70.264H64.746L62.971,179.483Z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M112.879,193.085L110.99,198.757H104.902L109.94,202.328L108.051,208L112.879,204.429L117.918,208L116.028,202.328L120.857,198.757H114.769L112.879,193.085Z"
+      android:fillColor="#ffffff"
+      android:fillAlpha="0.2"/>
+  <path
+      android:strokeWidth="1"
+      android:pathData="M113.354,192.927L112.879,191.503L112.405,192.927L110.63,198.257H104.902H103.332L104.613,199.165L109.349,202.522L107.577,207.842L107.077,209.342L108.348,208.402L112.886,205.046L117.629,208.408L118.875,209.291L118.392,207.842L116.617,202.514L121.154,199.159L122.374,198.257H120.857H115.129L113.354,192.927Z"
+      android:strokeAlpha="0.2"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/drawable/vaccination_test_success_gradient.xml b/Corona-Warn-App/src/main/res/drawable/vaccination_test_success_gradient.xml
new file mode 100644
index 000000000..d391deadf
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/vaccination_test_success_gradient.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+        android:angle="180"
+        android:endColor="#2E854B"
+        android:startColor="#35B55F" />
+</shape>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/covid_test_error_card.xml b/Corona-Warn-App/src/main/res/layout/covid_test_error_card.xml
new file mode 100644
index 000000000..4e38c7504
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/covid_test_error_card.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="200dp">
+
+    <TextView
+        android:id="@+id/card_title"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="@dimen/card_padding"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:focusable="false"
+        android:text="@string/info_banner_title_1"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/vaccination_label"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:focusable="false"
+        android:text="@string/info_banner_title_2"
+        android:textStyle="bold"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/card_title" />
+
+    <TextView
+        android:id="@+id/body"
+        style="@style/subtitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="@dimen/spacing_small"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:focusable="false"
+        android:text="@string/test_certificate_error_label"
+        android:textStyle="bold"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/vaccination_label" />
+
+    <TextView
+        android:id="@+id/test_time"
+        style="@style/body2"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="@dimen/spacing_mega_tiny"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:focusable="false"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/body"
+        tools:text="Test durchgeführt am 12.04.21, 18:01 Uhr " />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/retry_button"
+        style="@style/Widget.MaterialComponents.Button.OutlinedButton"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/card_padding"
+        android:layout_marginTop="@dimen/spacing_small"
+        android:layout_marginEnd="@dimen/card_padding"
+        android:layout_marginBottom="@dimen/spacing_small"
+        android:text="@string/test_certificate_error_retry_button"
+        android:textColor="@color/colorAccent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/test_time"
+        app:strokeColor="@color/colorAccent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/covid_test_success_card.xml b/Corona-Warn-App/src/main/res/layout/covid_test_success_card.xml
new file mode 100644
index 000000000..985db9969
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/covid_test_success_card.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/vaccination_test_success_gradient"
+    android:minHeight="200dp"
+    tools:ignore="UnusedAttribute">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:srcCompat="@drawable/ic_eu_stars" />
+
+    <TextView
+        android:id="@+id/title1"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/card_padding"
+        android:layout_marginTop="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/info_banner_title_1"
+        android:textColor="@color/colorTextPrimary1InvertedStable"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/title2"
+        style="@style/headline5"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/card_padding"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:text="@string/info_banner_title_2"
+        android:textColor="@color/colorTextPrimary1InvertedStable"
+        android:textStyle="bold"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title1" />
+
+    <ImageView
+        android:id="@+id/prc_icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginStart="@dimen/card_padding"
+        android:importantForAccessibility="no"
+        app:layout_constraintBottom_toBottomOf="@id/test_time"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@id/test_time"
+        app:srcCompat="@drawable/ic_risk_details_pcr"
+        app:tint="@color/colorStableLight" />
+
+    <TextView
+        android:id="@+id/person_name"
+        style="@style/body1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/card_padding"
+        android:layout_marginTop="@dimen/spacing_small"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:textColor="@color/colorTextPrimary1InvertedStable"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title2"
+        tools:text="Andrea Schneider" />
+
+    <TextView
+        android:id="@+id/test_time"
+        style="@style/body2"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginBottom="@dimen/spacing_normal"
+        android:accessibilityHeading="true"
+        android:focusable="false"
+        android:textColor="@color/colorTextPrimary1InvertedStable"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/prc_icon"
+        app:layout_constraintTop_toBottomOf="@id/person_name"
+        app:layout_constraintVertical_bias="1"
+        app:layout_constraintVertical_chainStyle="packed"
+        tools:text="Test durchgeführt am 12.04.21, 18:01 Uhr" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
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 3061c74c4..6c4b5a1fd 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
@@ -48,4 +48,11 @@
     <string name="info_banner_title_2">COVID-Testzertifikat</string>
     <!-- XTXT: Green certificate info card body  -->
     <string name="info_banner_body">Registrieren Sie einen Test auf der Startseite und stimmen Sie zu, ein digitales Testzertifikat zu erhalten. Sobald das Zertifikat vorliegt, wird es hier angezeigt.</string>
+
+    <!-- XTXT: Test certificate time -->
+    <string name="test_certificate_time">Test durchgeführt am %1$s, %2$s Uhr</string>
+    <!-- XTXT: Test error label -->
+    <string name="test_certificate_error_label">Fehler bei der Zertifikatsabfrage</string>
+    <!-- XBUT: Test error retry button -->
+    <string name="test_certificate_error_retry_button">Nochmal versuchen</string>
 </resources>
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 946f819e0..8d4c8deba 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
@@ -49,4 +49,10 @@
     <!-- XTXT: Green certificate info card body  -->
     <string name="info_banner_body">Registrieren Sie einen Test auf der Startseite und stimmen Sie zu, ein digitales Testzertifikat zu erhalten. Sobald das Zertifikat vorliegt, wird es hier angezeigt.</string>
 
+    <!-- XTXT: Test certificate time -->
+    <string name="test_certificate_time">Test durchgeführt am %1$s, %2$s</string>
+    <!-- XTXT: Test error label -->
+    <string name="test_certificate_error_label">Fehler bei der Zertifikatsabfrage</string>
+    <!-- XBUT: Test error retry button -->
+    <string name="test_certificate_error_retry_button">Nochmal versuchen</string>
 </resources>
-- 
GitLab