From 7c56e97ec603a2820e82facf56ccadaa66066e0e Mon Sep 17 00:00:00 2001
From: Lukas Lechner <lukas.lechner@sap.com>
Date: Mon, 10 May 2021 16:15:59 +0200
Subject: [PATCH] Screen: Vaccination List (EXPOSUREAPP-6734) (#3082)

* Create first version of incomplete vaccination list screen with mock data

* Add UiState for VaccinationListFragment.kt

* Set Toolbar Overlay

* Fine tune fragment_vaccination_list.xml

* Use ModularAdapter

* Add click listener to navigate to detail fragment

* Fix nav_graph issue

* Adjust layouts for night mode

* Fix vaccination card colors

* Fix mock data

* Revert changes in Project.xml

* Add TODO

* Show blue background on complete vaccination status

* Show subtitle on complete vaccination status

* Add VaccinationListCertificateCardItem.kt

* Show Refresh button on complete vaccination status

* Add fragment in vaccination_nav_graph.xml

* Connect Vaccination List ViewModel to Vaccination Repository

* Create new mock data

* Adapt VaccinationTestFragment.kt

* Try to make sonar happy

* Use VaccinationCertificate and ProofCertificate interfaces instead of VaccinationCertificateV1 and ProofCertificateV1

* Adjust title+subtitle alignment

* Adjust text size in vaccination_list_incomplete_top_card.xml

* Use isEligibleForProofCertificate boolean to show 'refresh' or 'register new vaccination' button

* Adjust subtitle color of vaccination_list_certificate_card.xml and vaccination_list_name_card.xml

* Fix package declaration of VaccinationListAdapter

* Update VaccinationListAdapter instead of re-creating a new one each time we observe a uiState change

* Refactor Navigation

* Show sample QR-code

* Add some layout improvements

* Adjust background height for COMPLETE and INCOMPLETE state

* Show both buttons in VaccinationListFragment - Refresh button and register Vaccination button

* Use separate flow to generate qrCode

* Refactor CardItems to be inlined into their ViewHolders

* Ignore onClick listener in equals() and hashCode() of VaccinationListVaccinationCardItem.kt to avoid re-drawings

* Fix Lint

* Fix Detekt

Co-authored-by: Mohamed <mohamed.metwalli@sap.com>
Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com>
---
 .../vaccination/ui/VaccinationTestFragment.kt |   9 +
 .../res/layout/fragment_test_vaccination.xml  |  12 +-
 .../res/navigation/test_nav_graph.xml         |  17 ++
 .../util/TimeAndDateExtensions.kt             |  13 ++
 .../vaccination/ui/VaccinationUIModule.kt     |   6 +
 .../ui/list/VaccinationListFragment.kt        | 122 +++++++++++++
 .../ui/list/VaccinationListFragmentModule.kt  |  19 ++
 .../ui/list/VaccinationListViewModel.kt       | 168 ++++++++++++++++++
 .../ui/list/VaccinationMockData.kt            |  83 +++++++++
 .../ui/list/adapter/VaccinationListAdapter.kt |  60 +++++++
 .../VaccinationListCertificateCardItemVH.kt   |  43 +++++
 .../VaccinationListIncompleteTopCardItemVH.kt |  27 +++
 .../VaccinationListNameCardItemVH.kt          |  33 ++++
 .../VaccinationListVaccinationCardItemVH.kt   | 111 ++++++++++++
 .../drawable-night/ic_arrow_right_grey.xml    |  10 ++
 .../main/res/drawable/ic_arrow_right_grey.xml |  11 ++
 .../res/drawable/ic_vaccination_complete.xml  |  47 +++++
 .../ic_vaccination_complete_final.xml         |  30 ++++
 .../drawable/ic_vaccination_incomplete.xml    |  15 ++
 .../ic_vaccination_incomplete_final.xml       |  15 ++
 .../layout/fragment_vaccination_details.xml   |   2 +-
 .../res/layout/fragment_vaccination_list.xml  | 140 +++++++++++++++
 .../vaccination_list_certificate_card.xml     |  64 +++++++
 .../vaccination_list_incomplete_top_card.xml  |  30 ++++
 .../res/layout/vaccination_list_name_card.xml |  29 +++
 .../vaccination_list_vaccination_card.xml     |  55 ++++++
 .../res/navigation/vaccination_nav_graph.xml  |  13 ++
 .../res/values-de/vaccination_strings.xml     |  25 +++
 .../src/main/res/values-night/colors.xml      |   4 +
 .../src/main/res/values/colors.xml            |   4 +-
 .../src/main/res/values/styles.xml            |   7 +
 .../main/res/values/vaccination_strings.xml   |  25 +++
 32 files changed, 1246 insertions(+), 3 deletions(-)
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListFragment.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListFragmentModule.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListViewModel.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationMockData.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/VaccinationListAdapter.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListCertificateCardItemVH.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListIncompleteTopCardItemVH.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListNameCardItemVH.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListVaccinationCardItemVH.kt
 create mode 100644 Corona-Warn-App/src/main/res/drawable-night/ic_arrow_right_grey.xml
 create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_arrow_right_grey.xml
 create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_vaccination_complete.xml
 create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_vaccination_complete_final.xml
 create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_vaccination_incomplete.xml
 create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_vaccination_incomplete_final.xml
 create mode 100644 Corona-Warn-App/src/main/res/layout/fragment_vaccination_list.xml
 create mode 100644 Corona-Warn-App/src/main/res/layout/vaccination_list_certificate_card.xml
 create mode 100644 Corona-Warn-App/src/main/res/layout/vaccination_list_incomplete_top_card.xml
 create mode 100644 Corona-Warn-App/src/main/res/layout/vaccination_list_name_card.xml
 create mode 100644 Corona-Warn-App/src/main/res/layout/vaccination_list_vaccination_card.xml

diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/vaccination/ui/VaccinationTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/vaccination/ui/VaccinationTestFragment.kt
index 93fe4ac72..5bea439e2 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/vaccination/ui/VaccinationTestFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/vaccination/ui/VaccinationTestFragment.kt
@@ -24,6 +24,15 @@ class VaccinationTestFragment : Fragment(R.layout.fragment_test_vaccination), Au
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+
+        binding.openVaccinationList.setOnClickListener {
+            doNavigate(
+                VaccinationTestFragmentDirections.actionVaccinationTestFragmentToVaccinationListFragment(
+                    "vaccinated-person-identifier"
+                )
+            )
+        }
+
         binding.openVaccinationDetailsIncomplete.setOnClickListener {
             doNavigate(
                 VaccinationTestFragmentDirections
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_vaccination.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_vaccination.xml
index 2c5298753..ca0a9a4d5 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_vaccination.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_vaccination.xml
@@ -31,6 +31,16 @@
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent" />
 
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/open_vaccination_list"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:text="Vaccination List"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/textView2" />
+
             <com.google.android.material.button.MaterialButton
                 android:id="@+id/open_vaccination_details_complete"
                 android:layout_width="wrap_content"
@@ -39,7 +49,7 @@
                 android:text="Vaccination details - complete"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/textView2" />
+                app:layout_constraintTop_toBottomOf="@+id/open_vaccination_list" />
 
             <com.google.android.material.button.MaterialButton
                 android:id="@+id/open_vaccination_details_incomplete"
diff --git a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
index 6056a4d1e..bbb72a602 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml
@@ -170,9 +170,26 @@
         android:name="de.rki.coronawarnapp.vaccination.ui.VaccinationTestFragment"
         android:label="VaccinationTestFragment"
         tools:layout="@layout/fragment_test_vaccination" >
+
         <action
             android:id="@+id/action_vaccinationTestFragment_to_vaccinationDetailsFragment"
             app:destination="@id/vaccinationDetailsFragment" />
+        <action
+            android:id="@+id/action_vaccinationTestFragment_to_vaccinationListFragment"
+            app:destination="@id/vaccinationListFragment" />
+    </fragment>
+
+    <fragment
+        android:id="@+id/vaccinationListFragment"
+        android:name="de.rki.coronawarnapp.vaccination.ui.list.VaccinationListFragment"
+        android:label="fragment_vaccination_list"
+        tools:layout="@layout/fragment_vaccination_list">
+        <argument
+            android:name="vaccinatedPersonId"
+            app:argType="string" />
+        <action
+            android:id="@+id/action_vaccinationListFragment_to_vaccinationDetailsFragment"
+            app:destination="@id/vaccinationDetailsFragment" />
     </fragment>
 
     <fragment
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 60b57e29d..7392f5fe7 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
@@ -16,12 +16,15 @@ import java.util.Date
 import java.util.TimeZone
 import java.util.concurrent.TimeUnit
 
+@Suppress("TooManyFunctions")
 object TimeAndDateExtensions {
 
     private const val MS_TO_DAYS = (1000 * 60 * 60 * 24)
     private const val MS_TO_HOURS = (1000 * 60 * 60)
     private const val MS_TO_SECONDS = 1000
 
+    private val dayFormatter = DateTimeFormat.forPattern("dd.MM.yyyy")
+
     fun getCurrentHourUTC(): Int = DateTime(Instant.now(), DateTimeZone.UTC).hourOfDay().get()
 
     fun Date.toServerFormat(): String =
@@ -104,6 +107,16 @@ object TimeAndDateExtensions {
     fun Instant.toUserTimeZone() = this.toDateTime(DateTimeZone.forTimeZone(TimeZone.getDefault()))
 
     fun Instant.toLocalDateUserTz(): LocalDate = this.toUserTimeZone().toLocalDate()
+
+    /**
+     * Returns a readable date String with the format "dd.MM.yyyy" like 23.05.1989 of an Instant
+     */
+    fun Instant.toDayFormat() = toString(dayFormatter)
+
+    /**
+     * Returns a readable date String with the format "dd.MM.yyyy" like 23.05.1989 of a LocalDate
+     */
+    fun LocalDate.toDayFormat() = toString(dayFormatter)
 }
 
 typealias HourInterval = Long
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/VaccinationUIModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/VaccinationUIModule.kt
index ddbd04c76..78afda0dd 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/VaccinationUIModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/VaccinationUIModule.kt
@@ -4,9 +4,15 @@ import dagger.Module
 import dagger.android.ContributesAndroidInjector
 import de.rki.coronawarnapp.vaccination.ui.details.VaccinationDetailsFragment
 import de.rki.coronawarnapp.vaccination.ui.details.VaccinationDetailsFragmentModule
+import de.rki.coronawarnapp.vaccination.ui.list.VaccinationListFragment
+import de.rki.coronawarnapp.vaccination.ui.list.VaccinationListFragmentModule
 
 @Module
 abstract class VaccinationUIModule {
+
+    @ContributesAndroidInjector(modules = [VaccinationListFragmentModule::class])
+    abstract fun vaccinationListFragment(): VaccinationListFragment
+
     @ContributesAndroidInjector(modules = [VaccinationDetailsFragmentModule::class])
     abstract fun vaccinationDetailsFragment(): VaccinationDetailsFragment
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListFragment.kt
new file mode 100644
index 000000000..93ce6c491
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListFragment.kt
@@ -0,0 +1,122 @@
+package de.rki.coronawarnapp.vaccination.ui.list
+
+import android.os.Bundle
+import android.view.View
+import android.widget.LinearLayout
+import android.widget.Toast
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.navArgs
+import com.google.android.material.appbar.AppBarLayout
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentVaccinationListBinding
+import de.rki.coronawarnapp.ui.view.onOffsetChange
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.lists.diffutil.update
+import de.rki.coronawarnapp.util.ui.doNavigate
+import de.rki.coronawarnapp.util.ui.popBackStack
+import de.rki.coronawarnapp.util.ui.viewBindingLazy
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted
+import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson
+import de.rki.coronawarnapp.vaccination.ui.list.VaccinationListViewModel.Event.NavigateToVaccinationCertificateDetails
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.VaccinationListAdapter
+import javax.inject.Inject
+
+class VaccinationListFragment : Fragment(R.layout.fragment_vaccination_list), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+
+    private val args by navArgs<VaccinationListFragmentArgs>()
+    private val binding: FragmentVaccinationListBinding by viewBindingLazy()
+    private val viewModel: VaccinationListViewModel by cwaViewModelsAssisted(
+        factoryProducer = { viewModelFactory },
+        constructorCall = { factory, _ ->
+            factory as VaccinationListViewModel.Factory
+            factory.create(
+                vaccinatedPersonIdentifier = args.vaccinatedPersonId
+            )
+        }
+    )
+
+    private val adapter = VaccinationListAdapter()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        with(binding) {
+            toolbar.setNavigationOnClickListener {
+                popBackStack()
+            }
+
+            recyclerViewVaccinationList.adapter = adapter
+
+            viewModel.uiState.observe(viewLifecycleOwner) { uiState ->
+                bindViews(uiState)
+            }
+
+            viewModel.events.observe(viewLifecycleOwner) { event ->
+                when (event) {
+                    is NavigateToVaccinationCertificateDetails -> doNavigate(
+                        VaccinationListFragmentDirections.actionVaccinationListFragmentToVaccinationDetailsFragment(
+                            event.vaccinationCertificateId
+                        )
+                    )
+                }
+            }
+
+            registerNewVaccinationButton.setOnClickListener {
+                Toast.makeText(requireContext(), "TODO \uD83D\uDEA7", Toast.LENGTH_LONG).show()
+            }
+
+            refreshButton.setOnClickListener {
+                Toast.makeText(requireContext(), "TODO \uD83D\uDEA7", Toast.LENGTH_LONG).show()
+            }
+        }
+    }
+
+    private fun FragmentVaccinationListBinding.bindViews(uiState: VaccinationListViewModel.UiState) = with(uiState) {
+
+        adapter.update(listItems)
+
+        val isVaccinationComplete = vaccinationStatus == VaccinatedPerson.Status.COMPLETE
+
+        val background = if (isVaccinationComplete) {
+            R.drawable.vaccination_compelete_gradient
+        } else {
+            R.drawable.vaccination_incomplete
+        }
+
+        expandedImage.setImageResource(background)
+
+        subtitle.isVisible = isVaccinationComplete
+
+        appBarLayout.onOffsetChange { titleAlpha, subtitleAlpha ->
+            title.alpha = titleAlpha
+            subtitle.alpha = subtitleAlpha
+        }
+
+        setToolbarOverlay(isVaccinationComplete)
+    }
+
+    private fun setToolbarOverlay(isVaccinationComplete: Boolean) {
+
+        // subtitle is only visible when vaccination is complete
+        val bottomTextView = if (isVaccinationComplete) binding.subtitle else binding.title
+
+        val deviceWidth = requireContext().resources.displayMetrics.widthPixels
+
+        val params: CoordinatorLayout.LayoutParams = binding.recyclerViewVaccinationList.layoutParams
+            as (CoordinatorLayout.LayoutParams)
+
+        val textParams = bottomTextView.layoutParams as (LinearLayout.LayoutParams)
+
+        val divider = if (isVaccinationComplete) 2 else 3
+        textParams.bottomMargin = (deviceWidth / divider) - 24 /* 24 is space between screen border and Card */
+        bottomTextView.requestLayout()
+
+        val behavior: AppBarLayout.ScrollingViewBehavior = params.behavior as (AppBarLayout.ScrollingViewBehavior)
+        behavior.overlayTop = (deviceWidth / divider) - 24
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListFragmentModule.kt
new file mode 100644
index 000000000..86c2cb094
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListFragmentModule.kt
@@ -0,0 +1,19 @@
+package de.rki.coronawarnapp.vaccination.ui.list
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
+
+@Module
+abstract class VaccinationListFragmentModule {
+
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(VaccinationListViewModel::class)
+    abstract fun vaccinationListFragment(
+        factory: VaccinationListViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListViewModel.kt
new file mode 100644
index 000000000..dada5d174
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListViewModel.kt
@@ -0,0 +1,168 @@
+package de.rki.coronawarnapp.vaccination.ui.list
+
+import android.graphics.Bitmap
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.asLiveData
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.presencetracing.checkins.qrcode.QrCodeGenerator
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUserTz
+import de.rki.coronawarnapp.util.TimeStamper
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import de.rki.coronawarnapp.vaccination.core.ProofCertificate
+import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson
+import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson.Status.COMPLETE
+import de.rki.coronawarnapp.vaccination.core.VaccinationCertificate
+import de.rki.coronawarnapp.vaccination.core.repository.VaccinationRepository
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.VaccinationListItem
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListCertificateCardItemVH.VaccinationListCertificateCardItem
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListIncompleteTopCardItemVH.VaccinationListIncompleteTopCardItem
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListNameCardItemVH.VaccinationListNameCardItem
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListVaccinationCardItemVH.VaccinationListVaccinationCardItem
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.transform
+import org.joda.time.Days
+import org.joda.time.LocalDate
+
+class VaccinationListViewModel @AssistedInject constructor(
+    private val vaccinationRepository: VaccinationRepository,
+    private val timeStamper: TimeStamper,
+    private val qrCodeGenerator: QrCodeGenerator,
+    @Assisted private val vaccinatedPersonIdentifier: String
+) : CWAViewModel() {
+
+    val events = SingleLiveEvent<Event>()
+
+    val vaccinationInfoFlow = vaccinationRepository.vaccinationInfos.map { vaccinatedPersonSet ->
+        // TODO: use the line below once the repository returns actual values
+        // val vaccinatedPerson = vaccinatedPersonSet.single { it.identifier.code == vaccinatedPersonIdentifier }
+    }
+
+    private val proofQrCode: Flow<Bitmap?> = vaccinationRepository.vaccinationInfos.transform { vaccinationInfos ->
+
+        emit(null)
+
+        // TODO: use actual values from repository instead of these mocked ones
+        val proofCertificates = setOf(
+            getMockProofCertificate()
+        )
+
+        if (proofCertificates.isNotEmpty()) {
+            emit(qrCodeGenerator.createQrCode("TODO create qrCode from actual value"))
+        }
+    }
+
+    val uiState: LiveData<UiState> = combine(vaccinationInfoFlow, proofQrCode) { vaccinatedPerson, proofQrCode ->
+
+        // For now, use mock data
+        val vaccinationStatus = COMPLETE
+        // val vaccinationStatus = COMPLETE
+
+        val vaccinationCertificates = setOf(
+            getMockVaccinationCertificate(),
+            getMockVaccinationCertificate().copy(
+                doseNumber = 2
+            )
+        )
+
+        val proofCertificates = setOf(
+            getMockProofCertificate()
+        )
+
+        val listItems = assembleItemList(
+            vaccinationCertificates = vaccinationCertificates,
+            proofCertificates = proofCertificates,
+            firstName = "François-Joan",
+            lastName = "d'Arsøns - van Halen",
+            dateOfBirth = LocalDate.parse("2009-02-28"),
+            vaccinationStatus,
+            proofQrCode
+        )
+
+        UiState(
+            listItems,
+            vaccinationStatus = vaccinationStatus
+        )
+    }.catch {
+        // TODO Error Handling in an upcoming subtask
+    }.asLiveData()
+
+    // TODO: after using actual values from the repository, we only pass VaccinatedPerson here instead of all these
+    // arguments
+    @Suppress("LongParameterList")
+    private fun assembleItemList(
+        vaccinationCertificates: Set<VaccinationCertificate>,
+        proofCertificates: Set<ProofCertificate>,
+        firstName: String,
+        lastName: String,
+        dateOfBirth: LocalDate,
+        vaccinationStatus: VaccinatedPerson.Status,
+        proofQrCode: Bitmap?
+    ) = mutableListOf<VaccinationListItem>().apply {
+        if (vaccinationStatus == COMPLETE) {
+            if (proofCertificates.isNotEmpty()) {
+
+                val proofCertificate = proofCertificates.first()
+                val expiresAt = proofCertificate.expiresAt.toLocalDateUserTz()
+                val today = timeStamper.nowUTC.toLocalDateUserTz()
+                val remainingValidityInDays = Days.daysBetween(today, expiresAt).days
+
+                add(
+                    VaccinationListCertificateCardItem(
+                        qrCode = proofQrCode,
+                        remainingValidityInDays = remainingValidityInDays
+                    )
+                )
+            }
+        } else {
+            add(VaccinationListIncompleteTopCardItem)
+        }
+        add(
+            VaccinationListNameCardItem(
+                fullName = "$firstName $lastName",
+                dayOfBirth = dateOfBirth.toDayFormat()
+            )
+        )
+        vaccinationCertificates.forEach { vaccinationCertificate ->
+            with(vaccinationCertificate) {
+                add(
+                    VaccinationListVaccinationCardItem(
+                        vaccinationCertificateId = certificateId,
+                        doseNumber = doseNumber.toString(),
+                        totalSeriesOfDoses = totalSeriesOfDoses.toString(),
+                        vaccinatedAt = vaccinatedAt.toDayFormat(),
+                        vaccinationStatus = vaccinationStatus,
+                        isFinalVaccination =
+                            doseNumber == totalSeriesOfDoses,
+                        onCardClick = { certificateId ->
+                            events.postValue(Event.NavigateToVaccinationCertificateDetails(certificateId))
+                        }
+                    )
+                )
+            }
+        }
+    }.toList()
+
+    data class UiState(
+        val listItems: List<VaccinationListItem>,
+        val vaccinationStatus: VaccinatedPerson.Status
+    )
+
+    sealed class Event {
+        data class NavigateToVaccinationCertificateDetails(val vaccinationCertificateId: String) : Event()
+    }
+
+    @AssistedFactory
+    interface Factory : CWAViewModelFactory<VaccinationListViewModel> {
+        fun create(
+            vaccinatedPersonIdentifier: String
+        ): VaccinationListViewModel
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationMockData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationMockData.kt
new file mode 100644
index 000000000..b8d84a56c
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationMockData.kt
@@ -0,0 +1,83 @@
+package de.rki.coronawarnapp.vaccination.ui.list
+
+import de.rki.coronawarnapp.ui.Country
+import de.rki.coronawarnapp.vaccination.core.ProofCertificate
+import de.rki.coronawarnapp.vaccination.core.VaccinatedPersonIdentifier
+import de.rki.coronawarnapp.vaccination.core.VaccinationCertificate
+import org.joda.time.Duration
+import org.joda.time.Instant
+import org.joda.time.LocalDate
+
+/**
+ * Mock Data needed for UI development while backend connection is not yet available
+ * Can be removed later
+ */
+internal fun getMockVaccinationCertificate() = MockVaccinationCertificate(
+    firstName = "François-Joan",
+    lastName = "FRANCOIS<JOAN",
+    dateOfBirth = LocalDate.parse("2009-02-28"),
+    vaccinatedAt = LocalDate.parse("2021-04-22"),
+    vaccineName = "vaccineName",
+    vaccineManufacturer = "vaccineManufactorer",
+    medicalProductName = "medicalProductName",
+    doseNumber = 1,
+    totalSeriesOfDoses = 2,
+    certificateIssuer = "certificateIssuer",
+    certificateCountry = Country.AT,
+    certificateId = "certificate Id",
+    personIdentifier = getPersonIdentifier()
+)
+
+internal fun getMockProofCertificate() = MockProofCertificate(
+    personIdentifier = getPersonIdentifier(),
+    expiresAt = Instant.now().plus(Duration.standardDays(3)),
+    firstName = "François-Joan",
+    lastName = "FRANCOIS<JOAN",
+    dateOfBirth = LocalDate.parse("2009-02-28"),
+    vaccinatedAt = LocalDate.parse("2021-04-22"),
+    vaccineName = "vaccineName",
+    vaccineManufacturer = "vaccineManufactorer",
+    medicalProductName = "medicalProductName",
+    doseNumber = 1,
+    totalSeriesOfDoses = 2,
+    certificateIssuer = "certificateIssuer",
+    certificateId = "certificate Id"
+)
+
+fun getPersonIdentifier() = VaccinatedPersonIdentifier(
+    dateOfBirth = LocalDate.parse("2009-02-28"),
+    lastNameStandardized = "DARSONS<VAN<HALEN",
+    firstNameStandardized = "FRANCOIS<JOAN"
+)
+
+internal data class MockProofCertificate(
+    override val personIdentifier: VaccinatedPersonIdentifier,
+    override val expiresAt: Instant,
+    override val firstName: String?,
+    override val lastName: String,
+    override val dateOfBirth: LocalDate,
+    override val vaccineName: String,
+    override val medicalProductName: String,
+    override val vaccineManufacturer: String,
+    override val doseNumber: Int,
+    override val totalSeriesOfDoses: Int,
+    override val vaccinatedAt: LocalDate,
+    override val certificateIssuer: String,
+    override val certificateId: String
+) : ProofCertificate
+
+internal data class MockVaccinationCertificate(
+    override val firstName: String?,
+    override val lastName: String,
+    override val dateOfBirth: LocalDate,
+    override val vaccinatedAt: LocalDate,
+    override val vaccineName: String,
+    override val vaccineManufacturer: String,
+    override val medicalProductName: String,
+    override val doseNumber: Int,
+    override val totalSeriesOfDoses: Int,
+    override val certificateIssuer: String,
+    override val certificateCountry: Country,
+    override val certificateId: String,
+    override val personIdentifier: VaccinatedPersonIdentifier
+) : VaccinationCertificate
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/VaccinationListAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/VaccinationListAdapter.kt
new file mode 100644
index 000000000..e152edbe9
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/VaccinationListAdapter.kt
@@ -0,0 +1,60 @@
+package de.rki.coronawarnapp.vaccination.ui.list.adapter
+
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.viewbinding.ViewBinding
+import de.rki.coronawarnapp.util.lists.BindableVH
+import de.rki.coronawarnapp.util.lists.HasStableId
+import de.rki.coronawarnapp.util.lists.diffutil.AsyncDiffUtilAdapter
+import de.rki.coronawarnapp.util.lists.diffutil.AsyncDiffer
+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.list.adapter.viewholder.VaccinationListCertificateCardItemVH
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListCertificateCardItemVH.VaccinationListCertificateCardItem
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListIncompleteTopCardItemVH
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListIncompleteTopCardItemVH.VaccinationListIncompleteTopCardItem
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListNameCardItemVH
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListNameCardItemVH.VaccinationListNameCardItem
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListVaccinationCardItemVH
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListVaccinationCardItemVH.VaccinationListVaccinationCardItem
+
+class VaccinationListAdapter :
+    ModularAdapter<VaccinationListAdapter.ItemVH<VaccinationListItem, ViewBinding>>(),
+    AsyncDiffUtilAdapter<VaccinationListItem> {
+
+    override val asyncDiffer: AsyncDiffer<VaccinationListItem> = AsyncDiffer(adapter = this)
+
+    init {
+        modules.addAll(
+            listOf(
+                StableIdMod(data),
+                DataBinderMod<VaccinationListItem, ItemVH<VaccinationListItem, ViewBinding>>(data),
+                TypedVHCreatorMod({ data[it] is VaccinationListIncompleteTopCardItem }) {
+                    VaccinationListIncompleteTopCardItemVH(it)
+                },
+                TypedVHCreatorMod({ data[it] is VaccinationListNameCardItem }) {
+                    VaccinationListNameCardItemVH(it)
+                },
+                TypedVHCreatorMod({ data[it] is VaccinationListVaccinationCardItem }) {
+                    VaccinationListVaccinationCardItemVH(it)
+                },
+                TypedVHCreatorMod({ data[it] is VaccinationListCertificateCardItem }) {
+                    VaccinationListCertificateCardItemVH(it)
+                }
+            )
+        )
+    }
+
+    override fun getItemCount(): Int {
+        return data.size
+    }
+
+    abstract class ItemVH<Item : VaccinationListItem, VB : ViewBinding>(
+        @LayoutRes layoutRes: Int,
+        parent: ViewGroup
+    ) : ModularAdapter.VH(layoutRes, parent), BindableVH<Item, VB>
+}
+
+interface VaccinationListItem : HasStableId
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListCertificateCardItemVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListCertificateCardItemVH.kt
new file mode 100644
index 000000000..2a67a41e4
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListCertificateCardItemVH.kt
@@ -0,0 +1,43 @@
+package de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder
+
+import android.graphics.Bitmap
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.VaccinationListCertificateCardBinding
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.VaccinationListAdapter
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.VaccinationListItem
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListCertificateCardItemVH.VaccinationListCertificateCardItem
+
+class VaccinationListCertificateCardItemVH(parent: ViewGroup) :
+    VaccinationListAdapter.ItemVH<VaccinationListCertificateCardItem, VaccinationListCertificateCardBinding>(
+        layoutRes = R.layout.vaccination_list_certificate_card,
+        parent = parent
+    ) {
+
+    override val viewBinding: Lazy<VaccinationListCertificateCardBinding> = lazy {
+        VaccinationListCertificateCardBinding.bind(itemView)
+    }
+
+    override val onBindData: VaccinationListCertificateCardBinding
+    .(item: VaccinationListCertificateCardItem, payloads: List<Any>) -> Unit =
+        { item, _ ->
+            when (item.qrCode) {
+                null -> progressBar.isVisible = true
+                else -> {
+                    qrCodeImage.setImageBitmap(item.qrCode)
+                    progressBar.isVisible = false
+                }
+            }
+            certificateCardSubtitle.text =
+                context.getString(R.string.vaccination_list_certificate_card_subtitle, item.remainingValidityInDays)
+        }
+
+    data class VaccinationListCertificateCardItem(
+        val qrCode: Bitmap?,
+        val remainingValidityInDays: Int
+    ) :
+        VaccinationListItem {
+        override val stableId = this.hashCode().toLong()
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListIncompleteTopCardItemVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListIncompleteTopCardItemVH.kt
new file mode 100644
index 000000000..dd9ec7bba
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListIncompleteTopCardItemVH.kt
@@ -0,0 +1,27 @@
+package de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.VaccinationListIncompleteTopCardBinding
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.VaccinationListAdapter
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.VaccinationListItem
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListIncompleteTopCardItemVH.VaccinationListIncompleteTopCardItem
+
+class VaccinationListIncompleteTopCardItemVH(parent: ViewGroup) :
+    VaccinationListAdapter.ItemVH<VaccinationListIncompleteTopCardItem, VaccinationListIncompleteTopCardBinding>(
+        layoutRes = R.layout.vaccination_list_incomplete_top_card,
+        parent = parent
+    ) {
+
+    override val viewBinding: Lazy<VaccinationListIncompleteTopCardBinding> = lazy {
+        VaccinationListIncompleteTopCardBinding.bind(itemView)
+    }
+
+    override val onBindData: VaccinationListIncompleteTopCardBinding
+    .(item: VaccinationListIncompleteTopCardItem, payloads: List<Any>) -> Unit = { _, _ -> // NOOP
+    }
+
+    object VaccinationListIncompleteTopCardItem : VaccinationListItem {
+        override val stableId = this.hashCode().toLong()
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListNameCardItemVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListNameCardItemVH.kt
new file mode 100644
index 000000000..5cd1501ca
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListNameCardItemVH.kt
@@ -0,0 +1,33 @@
+package de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.VaccinationListNameCardBinding
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.VaccinationListAdapter
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.VaccinationListItem
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListNameCardItemVH.VaccinationListNameCardItem
+
+class VaccinationListNameCardItemVH(parent: ViewGroup) :
+    VaccinationListAdapter.ItemVH<VaccinationListNameCardItem, VaccinationListNameCardBinding>(
+        layoutRes = R.layout.vaccination_list_name_card,
+        parent = parent
+    ) {
+
+    override val viewBinding: Lazy<VaccinationListNameCardBinding> = lazy {
+        VaccinationListNameCardBinding.bind(itemView)
+    }
+
+    override val onBindData: VaccinationListNameCardBinding
+    .(item: VaccinationListNameCardItem, payloads: List<Any>) -> Unit =
+        { item, _ ->
+            nameCardTitle.text = item.fullName
+            nameCardSubtitle.text = context.getString(
+                R.string.vaccination_list_name_card_subtitle,
+                item.dayOfBirth
+            )
+        }
+
+    data class VaccinationListNameCardItem(val fullName: String, val dayOfBirth: String) : VaccinationListItem {
+        override val stableId = this.hashCode().toLong()
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListVaccinationCardItemVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListVaccinationCardItemVH.kt
new file mode 100644
index 000000000..340a86e7a
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListVaccinationCardItemVH.kt
@@ -0,0 +1,111 @@
+package de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder
+
+import android.view.ViewGroup
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.VaccinationListVaccinationCardBinding
+import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson
+import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson.Status.COMPLETE
+import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson.Status.INCOMPLETE
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.VaccinationListAdapter
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.VaccinationListItem
+import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListVaccinationCardItemVH.VaccinationListVaccinationCardItem
+import java.util.Objects
+
+class VaccinationListVaccinationCardItemVH(
+    parent: ViewGroup,
+) :
+    VaccinationListAdapter.ItemVH<VaccinationListVaccinationCardItem, VaccinationListVaccinationCardBinding>(
+        layoutRes = R.layout.vaccination_list_vaccination_card,
+        parent = parent
+    ) {
+
+    override val viewBinding: Lazy<VaccinationListVaccinationCardBinding> = lazy {
+        VaccinationListVaccinationCardBinding.bind(itemView)
+    }
+    override val onBindData:
+        VaccinationListVaccinationCardBinding.(item: VaccinationListVaccinationCardItem, payloads: List<Any>) -> Unit =
+            { item, _ ->
+                with(item) {
+                    root.setOnClickListener {
+                        onCardClick.invoke(vaccinationCertificateId)
+                    }
+                    vaccinationCardTitle.text = context.getString(
+                        R.string.vaccination_list_vaccination_card_title,
+                        doseNumber,
+                        totalSeriesOfDoses
+                    )
+                    vaccinationCardSubtitle.text = context.getString(
+                        R.string.vaccination_list_vaccination_card_subtitle,
+                        vaccinatedAt
+                    )
+
+                    val iconRes = when (item.vaccinationStatus) {
+                        INCOMPLETE -> {
+                            if (isFinalVaccination) {
+                                R.drawable.ic_vaccination_incomplete_final
+                            } else {
+                                R.drawable.ic_vaccination_incomplete
+                            }
+                        }
+                        COMPLETE -> {
+                            if (isFinalVaccination) {
+                                R.drawable.ic_vaccination_complete_final
+                            } else {
+                                R.drawable.ic_vaccination_complete
+                            }
+                        }
+                    }
+                    vaccinationIcon.setImageResource(iconRes)
+                }
+            }
+
+    data class VaccinationListVaccinationCardItem(
+        val vaccinationCertificateId: String,
+        val doseNumber: String,
+        val totalSeriesOfDoses: String,
+        val vaccinatedAt: String,
+        val vaccinationStatus: VaccinatedPerson.Status,
+        val isFinalVaccination: Boolean,
+        val onCardClick: (String) -> Unit
+    ) : VaccinationListItem {
+
+        override val stableId: Long = Objects.hash(
+            vaccinationCertificateId,
+            doseNumber,
+            totalSeriesOfDoses,
+            vaccinatedAt,
+            vaccinationStatus,
+            isFinalVaccination
+        ).toLong()
+
+        // Ignore onCardClick Listener in equals() to avoid re-drawing when only the click listener is updated
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (javaClass != other?.javaClass) return false
+
+            other as VaccinationListVaccinationCardItem
+
+            if (vaccinationCertificateId != other.vaccinationCertificateId) return false
+            if (doseNumber != other.doseNumber) return false
+            if (totalSeriesOfDoses != other.totalSeriesOfDoses) return false
+            if (vaccinatedAt != other.vaccinatedAt) return false
+            if (vaccinationStatus != other.vaccinationStatus) return false
+            if (isFinalVaccination != other.isFinalVaccination) return false
+            if (stableId != other.stableId) return false
+
+            return true
+        }
+
+        // Ignore onCardClick Listener in equals() to avoid re-drawing when only the click listener is updated
+        override fun hashCode(): Int {
+            var result = vaccinationCertificateId.hashCode()
+            result = 31 * result + doseNumber.hashCode()
+            result = 31 * result + totalSeriesOfDoses.hashCode()
+            result = 31 * result + vaccinatedAt.hashCode()
+            result = 31 * result + vaccinationStatus.hashCode()
+            result = 31 * result + isFinalVaccination.hashCode()
+            result = 31 * result + stableId.hashCode()
+            return result
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/main/res/drawable-night/ic_arrow_right_grey.xml b/Corona-Warn-App/src/main/res/drawable-night/ic_arrow_right_grey.xml
new file mode 100644
index 000000000..40001f61b
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable-night/ic_arrow_right_grey.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="19dp"
+    android:height="16dp"
+    android:viewportWidth="19"
+    android:viewportHeight="16">
+  <path
+      android:pathData="M11.9186,0.7474L10.4286,2.3179L14.9685,7.103H0.9918V9.197H14.9685L10.4286,13.9821L11.9186,15.5525L18.9419,8.15L11.9186,0.7474Z"
+      android:fillColor="#A7A7A7"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_arrow_right_grey.xml b/Corona-Warn-App/src/main/res/drawable/ic_arrow_right_grey.xml
new file mode 100644
index 000000000..fea245299
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_arrow_right_grey.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="19dp"
+    android:height="16dp"
+    android:viewportWidth="19"
+    android:viewportHeight="16">
+  <path
+      android:pathData="M11.9186,0.7475L10.4286,2.318L14.9685,7.1031H0.9918V9.197H14.9685L10.4286,13.9821L11.9186,15.5526L18.9419,8.15L11.9186,0.7475Z"
+      android:fillColor="#17191A"
+      android:fillAlpha="0.6"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_vaccination_complete.xml b/Corona-Warn-App/src/main/res/drawable/ic_vaccination_complete.xml
new file mode 100644
index 000000000..38ca300a8
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_vaccination_complete.xml
@@ -0,0 +1,47 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="88dp"
+    android:height="95dp"
+    android:viewportWidth="88"
+    android:viewportHeight="95">
+  <path
+      android:pathData="M0,4C0,1.7909 1.7909,0 4,0H84C86.2091,0 88,1.7909 88,4V91C88,93.2091 86.2091,95 84,95H4C1.7909,95 0,93.2091 0,91V4Z"
+      android:fillColor="#FDD207"/>
+  <path
+      android:pathData="M0,4C0,1.7909 1.7909,0 4,0H84C86.2091,0 88,1.7909 88,4V91C88,93.2091 86.2091,95 84,95H4C1.7909,95 0,93.2091 0,91V4Z">
+    <aapt:attr name="android:fillColor">
+      <gradient 
+          android:startY="35.3456"
+          android:startX="-1.95773E-6"
+          android:endY="57.3292"
+          android:endX="82.1951"
+          android:type="linear">
+        <item android:offset="0" android:color="#FF0093C8"/>
+        <item android:offset="1" android:color="#FF007FAD"/>
+      </gradient>
+    </aapt:attr>
+  </path>
+  <path
+      android:pathData="M0,4C0,1.7909 1.7909,0 4,0H84C86.2091,0 88,1.7909 88,4V91C88,93.2091 86.2091,95 84,95H4C1.7909,95 0,93.2091 0,91V4Z"
+      android:fillColor="#FDD207"/>
+  <path
+      android:pathData="M0,4C0,1.7909 1.7909,0 4,0H84C86.2091,0 88,1.7909 88,4V91C88,93.2091 86.2091,95 84,95H4C1.7909,95 0,93.2091 0,91V4Z">
+    <aapt:attr name="android:fillColor">
+      <gradient 
+          android:startY="35.3456"
+          android:startX="-1.95773E-6"
+          android:endY="57.3292"
+          android:endX="82.1951"
+          android:type="linear">
+        <item android:offset="0" android:color="#FF0093C8"/>
+        <item android:offset="1" android:color="#FF007FAD"/>
+      </gradient>
+    </aapt:attr>
+  </path>
+  <path
+      android:pathData="M44.6667,21L23,29.125V45.6188C23,59.2958 32.2354,72.0521 44.6667,75.1667C57.0979,72.0521 66.3333,59.2958 66.3333,45.6188V29.125L44.6667,21ZM60.9167,45.6188C60.9167,56.4521 54.0104,66.4729 44.6667,69.5333C35.3229,66.4729 28.4167,56.4792 28.4167,45.6188V32.8896L44.6667,26.7958L60.9167,32.8896V45.6188Z"
+      android:fillColor="#ffffff"/>
+  <path
+      android:pathData="M44.6665,71.0834V26.5834V24.0834L26.1665,32.0834V49.0834L31.1665,63.0834L44.6665,71.0834Z"
+      android:fillColor="#ffffff"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_vaccination_complete_final.xml b/Corona-Warn-App/src/main/res/drawable/ic_vaccination_complete_final.xml
new file mode 100644
index 000000000..81e5a5d1c
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_vaccination_complete_final.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="88dp"
+    android:height="95dp"
+    android:viewportWidth="88"
+    android:viewportHeight="95">
+  <path
+      android:pathData="M0,4C0,1.7909 1.7909,0 4,0H84C86.2091,0 88,1.7909 88,4V91C88,93.2091 86.2091,95 84,95H4C1.7909,95 0,93.2091 0,91V4Z"
+      android:fillColor="#FDD207"/>
+  <path
+      android:pathData="M0,4C0,1.7909 1.7909,0 4,0H84C86.2091,0 88,1.7909 88,4V91C88,93.2091 86.2091,95 84,95H4C1.7909,95 0,93.2091 0,91V4Z">
+    <aapt:attr name="android:fillColor">
+      <gradient 
+          android:startY="35.3456"
+          android:startX="-1.95773E-6"
+          android:endY="57.3292"
+          android:endX="82.1951"
+          android:type="linear">
+        <item android:offset="0" android:color="#FF0093C8"/>
+        <item android:offset="1" android:color="#FF007FAD"/>
+      </gradient>
+    </aapt:attr>
+  </path>
+  <path
+      android:pathData="M44.5,21L23,30.8182V45.5455C23,59.1682 32.1733,71.9073 44.5,75C56.8267,71.9073 66,59.1682 66,45.5455V30.8182L44.5,21Z"
+      android:fillColor="#ffffff"/>
+  <path
+      android:pathData="M32.525,46.7909L29,50.2335L39,60L59,40.4671L55.475,37L39,53.0902L32.525,46.7909Z"
+      android:fillColor="#0186B6"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_vaccination_incomplete.xml b/Corona-Warn-App/src/main/res/drawable/ic_vaccination_incomplete.xml
new file mode 100644
index 000000000..8a6cdf9e1
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_vaccination_incomplete.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="88dp"
+    android:height="95dp"
+    android:viewportWidth="88"
+    android:viewportHeight="95">
+  <path
+      android:pathData="M0,4C0,1.7909 1.7909,0 4,0H84C86.2091,0 88,1.7909 88,4V91C88,93.2091 86.2091,95 84,95H4C1.7909,95 0,93.2091 0,91V4Z"
+      android:fillColor="#616F7E"/>
+  <path
+      android:pathData="M44.6667,21L23,29.125V45.6188C23,59.2958 32.2354,72.0521 44.6667,75.1667C57.0979,72.0521 66.3333,59.2958 66.3333,45.6188V29.125L44.6667,21ZM60.9167,45.6188C60.9167,56.4521 54.0104,66.4729 44.6667,69.5333C35.3229,66.4729 28.4167,56.4792 28.4167,45.6188V32.8896L44.6667,26.7958L60.9167,32.8896V45.6188Z"
+      android:fillColor="#ffffff"/>
+  <path
+      android:pathData="M44.6665,71.0834V26.5834V24.0834L26.1665,32.0834V49.0834L31.1665,63.0834L44.6665,71.0834Z"
+      android:fillColor="#ffffff"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/drawable/ic_vaccination_incomplete_final.xml b/Corona-Warn-App/src/main/res/drawable/ic_vaccination_incomplete_final.xml
new file mode 100644
index 000000000..ea2e7488d
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/drawable/ic_vaccination_incomplete_final.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="88dp"
+    android:height="95dp"
+    android:viewportWidth="88"
+    android:viewportHeight="95">
+  <path
+      android:pathData="M0,4C0,1.7909 1.7909,0 4,0H84C86.2091,0 88,1.7909 88,4V91C88,93.2091 86.2091,95 84,95H4C1.7909,95 0,93.2091 0,91V4Z"
+      android:fillColor="#616F7E"/>
+  <path
+      android:pathData="M44.5,21L23,30.8182V45.5455C23,59.1682 32.1733,71.9073 44.5,75C56.8267,71.9073 66,59.1682 66,45.5455V30.8182L44.5,21Z"
+      android:fillColor="#ffffff"/>
+  <path
+      android:pathData="M32.525,46.7909L29,50.2335L39,60L59,40.4671L55.475,37L39,53.0902L32.525,46.7909Z"
+      android:fillColor="#616F7E"/>
+</vector>
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_vaccination_details.xml b/Corona-Warn-App/src/main/res/layout/fragment_vaccination_details.xml
index 63afd133b..544a647db 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_vaccination_details.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_vaccination_details.xml
@@ -108,7 +108,7 @@
             app:layout_behavior="@string/appbar_scrolling_view_behavior">
 
             <LinearLayout
-                style="@style/Card.NoElevation"
+                style="@style/Card.Vaccination"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginHorizontal="24dp"
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_vaccination_list.xml b/Corona-Warn-App/src/main/res/layout/fragment_vaccination_list.xml
new file mode 100644
index 000000000..ec70295fc
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/fragment_vaccination_list.xml
@@ -0,0 +1,140 @@
+<?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:id="@+id/content_container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/trace_location_gradient_background">
+
+    <androidx.coordinatorlayout.widget.CoordinatorLayout
+        android:id="@+id/coordinator_layout"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginBottom="12dp"
+        android:nestedScrollingEnabled="true"
+        app:layout_constraintBottom_toTopOf="@id/refresh_button"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <com.google.android.material.appbar.AppBarLayout
+            android:id="@+id/appBarLayout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <com.google.android.material.appbar.CollapsingToolbarLayout
+                android:id="@+id/collapsing_toolbar_layout"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:nestedScrollingEnabled="true"
+                app:layout_scrollFlags="scroll|exitUntilCollapsed">
+
+                <ImageView
+                    android:id="@+id/expandedImage"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:importantForAccessibility="no"
+                    app:layout_collapseMode="parallax"
+                    app:srcCompat="@drawable/vaccination_incomplete" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    app:layout_collapseMode="parallax">
+
+                    <TextView
+                        android:id="@+id/title"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginHorizontal="24dp"
+                        android:layout_marginTop="80dp"
+                        android:layout_marginBottom="12dp"
+                        android:gravity="center"
+                        android:text="@string/vaccination_list_title"
+                        android:textColor="@android:color/white"
+                        android:textSize="20sp"
+                        android:textStyle="bold"/>
+
+                    <TextView
+                        android:id="@+id/subtitle"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="center"
+                        android:layout_marginHorizontal="24dp"
+                        android:gravity="center"
+                        android:text="@string/vaccination_list_complete_vaccination_subtitle"
+                        android:textColor="@android:color/white"
+                        android:textSize="18sp"
+                        android:visibility="visible"/>
+
+                </LinearLayout>
+
+                <com.google.android.material.appbar.MaterialToolbar
+                    android:id="@+id/toolbar"
+                    android:layout_width="match_parent"
+                    android:layout_height="?attr/actionBarSize"
+                    app:layout_collapseMode="pin"
+                    app:layout_scrollFlags="scroll|enterAlways"
+                    app:navigationIcon="@drawable/ic_close_white"
+                    app:titleTextColor="@color/colorAccentTintButton">
+
+                    <LinearLayout
+                        android:id="@+id/header_text_layout"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:gravity="center_vertical"
+                        android:orientation="horizontal">
+                        <ImageView
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_marginEnd="72dp"
+                            android:importantForAccessibility="no"
+                            app:srcCompat="@drawable/ic_cwa_logo_white" />
+                    </LinearLayout>
+
+                </com.google.android.material.appbar.MaterialToolbar>
+
+            </com.google.android.material.appbar.CollapsingToolbarLayout>
+
+        </com.google.android.material.appbar.AppBarLayout>
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/recycler_view_vaccination_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+            app:layout_behavior="@string/appbar_scrolling_view_behavior"
+            tools:listitem="@layout/vaccination_list_incomplete_top_card" />
+
+    </androidx.coordinatorlayout.widget.CoordinatorLayout>
+
+    <Button
+        android:id="@+id/refresh_button"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/spacing_normal"
+        android:layout_marginEnd="@dimen/spacing_normal"
+        android:layout_marginBottom="8dp"
+        android:text="@string/vaccination_list_refresh_button"
+        app:layout_constraintBottom_toTopOf="@id/register_new_vaccination_button"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"/>
+
+    <Button
+        android:id="@+id/register_new_vaccination_button"
+        style="@style/buttonPrimary"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/spacing_normal"
+        android:layout_marginEnd="@dimen/spacing_normal"
+        android:layout_marginBottom="8dp"
+        android:text="@string/vaccination_list_register_new_vaccination_button"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/Corona-Warn-App/src/main/res/layout/vaccination_list_certificate_card.xml b/Corona-Warn-App/src/main/res/layout/vaccination_list_certificate_card.xml
new file mode 100644
index 000000000..25aea54b9
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/vaccination_list_certificate_card.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    style="@style/Card.Vaccination"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginHorizontal="24dp"
+    android:layout_marginTop="16dp"
+    android:padding="0dp"
+    android:orientation="vertical">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="4dp">
+
+        <ImageView
+            android:id="@+id/qrCodeImage"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:contentDescription="@string/vaccination_list_qr_code_accessibility"
+            app:layout_constraintDimensionRatio="H,1:1"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:src="@drawable/ic_qrcode"
+            tools:tint="@android:color/black" />
+
+        <com.google.android.material.progressindicator.LinearProgressIndicator
+            android:id="@+id/progress_bar"
+            android:layout_width="150dp"
+            android:layout_height="wrap_content"
+            android:indeterminate="true"
+            app:hideAnimationBehavior="inward"
+            app:indicatorColor="@color/colorAccent"
+            app:layout_constraintBottom_toBottomOf="@id/qrCodeImage"
+            app:layout_constraintEnd_toEndOf="@id/qrCodeImage"
+            app:layout_constraintStart_toStartOf="@id/qrCodeImage"
+            app:layout_constraintTop_toTopOf="@id/qrCodeImage" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <TextView
+        android:id="@+id/certificate_card_title"
+        style="@style/body2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="16dp"
+        android:textSize="18sp"
+        android:textStyle="bold"
+        android:text="@string/vaccination_list_certificate_card_title" />
+
+    <TextView
+        android:id="@+id/certificate_card_subtitle"
+        style="@style/body2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:layout_marginBottom="16dp"
+        android:layout_marginHorizontal="16dp"
+        tools:text="Noch 3 Tage gültig" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/vaccination_list_incomplete_top_card.xml b/Corona-Warn-App/src/main/res/layout/vaccination_list_incomplete_top_card.xml
new file mode 100644
index 000000000..b5238b92d
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/vaccination_list_incomplete_top_card.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/Card.Vaccination"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginHorizontal="24dp"
+    android:layout_marginTop="16dp"
+    android:orientation="vertical"
+    android:paddingHorizontal="16dp"
+    android:paddingTop="24dp"
+    android:paddingBottom="12dp">
+
+    <TextView
+        android:id="@+id/top_card_title"
+        style="@style/headline4Bold"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/vaccination_list_top_card_title"
+        android:textSize="30sp"/>
+
+    <TextView
+        android:id="@+id/top_card_subtitle"
+        style="@style/body2Medium"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:text="@string/vaccination_list_top_card_subtitle"
+        android:textSize="18sp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/vaccination_list_name_card.xml b/Corona-Warn-App/src/main/res/layout/vaccination_list_name_card.xml
new file mode 100644
index 000000000..a749f7d47
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/vaccination_list_name_card.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    style="@style/Card.Vaccination"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginHorizontal="24dp"
+    android:layout_marginTop="8dp"
+    android:orientation="vertical"
+    android:padding="16dp">
+
+    <TextView
+        android:id="@+id/name_card_title"
+        style="@style/body2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:textStyle="bold"
+        tools:text="Andrea Schneider" />
+
+    <TextView
+        android:id="@+id/name_card_subtitle"
+        style="@style/body2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        tools:text="geboren am 18.04.1943" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/layout/vaccination_list_vaccination_card.xml b/Corona-Warn-App/src/main/res/layout/vaccination_list_vaccination_card.xml
new file mode 100644
index 000000000..2078b26bd
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/vaccination_list_vaccination_card.xml
@@ -0,0 +1,55 @@
+<?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"
+    android:id="@+id/vaccination_card"
+    style="@style/Card.Vaccination.Ripple"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginHorizontal="24dp"
+    android:layout_marginTop="8dp"
+    android:padding="16dp">
+
+    <ImageView
+        android:id="@+id/vaccination_icon"
+        android:layout_width="88dp"
+        android:layout_height="95dp"
+        android:importantForAccessibility="no"
+        app:srcCompat="@drawable/ic_vaccination_incomplete"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/vaccination_card_title"
+        style="@style/body2"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:text="@string/vaccination_list_vaccination_card_title"
+        android:textSize="18sp"
+        android:textStyle="bold"
+        app:layout_constraintEnd_toStartOf="@id/arrow_right"
+        app:layout_constraintStart_toEndOf="@id/vaccination_icon"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/vaccination_card_subtitle"
+        style="@style/body2Medium"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="4dp"
+        android:text="@string/vaccination_list_vaccination_card_subtitle"
+        app:layout_constraintEnd_toStartOf="@id/arrow_right"
+        app:layout_constraintStart_toEndOf="@id/vaccination_icon"
+        app:layout_constraintTop_toBottomOf="@id/vaccination_card_title" />
+
+    <ImageView
+        android:id="@+id/arrow_right"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        app:srcCompat="@drawable/ic_arrow_right_grey"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/navigation/vaccination_nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/vaccination_nav_graph.xml
index f6f5b0450..d446e1d2d 100644
--- a/Corona-Warn-App/src/main/res/navigation/vaccination_nav_graph.xml
+++ b/Corona-Warn-App/src/main/res/navigation/vaccination_nav_graph.xml
@@ -5,6 +5,19 @@
     android:id="@+id/vaccination_nav_graph"
     app:startDestination="@id/vaccinationDetailsFragment">
 
+    <fragment
+        android:id="@+id/vaccinationListFragment"
+        android:name="de.rki.coronawarnapp.vaccination.ui.list.VaccinationListFragment"
+        android:label="fragment_vaccination_list"
+        tools:layout="@layout/fragment_vaccination_list">
+        <argument
+            android:name="vaccinatedPersonId"
+            app:argType="string" />
+        <action
+            android:id="@+id/action_vaccinationListFragment_to_vaccinationDetailsFragment"
+            app:destination="@id/vaccinationDetailsFragment" />
+    </fragment>
+
     <fragment
         android:id="@+id/vaccinationDetailsFragment"
         android:name="de.rki.coronawarnapp.vaccination.ui.details.VaccinationDetailsFragment"
diff --git a/Corona-Warn-App/src/main/res/values-de/vaccination_strings.xml b/Corona-Warn-App/src/main/res/values-de/vaccination_strings.xml
index a84d67249..509205f64 100644
--- a/Corona-Warn-App/src/main/res/values-de/vaccination_strings.xml
+++ b/Corona-Warn-App/src/main/res/values-de/vaccination_strings.xml
@@ -28,4 +28,29 @@
     <string name="vaccination_details_deletion_dialog_positive_button">Entfernen</string>
     <!-- XBUT: Vaccination Details deletion dialog negative button-->
     <string name="vaccination_details_deletion_dialog_negative_button">Abbrechen</string>
+
+    <!-- XTXT: Vaccination List title-->
+    <string name="vaccination_list_title">Digitaler Impfnachweis</string>
+    <!-- XTXT: Vaccination List complete vaccination subtitle-->
+    <string name="vaccination_list_complete_vaccination_subtitle">SARS-CoV-2-Impfschutz</string>
+    <!-- XTXT: Vaccination List top card title-->
+    <string name="vaccination_list_top_card_title">SARS-CoV-2 Impfung</string>
+    <!-- XTXT: Vaccination List top card subtitle-->
+    <string name="vaccination_list_top_card_subtitle">Unvollständiger Impfschutz</string>
+    <!-- XTXT: Vaccination List name card subtitle-->
+    <string name="vaccination_list_name_card_subtitle">geboren %1$s</string>
+    <!-- XTXT: Vaccination List top card subtitle-->
+    <string name="vaccination_list_vaccination_card_title">Impfung %1$s von %2$s</string>
+    <!-- XTXT: Vaccination List top card subtitle-->
+    <string name="vaccination_list_vaccination_card_subtitle">durchgeführt am %1$s</string>
+    <!-- XTXT: Vaccination List top card subtitle-->
+    <string name="vaccination_list_certificate_card_title">COVID-19-Prüfzertifikat</string>
+    <!-- XTXT: Vaccination List top card subtitle-->
+    <string name="vaccination_list_certificate_card_subtitle">Noch %1$d Tage gültig</string>
+    <!-- XBUT: Vaccination List register additional vaccination button -->
+    <string name="vaccination_list_register_new_vaccination_button">Weitere Impfung registrieren</string>
+    <!-- XBUT: Vaccination List refresh button -->
+    <string name="vaccination_list_refresh_button">Aktualisieren</string>
+    <!-- XACT: Vaccination List Qr-Code for screen readers -->
+    <string name="vaccination_list_qr_code_accessibility">Qr-Code</string>
 </resources>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/values-night/colors.xml b/Corona-Warn-App/src/main/res/values-night/colors.xml
index 98ab8628d..9f357cf9d 100644
--- a/Corona-Warn-App/src/main/res/values-night/colors.xml
+++ b/Corona-Warn-App/src/main/res/values-night/colors.xml
@@ -77,4 +77,8 @@
     <!-- QR Code Scan -->
     <color name="colorQrCodeScanToolbar">#FFFFFF</color>
     <color name="colorQrCodeScanMask">#BF000000</color>
+
+    <!-- Vaccination -->
+    <color name="colorVaccinationCardBackground">#434445</color>
+
 </resources>
diff --git a/Corona-Warn-App/src/main/res/values/colors.xml b/Corona-Warn-App/src/main/res/values/colors.xml
index 0c537ee78..0e27295b4 100644
--- a/Corona-Warn-App/src/main/res/values/colors.xml
+++ b/Corona-Warn-App/src/main/res/values/colors.xml
@@ -98,7 +98,6 @@
     <color name="colorTraceLocationGradientBackground">#F5F5F5</color>
     <color name="colorTraceLocationButtonTextColor">#F5F5F5</color>
 
-
     <!-- QR Code Scan -->
     <color name="colorQrCodeScanToolbar">#000000</color>
     <color name="colorQrCodeScanMask">#BFFFFFFF</color>
@@ -106,5 +105,8 @@
     <!-- Swipe to delete background color -->
     <color name="swipeDeleteBackgroundColor">#EB4D3D</color>
 
+    <!-- Vaccination -->
+    <color name="colorVaccinationCardBackground">#FFFFFF</color>
+
     <color name="whiteAlpha60">#99FFFFFF</color>
 </resources>
diff --git a/Corona-Warn-App/src/main/res/values/styles.xml b/Corona-Warn-App/src/main/res/values/styles.xml
index 5d15c3486..3923b66d0 100644
--- a/Corona-Warn-App/src/main/res/values/styles.xml
+++ b/Corona-Warn-App/src/main/res/values/styles.xml
@@ -237,6 +237,13 @@
         <item name="android:elevation">@dimen/elevation_none</item>
     </style>
 
+    <style name="Card.Vaccination" parent="Card.NoElevation">
+        <item name="android:backgroundTint">@color/colorVaccinationCardBackground</item>
+    </style>
+
+    <style name="Card.Vaccination.Ripple">
+        <item name="android:background">@drawable/grey_card_ripple</item>
+    </style>
 
     <style name="Card.NoPadding">
         <item name="android:padding">@dimen/no_padding</item>
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 63c692c41..19d6084dc 100644
--- a/Corona-Warn-App/src/main/res/values/vaccination_strings.xml
+++ b/Corona-Warn-App/src/main/res/values/vaccination_strings.xml
@@ -28,4 +28,29 @@
     <string name="vaccination_details_deletion_dialog_positive_button">Entfernen</string>
     <!-- XBUT: Vaccination Details deletion dialog negative button-->
     <string name="vaccination_details_deletion_dialog_negative_button">Abbrechen</string>
+
+    <!-- XTXT: Vaccination List title-->
+    <string name="vaccination_list_title">Digitaler Impfnachweis</string>
+    <!-- XTXT: Vaccination List complete vaccination subtitle-->
+    <string name="vaccination_list_complete_vaccination_subtitle">SARS-CoV-2-Impfschutz</string>
+    <!-- XTXT: Vaccination List top card title-->
+    <string name="vaccination_list_top_card_title">SARS-CoV-2 Impfung</string>
+    <!-- XTXT: Vaccination List top card subtitle-->
+    <string name="vaccination_list_top_card_subtitle">Unvollständiger Impfschutz</string>
+    <!-- XTXT: Vaccination List name card subtitle-->
+    <string name="vaccination_list_name_card_subtitle">geboren %1$s</string>
+    <!-- XTXT: Vaccination List top card subtitle-->
+    <string name="vaccination_list_vaccination_card_title">Impfung %1$s von %2$s</string>
+    <!-- XTXT: Vaccination List top card subtitle-->
+    <string name="vaccination_list_vaccination_card_subtitle">durchgeführt am %1$s</string>
+    <!-- XTXT: Vaccination List top card subtitle-->
+    <string name="vaccination_list_certificate_card_title">COVID-19-Prüfzertifikat</string>
+    <!-- XTXT: Vaccination List top card subtitle-->
+    <string name="vaccination_list_certificate_card_subtitle">Noch %1$d Tage gültig</string>
+    <!-- XBUT: Vaccination List register additional vaccination button -->
+    <string name="vaccination_list_register_new_vaccination_button">Weitere Impfung registrieren</string>
+    <!-- XBUT: Vaccination List refresh button -->
+    <string name="vaccination_list_refresh_button">Aktualisieren</string>
+    <!-- XACT: Vaccination List Qr-Code for screen readers -->
+    <string name="vaccination_list_qr_code_accessibility">Qr-Code</string>
 </resources>
\ No newline at end of file
-- 
GitLab