From ad66d04e7bb2f90dcf63df130047ca3d9be3f526 Mon Sep 17 00:00:00 2001
From: Matthias Urhahn <matthias.urhahn@sap.com>
Date: Thu, 20 May 2021 15:47:41 +0200
Subject: [PATCH] Reverse swipe-responsibility (opt-out -> opt-in) (DEV)
 (#3223)

* Reverse swipe-responsibility (opt-out -> opt-in)
Call setupSwipe() on a RecyclerView, then any ViewHolder that implements `Swipeable` can be swiped.
Previously other unrelated items were swipeable too and had to be explicitly excluded.

* Remove unused extensions.

Co-authored-by: chris-cwa <69595386+chris-cwa@users.noreply.github.com>
Co-authored-by: Mohamed Metwalli <mohamed.metwalli@sap.com>
Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com>
---
 .../attendee/checkins/CheckInsFragment.kt     | 19 ++-----
 .../checkins/items/ActiveCheckInVH.kt         | 17 +++++--
 .../checkins/items/CameraPermissionVH.kt      |  4 +-
 .../attendee/checkins/items/PastCheckInVH.kt  | 18 +++++--
 .../organizer/list/TraceLocationsFragment.kt  | 10 +---
 .../organizer/list/items/TraceLocationVH.kt   | 16 ++++--
 .../coronawarnapp/util/list/SwipeConsumer.kt  | 49 -------------------
 .../coronawarnapp/util/list/SwipeExtension.kt | 37 +++++++-------
 .../rki/coronawarnapp/util/list/Swipeable.kt  | 23 +++++++++
 .../ui/list/VaccinationListFragment.kt        | 16 ++----
 ...nationListImmunityInformationCardItemVH.kt |  4 +-
 .../VaccinationListNameCardItemVH.kt          |  4 +-
 .../VaccinationListQrCodeCardItemVH.kt        |  4 +-
 .../VaccinationListVaccinationCardItemVH.kt   | 22 ++++++---
 14 files changed, 105 insertions(+), 138 deletions(-)
 delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/SwipeConsumer.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/Swipeable.kt

diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsFragment.kt
index d03a3c9ff..488b48271 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/CheckInsFragment.kt
@@ -26,8 +26,7 @@ import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items.CameraPer
 import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items.CheckInsItem
 import de.rki.coronawarnapp.ui.presencetracing.attendee.edit.EditCheckInFragmentArgs
 import de.rki.coronawarnapp.util.di.AutoInject
-import de.rki.coronawarnapp.util.list.isSwipeable
-import de.rki.coronawarnapp.util.list.onSwipeItem
+import de.rki.coronawarnapp.util.list.setupSwipe
 import de.rki.coronawarnapp.util.lists.decorations.TopBottomPaddingDecorator
 import de.rki.coronawarnapp.util.lists.diffutil.update
 import de.rki.coronawarnapp.util.onScroll
@@ -190,14 +189,7 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag
                 }
             }
 
-            onSwipeItem(
-                context = requireContext(),
-            ) { position, direction ->
-                val checkInsItem = checkInsAdapter.data[position]
-                if (checkInsItem.isSwipeable()) {
-                    checkInsItem.onSwipe(position, direction)
-                }
-            }
+            setupSwipe(context = requireContext())
         }
     }
 
@@ -211,11 +203,8 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag
             setPositiveButton(R.string.generic_action_remove) { _, _ ->
                 viewModel.onRemoveCheckInConfirmed(checkIn)
             }
-            setNegativeButton(R.string.generic_action_abort) { _, _ ->
-                position?.let { checkInsAdapter.notifyItemChanged(position) }
-            }
-
-            setOnCancelListener {
+            setNegativeButton(R.string.generic_action_abort) { _, _ -> }
+            setOnDismissListener {
                 position?.let { checkInsAdapter.notifyItemChanged(position) }
             }
         }.show()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/ActiveCheckInVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/ActiveCheckInVH.kt
index 40af15182..2ce414b1d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/ActiveCheckInVH.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/ActiveCheckInVH.kt
@@ -1,12 +1,13 @@
 package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items
 
 import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.contactdiary.util.getLocale
 import de.rki.coronawarnapp.databinding.TraceLocationAttendeeCheckinsItemActiveBinding
 import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone
-import de.rki.coronawarnapp.util.list.SwipeConsumer
+import de.rki.coronawarnapp.util.list.Swipeable
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
 import org.joda.time.Duration
 import org.joda.time.DurationFieldType
