diff --git a/stripe/res/drawable/stripe_google_pay_mark.xml b/stripe/res/drawable/stripe_google_pay_mark.xml new file mode 100644 index 00000000000..eeb63d1efe8 --- /dev/null +++ b/stripe/res/drawable/stripe_google_pay_mark.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + diff --git a/stripe/res/layout/google_pay_row.xml b/stripe/res/layout/google_pay_row.xml new file mode 100644 index 00000000000..a963e43d1b5 --- /dev/null +++ b/stripe/res/layout/google_pay_row.xml @@ -0,0 +1,16 @@ + + + + + + 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 751a16ba50b..e9dd9844f2f 100644 --- a/stripe/src/main/java/com/stripe/android/view/PaymentMethodSwipeCallback.kt +++ b/stripe/src/main/java/com/stripe/android/view/PaymentMethodSwipeCallback.kt @@ -50,7 +50,7 @@ internal class PaymentMethodSwipeCallback( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder ): Int { - return if (viewHolder is PaymentMethodsAdapter.PaymentMethodViewHolder) { + return if (viewHolder is PaymentMethodsAdapter.ViewHolder.PaymentMethodViewHolder) { // only allow swiping on Payment Method items super.getSwipeDirs(recyclerView, viewHolder) } else { @@ -68,7 +68,7 @@ internal class PaymentMethodSwipeCallback( isCurrentlyActive: Boolean ) { super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) - if (viewHolder is PaymentMethodsAdapter.PaymentMethodViewHolder) { + if (viewHolder is PaymentMethodsAdapter.ViewHolder.PaymentMethodViewHolder) { val itemView = viewHolder.itemView val startTransition = itemView.width * START_TRANSITION_THRESHOLD diff --git a/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.kt b/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.kt index 09b56d8ffed..6a6e2f72f6e 100644 --- a/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.kt +++ b/stripe/src/main/java/com/stripe/android/view/PaymentMethodsActivity.kt @@ -70,9 +70,9 @@ class PaymentMethodsActivity : AppCompatActivity() { args: PaymentMethodsActivityStarter.Args ) { adapter = PaymentMethodsAdapter( - viewModel.selectedPaymentMethodId, args, - args.paymentMethodTypes + args.paymentMethodTypes, + viewModel.selectedPaymentMethodId ) adapter.listener = object : PaymentMethodsAdapter.Listener { diff --git a/stripe/src/main/java/com/stripe/android/view/PaymentMethodsAdapter.kt b/stripe/src/main/java/com/stripe/android/view/PaymentMethodsAdapter.kt index 03b4ab034e3..5727b942731 100644 --- a/stripe/src/main/java/com/stripe/android/view/PaymentMethodsAdapter.kt +++ b/stripe/src/main/java/com/stripe/android/view/PaymentMethodsAdapter.kt @@ -15,10 +15,10 @@ import java.util.ArrayList * A [RecyclerView.Adapter] that holds a set of [MaskedCardView] items for a given set * of [PaymentMethod] objects. */ -internal class PaymentMethodsAdapter @JvmOverloads internal constructor( - initiallySelectedPaymentMethodId: String?, +internal class PaymentMethodsAdapter constructor( private val intentArgs: PaymentMethodsActivityStarter.Args, - private val addableTypes: List = listOf(PaymentMethod.Type.Card) + private val addableTypes: List = listOf(PaymentMethod.Type.Card), + initiallySelectedPaymentMethodId: String? = null ) : RecyclerView.Adapter() { internal val paymentMethods = ArrayList() @@ -52,15 +52,16 @@ internal class PaymentMethodsAdapter @JvmOverloads internal constructor( return if (position < paymentMethods.size) { val type = paymentMethods[position].type if (PaymentMethod.Type.Card.code == type) { - TYPE_CARD + ViewType.Card.ordinal } else { super.getItemViewType(position) } } else { - val paymentMethodType = addableTypes[getAddableTypesPosition(position)] + val paymentMethodType = + addableTypes[getAddableTypesPosition(position)] return when (paymentMethodType) { - PaymentMethod.Type.Card -> TYPE_ADD_CARD - PaymentMethod.Type.Fpx -> TYPE_ADD_FPX + PaymentMethod.Type.Card -> ViewType.AddCard.ordinal + PaymentMethod.Type.Fpx -> ViewType.AddFpx.ordinal else -> throw IllegalArgumentException( "Unsupported PaymentMethod type: ${paymentMethodType.code}") @@ -77,7 +78,7 @@ internal class PaymentMethodsAdapter @JvmOverloads internal constructor( } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - if (holder is PaymentMethodViewHolder) { + if (holder is ViewHolder.PaymentMethodViewHolder) { val paymentMethod = paymentMethods[position] holder.setPaymentMethod(paymentMethod) holder.setSelected(paymentMethod.id == selectedPaymentMethodId) @@ -109,31 +110,48 @@ internal class PaymentMethodsAdapter @JvmOverloads internal constructor( notifyItemChanged(position) } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return when (viewType) { - TYPE_CARD -> createPaymentMethodViewHolder(parent) - TYPE_ADD_CARD -> createAddCardPaymentMethodViewHolder(parent) - TYPE_ADD_FPX -> createAddFpxPaymentMethodViewHolder(parent) - else -> throw IllegalArgumentException("Unsupported viewType: $viewType") + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): RecyclerView.ViewHolder { + return when (ViewType.values()[viewType]) { + ViewType.Card -> createPaymentMethodViewHolder(parent) + ViewType.AddCard -> createAddCardPaymentMethodViewHolder(parent) + ViewType.AddFpx -> createAddFpxPaymentMethodViewHolder(parent) + ViewType.GooglePay -> createGooglePayViewHolder(parent) } } - private fun createAddCardPaymentMethodViewHolder(parent: ViewGroup): AddCardPaymentMethodViewHolder { - return AddCardPaymentMethodViewHolder( + private fun createAddCardPaymentMethodViewHolder( + parent: ViewGroup + ): ViewHolder.AddCardPaymentMethodViewHolder { + return ViewHolder.AddCardPaymentMethodViewHolder( AddPaymentMethodCardRowView(parent.context as Activity, intentArgs) ) } - private fun createAddFpxPaymentMethodViewHolder(parent: ViewGroup): AddFpxPaymentMethodViewHolder { - return AddFpxPaymentMethodViewHolder( + private fun createAddFpxPaymentMethodViewHolder( + parent: ViewGroup + ): ViewHolder.AddFpxPaymentMethodViewHolder { + return ViewHolder.AddFpxPaymentMethodViewHolder( AddPaymentMethodFpxRowView(parent.context as Activity, intentArgs) ) } - private fun createPaymentMethodViewHolder(parent: ViewGroup): PaymentMethodViewHolder { + private fun createPaymentMethodViewHolder( + parent: ViewGroup + ): ViewHolder.PaymentMethodViewHolder { val itemView = LayoutInflater.from(parent.context) .inflate(R.layout.masked_card_row, parent, false) - return PaymentMethodViewHolder(itemView) + return ViewHolder.PaymentMethodViewHolder(itemView) + } + + private fun createGooglePayViewHolder( + parent: ViewGroup + ): ViewHolder.GooglePayViewHolder { + val itemView = LayoutInflater.from(parent.context) + .inflate(R.layout.google_pay_row, parent, false) + return ViewHolder.GooglePayViewHolder(itemView) } internal fun deletePaymentMethod(paymentMethod: PaymentMethod) { @@ -151,35 +169,46 @@ internal class PaymentMethodsAdapter @JvmOverloads internal constructor( } } - private fun getAddableTypesPosition(position: Int) = position - paymentMethods.size + private fun getAddableTypesPosition(position: Int): Int { + return position - paymentMethods.size + } - internal class PaymentMethodViewHolder constructor( - itemView: View - ) : RecyclerView.ViewHolder(itemView) { - private val cardView: MaskedCardView = itemView.findViewById(R.id.masked_card_item) + internal sealed class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + internal class AddCardPaymentMethodViewHolder( + itemView: View + ) : RecyclerView.ViewHolder(itemView) - fun setPaymentMethod(paymentMethod: PaymentMethod) { - cardView.setPaymentMethod(paymentMethod) - } + internal class AddFpxPaymentMethodViewHolder( + itemView: View + ) : RecyclerView.ViewHolder(itemView) - fun setSelected(selected: Boolean) { - cardView.isSelected = selected - } - } + internal class GooglePayViewHolder( + itemView: View + ) : RecyclerView.ViewHolder(itemView) + + internal class PaymentMethodViewHolder constructor( + itemView: View + ) : ViewHolder(itemView) { + private val cardView: MaskedCardView = itemView.findViewById(R.id.masked_card_item) - internal class AddCardPaymentMethodViewHolder(itemView: View) : - RecyclerView.ViewHolder(itemView) + fun setPaymentMethod(paymentMethod: PaymentMethod) { + cardView.setPaymentMethod(paymentMethod) + } - internal class AddFpxPaymentMethodViewHolder(itemView: View) : - RecyclerView.ViewHolder(itemView) + fun setSelected(selected: Boolean) { + cardView.isSelected = selected + } + } + } internal interface Listener { fun onClick(paymentMethod: PaymentMethod) } - private companion object { - private const val TYPE_CARD = 1 - private const val TYPE_ADD_CARD = 2 - private const val TYPE_ADD_FPX = 3 + private enum class ViewType { + Card, + AddCard, + AddFpx, + GooglePay } } diff --git a/stripe/src/test/java/com/stripe/android/view/PaymentMethodsAdapterTest.kt b/stripe/src/test/java/com/stripe/android/view/PaymentMethodsAdapterTest.kt index ad050517ac7..555df4a31e5 100644 --- a/stripe/src/test/java/com/stripe/android/view/PaymentMethodsAdapterTest.kt +++ b/stripe/src/test/java/com/stripe/android/view/PaymentMethodsAdapterTest.kt @@ -1,6 +1,7 @@ package com.stripe.android.view import androidx.recyclerview.widget.RecyclerView +import com.nhaarman.mockitokotlin2.verify import com.stripe.android.model.PaymentMethodFixtures import kotlin.test.BeforeTest import kotlin.test.Test @@ -10,7 +11,6 @@ import kotlin.test.assertNull import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.times -import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.robolectric.RobolectricTestRunner @@ -21,15 +21,15 @@ import org.robolectric.RobolectricTestRunner class PaymentMethodsAdapterTest { @Mock private lateinit var adapterDataObserver: RecyclerView.AdapterDataObserver - private lateinit var paymentMethodsAdapter: PaymentMethodsAdapter + + private val paymentMethodsAdapter: PaymentMethodsAdapter = + PaymentMethodsAdapter(PaymentMethodsActivityStarter.Args.Builder() + .build() + ) @BeforeTest fun setup() { - PaymentMethodFixtures.createCard() MockitoAnnotations.initMocks(this) - paymentMethodsAdapter = PaymentMethodsAdapter(null, - PaymentMethodsActivityStarter.Args.Builder() - .build()) paymentMethodsAdapter.registerAdapterDataObserver(adapterDataObserver) } @@ -37,7 +37,7 @@ class PaymentMethodsAdapterTest { fun setSelection_changesSelection() { paymentMethodsAdapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS) assertEquals(4, paymentMethodsAdapter.itemCount) - verify(adapterDataObserver).onChanged() + verify(adapterDataObserver).onChanged() paymentMethodsAdapter.selectedPaymentMethodId = paymentMethodsAdapter.paymentMethods[2].id assertEquals( @@ -75,7 +75,7 @@ class PaymentMethodsAdapterTest { PaymentMethodFixtures.CARD_PAYMENT_METHODS[2].id, paymentMethodsAdapter.selectedPaymentMethod?.id ) - verify(adapterDataObserver, times(2)) + verify(adapterDataObserver, times(2)) .onChanged() } @@ -96,18 +96,21 @@ class PaymentMethodsAdapterTest { @Test fun setPaymentMethods_whenNoInitialSpecified_returnsNull() { - val adapter = PaymentMethodsAdapter(null, - PaymentMethodsActivityStarter.Args.Builder() - .build()) + val adapter = PaymentMethodsAdapter( + intentArgs = PaymentMethodsActivityStarter.Args.Builder() + .build() + ) adapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS) assertNull(adapter.selectedPaymentMethod) } @Test fun setPaymentMethods_whenInitialSpecified_selectsIt() { - val adapter = PaymentMethodsAdapter("pm_1000", - PaymentMethodsActivityStarter.Args.Builder() - .build()) + val adapter = PaymentMethodsAdapter( + intentArgs = PaymentMethodsActivityStarter.Args.Builder() + .build(), + initiallySelectedPaymentMethodId = "pm_1000" + ) adapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS) assertEquals("pm_1000", adapter.selectedPaymentMethod?.id) }