diff --git a/stripe/res/drawable/ic_trash.xml b/stripe/res/drawable/ic_trash.xml
index eb3494ac2b1..40e175221ba 100644
--- a/stripe/res/drawable/ic_trash.xml
+++ b/stripe/res/drawable/ic_trash.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24"
android:viewportHeight="24">
diff --git a/stripe/res/values/colors.xml b/stripe/res/values/colors.xml
index 6ce331e1f59..de1cb03e493 100644
--- a/stripe/res/values/colors.xml
+++ b/stripe/res/values/colors.xml
@@ -13,4 +13,7 @@
@android:color/white
@android:color/secondary_text_light
+
+ #DFDEDF
+ #D5473F
diff --git a/stripe/src/main/java/com/stripe/android/view/PaymentMethodSwipeCallback.kt b/stripe/src/main/java/com/stripe/android/view/PaymentMethodSwipeCallback.kt
index 57ef3660535..b8adc0f2bcb 100644
--- a/stripe/src/main/java/com/stripe/android/view/PaymentMethodSwipeCallback.kt
+++ b/stripe/src/main/java/com/stripe/android/view/PaymentMethodSwipeCallback.kt
@@ -2,10 +2,13 @@ package com.stripe.android.view
import android.content.Context
import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.View
+import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
-import com.stripe.android.R
import com.stripe.android.model.PaymentMethod
/**
@@ -19,7 +22,13 @@ internal class PaymentMethodSwipeCallback(
) : ItemTouchHelper.SimpleCallback(
0, ItemTouchHelper.RIGHT
) {
- private val trashIcon = ContextCompat.getDrawable(context, R.drawable.ic_trash)
+ private val trashIcon =
+ ContextCompat.getDrawable(context, com.stripe.android.R.drawable.ic_trash)!!
+ private val swipeStartColor =
+ ContextCompat.getColor(context, com.stripe.android.R.color.swipe_start_payment_method)
+ private val swipeThresholdColor =
+ ContextCompat.getColor(context, com.stripe.android.R.color.swipe_threshold_payment_method)
+ private val background = ColorDrawable(swipeStartColor)
override fun onMove(
recyclerView: RecyclerView,
@@ -34,8 +43,20 @@ internal class PaymentMethodSwipeCallback(
listener.onSwiped(paymentMethod)
}
+ override fun getSwipeDirs(
+ recyclerView: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder
+ ): Int {
+ return if (viewHolder is PaymentMethodsAdapter.PaymentMethodViewHolder) {
+ // only allow swiping on Payment Method items
+ super.getSwipeDirs(recyclerView, viewHolder)
+ } else {
+ 0
+ }
+ }
+
override fun onChildDraw(
- c: Canvas,
+ canvas: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
@@ -43,20 +64,115 @@ internal class PaymentMethodSwipeCallback(
actionState: Int,
isCurrentlyActive: Boolean
) {
- super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
- viewHolder.itemView
+ super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
+ if (viewHolder is PaymentMethodsAdapter.PaymentMethodViewHolder) {
+ val itemView = viewHolder.itemView
+
+ val startTransition = itemView.width * START_TRANSITION_THRESHOLD
+ val endTransition = itemView.width * END_TRANSITION_THRESHOLD
+
+ // calculate the transition fraction to animate the background color of the swipe
+ val transitionFraction: Float =
+ when {
+ dX < startTransition ->
+ 0F
+ dX >= endTransition ->
+ 1F
+ else ->
+ ((dX - startTransition) / (endTransition - startTransition))
+ }
+
+ updateSwipedPaymentMethod(
+ itemView,
+ dX.toInt(),
+ transitionFraction,
+ canvas
+ )
+ }
}
- override fun getSwipeDirs(
- recyclerView: RecyclerView,
- viewHolder: RecyclerView.ViewHolder
- ): Int {
- return if (viewHolder is PaymentMethodsAdapter.PaymentMethodViewHolder) {
- // only allow swiping on Payment Method items
- super.getSwipeDirs(recyclerView, viewHolder)
- } else {
- 0
+ override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float {
+ return END_TRANSITION_THRESHOLD
+ }
+
+ private fun updateSwipedPaymentMethod(
+ itemView: View,
+ dX: Int,
+ transitionFraction: Float,
+ canvas: Canvas
+ ) {
+ val backgroundCornerOffset = trashIcon.intrinsicWidth / 2
+
+ val iconMargin = (itemView.height - trashIcon.intrinsicHeight) / 2
+ val iconTop = itemView.top + (itemView.height - trashIcon.intrinsicHeight) / 2
+ val iconBottom = iconTop + trashIcon.intrinsicHeight
+
+ when {
+ // swipe right
+ dX > 0 -> {
+ val iconLeft = itemView.left + iconMargin
+ val iconRight = iconLeft + trashIcon.intrinsicWidth
+
+ // hide the icon until the swipe distance is enough that it won't clash
+ // with the view
+ if (dX > iconRight) {
+ trashIcon.setBounds(iconLeft, iconTop, iconRight, iconBottom)
+ } else {
+ trashIcon.setBounds(0, 0, 0, 0)
+ }
+
+ background.setBounds(itemView.left, itemView.top,
+ itemView.left + dX + backgroundCornerOffset,
+ itemView.bottom)
+ background.color = when {
+ transitionFraction <= 0.0F ->
+ swipeStartColor
+ transitionFraction >= 1.0F ->
+ swipeThresholdColor
+ else ->
+ calculateTransitionColor(
+ transitionFraction,
+ swipeStartColor,
+ swipeThresholdColor
+ )
+ }
+ }
+ else -> {
+ // reset when done swiping
+ trashIcon.setBounds(0, 0, 0, 0)
+ background.setBounds(0, 0, 0, 0)
+ }
}
+
+ background.draw(canvas)
+ trashIcon.draw(canvas)
+ }
+
+ companion object {
+ // calculate the background color while transitioning from start to end threshold
+ internal fun calculateTransitionColor(
+ fraction: Float,
+ @ColorInt startValue: Int,
+ @ColorInt endValue: Int
+ ): Int {
+ val startAlpha = Color.alpha(startValue)
+ val startRed = Color.red(startValue)
+ val startGreen = Color.green(startValue)
+ val startBlue = Color.blue(startValue)
+ val deltaAlpha = (Color.alpha(endValue) - startAlpha) * fraction
+ val deltaRed = (Color.red(endValue) - startRed) * fraction
+ val deltaGreen = (Color.green(endValue) - startGreen) * fraction
+ val deltaBlue = (Color.blue(endValue) - startBlue) * fraction
+ return Color.argb(
+ (startAlpha + deltaAlpha).toInt(),
+ (startRed + deltaRed).toInt(),
+ (startGreen + deltaGreen).toInt(),
+ (startBlue + deltaBlue).toInt()
+ )
+ }
+
+ private const val START_TRANSITION_THRESHOLD = 0.25F
+ private const val END_TRANSITION_THRESHOLD = 0.5F
}
interface Listener {
diff --git a/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.java b/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.java
index 7f20751ad1b..c6c23eef75a 100644
--- a/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.java
+++ b/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.java
@@ -47,9 +47,6 @@ public class PaymentMethodsActivity extends AppCompatActivity {
public static final String TOKEN_PAYMENT_METHODS_ACTIVITY = "PaymentMethodsActivity";
- // TODO(mshafrir-stripe): enable when ready
- private static final boolean SHOULD_ENABLE_PAYMENT_METHOD_SWIPING = false;
-
private PaymentMethodsAdapter mAdapter;
private ProgressBar mProgressBar;
private boolean mStartedFromPaymentSession;
@@ -110,9 +107,7 @@ public void onAnimationFinished(@NonNull RecyclerView.ViewHolder viewHolder) {
new PaymentMethodSwipeCallback(this, mAdapter,
new SwipeToDeleteCallbackListener(this))
);
- if (SHOULD_ENABLE_PAYMENT_METHOD_SWIPING) {
- itemTouchHelper.attachToRecyclerView(recyclerView);
- }
+ itemTouchHelper.attachToRecyclerView(recyclerView);
mCustomerSession = CustomerSession.getInstance();
mStartedFromPaymentSession = args.isPaymentSessionActive;
diff --git a/stripe/src/test/java/com/stripe/android/view/PaymentMethodSwipeCallbackTest.kt b/stripe/src/test/java/com/stripe/android/view/PaymentMethodSwipeCallbackTest.kt
new file mode 100644
index 00000000000..3b0bf0b9ad8
--- /dev/null
+++ b/stripe/src/test/java/com/stripe/android/view/PaymentMethodSwipeCallbackTest.kt
@@ -0,0 +1,25 @@
+package com.stripe.android.view
+
+import android.content.Context
+import androidx.core.content.ContextCompat
+import androidx.test.core.app.ApplicationProvider
+import com.stripe.android.R
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class PaymentMethodSwipeCallbackTest {
+
+ @Test
+ fun testCalculateTransitionColor() {
+ val context: Context = ApplicationProvider.getApplicationContext()
+ val calculatedColor = PaymentMethodSwipeCallback.calculateTransitionColor(
+ 0.25F,
+ ContextCompat.getColor(context, R.color.swipe_start_payment_method),
+ ContextCompat.getColor(context, R.color.swipe_threshold_payment_method)
+ )
+ assertEquals(-2312009, calculatedColor)
+ }
+}