@@ -20,7 +21,14 @@ class ActiveCheckInVH(parent: ViewGroup) :
     BaseCheckInVH<ActiveCheckInVH.Item, TraceLocationAttendeeCheckinsItemActiveBinding>(
         layoutRes = R.layout.trace_location_attendee_checkins_item_active,
         parent = parent
-    ) {
+    ),
+    Swipeable {
+
+    private var latestItem: Item? = null
+
+    override fun onSwipe(holder: RecyclerView.ViewHolder, direction: Int) {
+        latestItem?.let { it.onSwipeItem(it.checkin, holder.adapterPosition) }
+    }
 
     override val viewBinding: Lazy<TraceLocationAttendeeCheckinsItemActiveBinding> = lazy {
         TraceLocationAttendeeCheckinsItemActiveBinding.bind(itemView)
@@ -35,6 +43,7 @@ class ActiveCheckInVH(parent: ViewGroup) :
         payloads: List<Any>
     ) -> Unit = { item, payloads ->
         val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
+        latestItem = curItem
 
         val checkInStartUserTZ = curItem.checkin.checkInStart.toUserTimeZone()
 
@@ -93,12 +102,10 @@ class ActiveCheckInVH(parent: ViewGroup) :
         val onRemoveItem: (CheckIn) -> Unit,
         val onCheckout: (CheckIn) -> Unit,
         val onSwipeItem: (CheckIn, Int) -> Unit,
-    ) : CheckInsItem, HasPayloadDiffer, SwipeConsumer {
+    ) : CheckInsItem, HasPayloadDiffer {
         override val stableId: Long = checkin.id
 
         override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
-
-        override fun onSwipe(position: Int, direction: Int) = onSwipeItem(checkin, position)
     }
 
     companion object {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/CameraPermissionVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/CameraPermissionVH.kt
index 43d63e51f..c0dd87bca 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/CameraPermissionVH.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/CameraPermissionVH.kt
@@ -3,14 +3,12 @@ package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items
 import android.view.ViewGroup
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.TraceLocationAttendeeCheckinsItemCameraBinding
-import de.rki.coronawarnapp.util.list.Movable
 
 class CameraPermissionVH(parent: ViewGroup) :
     BaseCheckInVH<CameraPermissionVH.Item, TraceLocationAttendeeCheckinsItemCameraBinding>(
         layoutRes = R.layout.trace_location_attendee_checkins_item_camera,
         parent = parent
-    ),
-    Movable {
+    ) {
 
     override val viewBinding: Lazy<TraceLocationAttendeeCheckinsItemCameraBinding> = lazy {
         TraceLocationAttendeeCheckinsItemCameraBinding.bind(itemView)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/PastCheckInVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/PastCheckInVH.kt
index 106b09f13..67f8c4363 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/PastCheckInVH.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/attendee/checkins/items/PastCheckInVH.kt
@@ -1,18 +1,26 @@
 package de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items
 
 import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.TraceLocationAttendeeCheckinsItemPastBinding
 import de.rki.coronawarnapp.presencetracing.checkins.CheckIn
 import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.common.checkoutInfo
-import de.rki.coronawarnapp.util.list.SwipeConsumer
+import de.rki.coronawarnapp.util.list.Swipeable
 import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer
 
 class PastCheckInVH(parent: ViewGroup) :
     BaseCheckInVH<PastCheckInVH.Item, TraceLocationAttendeeCheckinsItemPastBinding>(
         layoutRes = R.layout.trace_location_attendee_checkins_item_past,
         parent = parent
-    ) {
+    ),
+    Swipeable {
+
+    private var latestItem: Item? = null
+
+    override fun onSwipe(holder: RecyclerView.ViewHolder, direction: Int) {
+        latestItem?.let { it.onSwipeItem(it.checkin, holder.adapterPosition) }
+    }
 
     override val viewBinding: Lazy<TraceLocationAttendeeCheckinsItemPastBinding> = lazy {
         TraceLocationAttendeeCheckinsItemPastBinding.bind(itemView)
@@ -23,6 +31,8 @@ class PastCheckInVH(parent: ViewGroup) :
         payloads: List<Any>
     ) -> Unit = { item, payloads ->
         val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item
+        latestItem = curItem
+
         description.text = curItem.checkin.description
         address.text = curItem.checkin.address
 
@@ -46,11 +56,9 @@ class PastCheckInVH(parent: ViewGroup) :
         val onCardClicked: (CheckIn, Int) -> Unit,
         val onRemoveItem: (CheckIn) -> Unit,
         val onSwipeItem: (CheckIn, Int) -> Unit,
-    ) : CheckInsItem, HasPayloadDiffer, SwipeConsumer {
+    ) : CheckInsItem, HasPayloadDiffer {
         override val stableId: Long = checkin.id
 
         override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null
-
-        override fun onSwipe(position: Int, direction: Int) = onSwipeItem(checkin, position)
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/list/TraceLocationsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/list/TraceLocationsFragment.kt
index 3c20d506d..ad2126659 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/list/TraceLocationsFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/list/TraceLocationsFragment.kt
@@ -20,8 +20,7 @@ import de.rki.coronawarnapp.ui.presencetracing.organizer.category.adapter.catego
 import de.rki.coronawarnapp.ui.presencetracing.organizer.details.QrCodeDetailFragmentArgs
 import de.rki.coronawarnapp.util.DialogHelper
 import de.rki.coronawarnapp.util.di.AutoInject
-import de.rki.coronawarnapp.util.list.isSwipeable
-import de.rki.coronawarnapp.util.list.onSwipeItem
+import de.rki.coronawarnapp.util.list.setupSwipe
 import de.rki.coronawarnapp.util.lists.decorations.TopBottomPaddingDecorator
 import de.rki.coronawarnapp.util.lists.diffutil.update
 import de.rki.coronawarnapp.util.onScroll
@@ -58,12 +57,7 @@ class TraceLocationsFragment : Fragment(R.layout.trace_location_organizer_trace_
             onScroll {
                 onScrollChange(it)
             }
-            onSwipeItem(context = requireContext()) { position, direction ->
-                val traceLocationItem = traceLocationsAdapter.data[position]
-                if (traceLocationItem.isSwipeable()) {
-                    traceLocationItem.onSwipe(position, direction)
-                }
-            }
+            setupSwipe(context = requireContext())
         }
 
         binding.toolbar.setNavigationOnClickListener {
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/list/items/TraceLocationVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/list/items/TraceLocationVH.kt
index 8b1504fc2..a2a24e884 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/list/items/TraceLocationVH.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/list/items/TraceLocationVH.kt
@@ -3,19 +3,27 @@ package de.rki.coronawarnapp.ui.presencetracing.organizer.list.items
 import android.view.ViewGroup
 import androidx.core.view.isGone
 import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.TraceLocationOrganizerTraceLocationsItemBinding
 import de.rki.coronawarnapp.presencetracing.checkins.qrcode.TraceLocation
 import de.rki.coronawarnapp.ui.presencetracing.attendee.checkins.items.BaseCheckInVH.Companion.setupMenu
 import de.rki.coronawarnapp.ui.presencetracing.organizer.list.TraceLocationsAdapter
-import de.rki.coronawarnapp.util.list.SwipeConsumer
+import de.rki.coronawarnapp.util.list.Swipeable
 import org.joda.time.format.DateTimeFormat
 
 class TraceLocationVH(parent: ViewGroup) :
     TraceLocationsAdapter.ItemVH<TraceLocationVH.Item, TraceLocationOrganizerTraceLocationsItemBinding>(
         layoutRes = R.layout.trace_location_organizer_trace_locations_item,
         parent = parent
-    ) {
+    ),
+    Swipeable {
+
+    private var latestItem: Item? = null
+
+    override fun onSwipe(holder: RecyclerView.ViewHolder, direction: Int) {
+        latestItem?.let { it.onSwipeItem(it.traceLocation, holder.adapterPosition) }
+    }
 
     override val viewBinding: Lazy<TraceLocationOrganizerTraceLocationsItemBinding> = lazy {
         TraceLocationOrganizerTraceLocationsItemBinding.bind(itemView)
@@ -25,6 +33,7 @@ class TraceLocationVH(parent: ViewGroup) :
         item: Item,
         payloads: List<Any>
     ) -> Unit = { item, _ ->
+        latestItem = item
 
         description.text = item.traceLocation.description
         address.text = item.traceLocation.address
@@ -84,8 +93,7 @@ class TraceLocationVH(parent: ViewGroup) :
         val onDeleteItem: (TraceLocation) -> Unit,
         val onSwipeItem: (TraceLocation, Int) -> Unit,
         val onCardClicked: (TraceLocation, Int) -> Unit
-    ) : TraceLocationItem, SwipeConsumer {
+    ) : TraceLocationItem {
         override val stableId: Long = traceLocation.id.hashCode().toLong()
-        override fun onSwipe(position: Int, direction: Int) = onSwipeItem(traceLocation, position)
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/SwipeConsumer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/SwipeConsumer.kt
deleted file mode 100644
index 986be05ff..000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/SwipeConsumer.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package de.rki.coronawarnapp.util.list
-
-import androidx.recyclerview.widget.ItemTouchHelper
-import androidx.recyclerview.widget.RecyclerView
-import de.rki.coronawarnapp.util.lists.HasStableId
-import kotlin.contracts.ExperimentalContracts
-import kotlin.contracts.contract
-
-/**
- * [RecyclerView] item data model should implement this contract to consume the swipe gestures
- */
-interface SwipeConsumer {
-    /**
-     * On swipe callback
-     * @param position [Int] item position
-     * @param direction [Int] from [ItemTouchHelper] such as [ItemTouchHelper.RIGHT]
-     */
-    fun onSwipe(position: Int, direction: Int)
-}
-
-/**
- * returns true if the item is as [SwipeConsumer] , false otherwise
- * and helps with smart cast
- */
-
-@OptIn(ExperimentalContracts::class)
-fun HasStableId?.isSwipeable(): Boolean {
-    contract {
-        returns(true) implies (this@isSwipeable is SwipeConsumer)
-    }
-    return this != null && this is SwipeConsumer
-}
-
-/**
- * Indicates whether [RecyclerView.ViewHolder]'s can be moved (Dragged, Swiped) or not
- * by default movementFlags = ACTION_STATE_IDLE which indicates that the view does not move
- * this behaviour can be overridden and provide any flag from [ItemTouchHelper]
- */
-interface Movable {
-    val movementFlags: Int get() = ItemTouchHelper.ACTION_STATE_IDLE
-}
-
-@OptIn(ExperimentalContracts::class)
-fun RecyclerView.ViewHolder?.isMovable(): Boolean {
-    contract {
-        returns(true) implies (this@isMovable is Movable)
-    }
-    return this != null && this is Movable
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/SwipeExtension.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/SwipeExtension.kt
index ed2b66642..0201f5f6c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/SwipeExtension.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/SwipeExtension.kt
@@ -18,20 +18,15 @@ import kotlin.math.min
 /**
  * On [RecyclerView] item swipe listener
  * @param context [Context]
- * @param onSwipe on swipe callback. It passes item's position and swipe direction
+ * @param onSwipe if you want to override the swipe callback globally
  *
- * Usage:
- * ```
- * RecyclerView.onSwipeItem(
- *   context = requireContext()
- * ) { position, direction ->
- *   // Do operation here
- * }
- * ```
+ * After calling this on a recyclerview, every ViewHolder that implements [Swipeable] is swipeable.
  */
-fun RecyclerView.onSwipeItem(
+fun RecyclerView.setupSwipe(
     context: Context,
-    onSwipe: (position: Int, direction: Int) -> Unit
+    onSwipe: (holder: RecyclerView.ViewHolder, direction: Int) -> Unit = { holder, direction ->
+        (holder as? Swipeable)?.let { holder.onSwipe(holder, direction) }
+    }
 ) {
     ItemTouchHelper(
         SwipeCallback(
@@ -46,11 +41,10 @@ fun RecyclerView.onSwipeItem(
  */
 private class SwipeCallback(
     context: Context,
-    private val action: (position: Int, direction: Int) -> Unit
-) : ItemTouchHelper.SimpleCallback(
-    0,
-    ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
-) {
+    private val action: (holder: RecyclerView.ViewHolder, direction: Int) -> Unit,
+    private val dragDirs: Int = 0,
+    private val swipeDirs: Int = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT,
+) : ItemTouchHelper.SimpleCallback(dragDirs, swipeDirs) {
     private val icon = context.getDrawableCompat(R.drawable.ic_delete)!!
     private val iconMargin = context.resources.getDimensionPixelSize(R.dimen.swipe_icon_margin)
     private val radius = context.resources.getDimensionPixelSize(R.dimen.radius_card).toFloat()
@@ -69,7 +63,7 @@ private class SwipeCallback(
     ): Boolean = false
 
     override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
-        action(viewHolder.adapterPosition, direction)
+        action(viewHolder, direction)
     }
 
     override fun onChildDraw(
@@ -102,9 +96,12 @@ private class SwipeCallback(
         }
     }
 
-    override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
-        if (viewHolder.isMovable()) return viewHolder.movementFlags
-        return super.getMovementFlags(recyclerView, viewHolder)
+    override fun getMovementFlags(
+        recyclerView: RecyclerView,
+        viewHolder: RecyclerView.ViewHolder
+    ): Int = when (viewHolder) {
+        is Swipeable -> viewHolder.movementFlags ?: ItemTouchHelper.Callback.makeMovementFlags(dragDirs, swipeDirs)
+        else -> ItemTouchHelper.ACTION_STATE_IDLE
     }
 
     private fun onSwipeLeft(
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/Swipeable.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/Swipeable.kt
new file mode 100644
index 000000000..2b9e42c8b
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/list/Swipeable.kt
@@ -0,0 +1,23 @@
+package de.rki.coronawarnapp.util.list
+
+import androidx.recyclerview.widget.ItemTouchHelper
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * [RecyclerView] item data model should implement this contract to consume the swipe gestures
+ */
+interface Swipeable {
+    /**
+     * Indicates whether [RecyclerView.ViewHolder]'s can be moved (Dragged, Swiped) or not
+     * by default movementFlags = ACTION_STATE_IDLE which indicates that the view does not move
+     * this behaviour can be overridden and provide any flag from [ItemTouchHelper]
+     */
+    val movementFlags: Int? get() = null
+
+    /**
+     * On swipe callback
+     * @param holder [RecyclerView.ViewHolder]  that was swiped
+     * @param direction [Int] from [ItemTouchHelper] such as [ItemTouchHelper.RIGHT]
+     */
+    fun onSwipe(holder: RecyclerView.ViewHolder, direction: Int)
+}
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
index c28098130..9c3768891 100644
--- 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
@@ -18,8 +18,7 @@ import de.rki.coronawarnapp.databinding.FragmentVaccinationListBinding
 import de.rki.coronawarnapp.ui.qrcode.fullscreen.QrCodeFullScreenFragmentArgs
 import de.rki.coronawarnapp.ui.view.onOffsetChange
 import de.rki.coronawarnapp.util.di.AutoInject
-import de.rki.coronawarnapp.util.list.isSwipeable
-import de.rki.coronawarnapp.util.list.onSwipeItem
+import de.rki.coronawarnapp.util.list.setupSwipe
 import de.rki.coronawarnapp.util.lists.diffutil.update
 import de.rki.coronawarnapp.util.ui.doNavigate
 import de.rki.coronawarnapp.util.ui.popBackStack
@@ -62,12 +61,7 @@ class VaccinationListFragment : Fragment(R.layout.fragment_vaccination_list), Au
 
             recyclerViewVaccinationList.apply {
                 adapter = vaccinationListAdapter
-                onSwipeItem(requireContext()) { position, direction ->
-                    val vaccinationItem = vaccinationListAdapter.data[position]
-                    if (vaccinationItem.isSwipeable()) {
-                        vaccinationItem.onSwipe(position, direction)
-                    }
-                }
+                setupSwipe(requireContext())
             }
 
             viewModel.uiState.observe(viewLifecycleOwner) { uiState ->
@@ -160,10 +154,8 @@ class VaccinationListFragment : Fragment(R.layout.fragment_vaccination_list), Au
             setPositiveButton(R.string.vaccination_list_deletion_dialog_positive_button) { _, _ ->
                 viewModel.deleteVaccination(vaccinationCertificateId)
             }
-            setNegativeButton(R.string.vaccination_list_deletion_dialog_negative_button) { _, _ ->
-                position?.let { vaccinationListAdapter.notifyItemChanged(it) }
-            }
-            setOnCancelListener {
+            setNegativeButton(R.string.vaccination_list_deletion_dialog_negative_button) { _, _ -> }
+            setOnDismissListener {
                 position?.let { vaccinationListAdapter.notifyItemChanged(it) }
             }
         }.show()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListImmunityInformationCardItemVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListImmunityInformationCardItemVH.kt
index d347b79da..f6824b79b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListImmunityInformationCardItemVH.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListImmunityInformationCardItemVH.kt
@@ -3,7 +3,6 @@ package de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder
 import android.view.ViewGroup
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.VaccinationListImmunityCardBinding
-import de.rki.coronawarnapp.util.list.Movable
 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.VaccinationListImmunityInformationCardItemVH.VaccinationListImmunityInformationCardItem
@@ -13,8 +12,7 @@ class VaccinationListImmunityInformationCardItemVH(parent: ViewGroup) :
     VaccinationListAdapter.ItemVH<VaccinationListImmunityInformationCardItem, VaccinationListImmunityCardBinding>(
         layoutRes = R.layout.vaccination_list_immunity_card,
         parent = parent
-    ),
-    Movable {
+    ) {
 
     override val viewBinding: Lazy<VaccinationListImmunityCardBinding> = lazy {
         VaccinationListImmunityCardBinding.bind(itemView)
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
index 392007562..5cd1501ca 100644
--- 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
@@ -3,7 +3,6 @@ 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.util.list.Movable
 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
@@ -12,8 +11,7 @@ class VaccinationListNameCardItemVH(parent: ViewGroup) :
     VaccinationListAdapter.ItemVH<VaccinationListNameCardItem, VaccinationListNameCardBinding>(
         layoutRes = R.layout.vaccination_list_name_card,
         parent = parent
-    ),
-    Movable {
+    ) {
 
     override val viewBinding: Lazy<VaccinationListNameCardBinding> = lazy {
         VaccinationListNameCardBinding.bind(itemView)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListQrCodeCardItemVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListQrCodeCardItemVH.kt
index 8a41db8da..f5741bd7a 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListQrCodeCardItemVH.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/adapter/viewholder/VaccinationListQrCodeCardItemVH.kt
@@ -5,7 +5,6 @@ import android.view.ViewGroup
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.VaccinationListQrcodeCardBinding
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toShortDayFormat
-import de.rki.coronawarnapp.util.list.Movable
 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.VaccinationListQrCodeCardItemVH.VaccinationListQrCodeCardItem
@@ -16,8 +15,7 @@ class VaccinationListQrCodeCardItemVH(parent: ViewGroup) :
     VaccinationListAdapter.ItemVH<VaccinationListQrCodeCardItem, VaccinationListQrcodeCardBinding>(
         layoutRes = R.layout.vaccination_list_qrcode_card,
         parent = parent
-    ),
-    Movable {
+    ) {
 
     override val viewBinding: Lazy<VaccinationListQrcodeCardBinding> = lazy {
         VaccinationListQrcodeCardBinding.bind(itemView)
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
index f38f54c83..82f2a8388 100644
--- 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
@@ -3,9 +3,10 @@ package de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder
 import android.view.Gravity
 import android.view.ViewGroup
 import androidx.appcompat.widget.PopupMenu
+import androidx.recyclerview.widget.RecyclerView
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.databinding.VaccinationListVaccinationCardBinding
-import de.rki.coronawarnapp.util.list.SwipeConsumer
+import de.rki.coronawarnapp.util.list.Swipeable
 import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson
 import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson.Status.COMPLETE
 import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson.Status.IMMUNITY
@@ -15,13 +16,18 @@ 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,
-) :
+class VaccinationListVaccinationCardItemVH(parent: ViewGroup) :
     VaccinationListAdapter.ItemVH<VaccinationListVaccinationCardItem, VaccinationListVaccinationCardBinding>(
         layoutRes = R.layout.vaccination_list_vaccination_card,
         parent = parent
-    ) {
+    ),
+    Swipeable {
+
+    private var latestItem: VaccinationListVaccinationCardItem? = null
+
+    override fun onSwipe(holder: RecyclerView.ViewHolder, direction: Int) {
+        latestItem?.let { it.onSwipeToDelete(it.vaccinationCertificateId, holder.adapterPosition) }
+    }
 
     override val viewBinding: Lazy<VaccinationListVaccinationCardBinding> = lazy {
         VaccinationListVaccinationCardBinding.bind(itemView)
@@ -30,6 +36,8 @@ class VaccinationListVaccinationCardItemVH(
         item: VaccinationListVaccinationCardItem,
         payloads: List<Any>
     ) -> Unit = { item, _ ->
+        latestItem = item
+
         item.apply {
             root.setOnClickListener {
                 onCardClick.invoke(vaccinationCertificateId)
@@ -82,7 +90,7 @@ class VaccinationListVaccinationCardItemVH(
         val onCardClick: (String) -> Unit,
         val onDeleteClick: (String) -> Unit,
         val onSwipeToDelete: (String, Int) -> Unit
-    ) : VaccinationListItem, SwipeConsumer {
+    ) : VaccinationListItem {
 
         override val stableId: Long = Objects.hash(
             vaccinationCertificateId,
@@ -93,8 +101,6 @@ class VaccinationListVaccinationCardItemVH(
             isFinalVaccination
         ).toLong()
 
-        override fun onSwipe(position: Int, direction: Int) = onSwipeToDelete(vaccinationCertificateId, position)
-
         // 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
-- 
GitLab