diff --git a/example/src/main/java/com/stripe/example/activity/CreateCardTokenActivity.kt b/example/src/main/java/com/stripe/example/activity/CreateCardTokenActivity.kt index f762306d7de..b7d2a60fb89 100644 --- a/example/src/main/java/com/stripe/example/activity/CreateCardTokenActivity.kt +++ b/example/src/main/java/com/stripe/example/activity/CreateCardTokenActivity.kt @@ -61,6 +61,8 @@ class CreateCardTokenActivity : AppCompatActivity() { .show() } } + + card_input_widget.requestFocus() } private fun onRequestStart() { diff --git a/stripe/res/layout/card_input_widget.xml b/stripe/res/layout/card_input_widget.xml index 4d4d1231e0c..40e11e6d1f0 100644 --- a/stripe/res/layout/card_input_widget.xml +++ b/stripe/res/layout/card_input_widget.xml @@ -1,5 +1,6 @@ - - - - - - - + android:background="@android:color/transparent" + tools:ignore="UnusedAttribute" + android:accessibilityTraversalBefore="@+id/tl_expiry_date" + android:nextFocusRight="@+id/tl_expiry_date" + android:nextFocusForward="@+id/tl_expiry_date" + android:nextFocusDown="@+id/tl_expiry_date" + android:contentDescription="@string/acc_label_card_number" + app:hintEnabled="false"> + + - + android:visibility="visible" + android:background="@android:color/transparent" + tools:ignore="UnusedAttribute" + android:accessibilityTraversalBefore="@+id/tl_cvc" + android:accessibilityTraversalAfter="@+id/tl_card_number" + android:nextFocusRight="@+id/tl_cvc" + android:nextFocusForward="@+id/tl_cvc" + android:nextFocusDown="@+id/tl_cvc" + android:nextFocusLeft="@id/tl_card_number" + android:nextFocusUp="@id/tl_card_number" + android:contentDescription="@string/acc_label_expiry_date" + app:hintEnabled="false"> + + - + android:background="@android:color/transparent" + tools:ignore="UnusedAttribute" + android:accessibilityTraversalAfter="@+id/tl_expiry_date" + android:nextFocusLeft="@id/tl_expiry_date" + android:nextFocusUp="@id/tl_expiry_date" + android:contentDescription="@string/cvc_number_hint" + app:hintEnabled="false"> + + - + android:background="@android:color/transparent" + tools:ignore="UnusedAttribute" + android:accessibilityTraversalAfter="@+id/tl_cvc" + android:nextFocusLeft="@id/tl_cvc" + android:nextFocusUp="@id/tl_cvc" + android:contentDescription="@string/address_label_postal_code" + app:hintEnabled="false"> + + diff --git a/stripe/src/main/java/com/stripe/android/view/CardInputWidget.kt b/stripe/src/main/java/com/stripe/android/view/CardInputWidget.kt index d478164c88f..6e2d4aa9a90 100644 --- a/stripe/src/main/java/com/stripe/android/view/CardInputWidget.kt +++ b/stripe/src/main/java/com/stripe/android/view/CardInputWidget.kt @@ -26,6 +26,7 @@ import androidx.core.graphics.drawable.DrawableCompat import androidx.core.view.AccessibilityDelegateCompat import androidx.core.view.ViewCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat +import com.google.android.material.textfield.TextInputLayout import com.stripe.android.R import com.stripe.android.model.Address import com.stripe.android.model.Card @@ -50,6 +51,11 @@ class CardInputWidget @JvmOverloads constructor( private val cardIconImageView: ImageView private val frameLayout: FrameLayout + private val cardNumberTextInputLayout: TextInputLayout + private val expiryDateTextInputLayout: TextInputLayout + private val cvcNumberTextInputLayout: TextInputLayout + private val postalCodeTextInputLayout: TextInputLayout + private val cardNumberEditText: CardNumberEditText private val expiryDateEditText: ExpiryDateEditText private val cvcNumberEditText: CvcEditText @@ -154,8 +160,7 @@ class CardInputWidget @JvmOverloads constructor( */ override val card: Card? get() { - val builder = cardBuilder - return builder?.build() + return cardBuilder?.build() } override val cardBuilder: Card.Builder? @@ -198,10 +203,16 @@ class CardInputWidget @JvmOverloads constructor( minimumWidth = resources.getDimensionPixelSize(R.dimen.stripe_card_widget_min_width) frameLayout = findViewById(R.id.frame_container) - cardNumberEditText = frameLayout.findViewById(R.id.et_card_number) - expiryDateEditText = frameLayout.findViewById(R.id.et_expiry_date) - cvcNumberEditText = frameLayout.findViewById(R.id.et_cvc) - postalCodeEditText = frameLayout.findViewById(R.id.et_postal_code) + + cardNumberTextInputLayout = frameLayout.findViewById(R.id.tl_card_number) + expiryDateTextInputLayout = frameLayout.findViewById(R.id.tl_expiry_date) + cvcNumberTextInputLayout = frameLayout.findViewById(R.id.tl_cvc) + postalCodeTextInputLayout = frameLayout.findViewById(R.id.tl_postal_code) + + cardNumberEditText = cardNumberTextInputLayout.findViewById(R.id.et_card_number) + expiryDateEditText = expiryDateTextInputLayout.findViewById(R.id.et_expiry_date) + cvcNumberEditText = cvcNumberTextInputLayout.findViewById(R.id.et_cvc) + postalCodeEditText = postalCodeTextInputLayout.findViewById(R.id.et_postal_code) postalCodeEditText.configureForGlobal() frameWidthSupplier = { frameLayout.width } @@ -370,22 +381,22 @@ class CardInputWidget @JvmOverloads constructor( } updateFieldLayout( - view = cardNumberEditText, + view = cardNumberTextInputLayout, width = placementParameters.cardWidth, leftMargin = cardLeftMargin ) updateFieldLayout( - view = expiryDateEditText, + view = expiryDateTextInputLayout, width = placementParameters.dateWidth, leftMargin = dateLeftMargin ) updateFieldLayout( - view = cvcNumberEditText, + view = cvcNumberTextInputLayout, width = placementParameters.cvcWidth, leftMargin = cvcLeftMargin ) updateFieldLayout( - view = postalCodeEditText, + view = postalCodeTextInputLayout, width = placementParameters.postalCodeWidth, leftMargin = postalCodeLeftMargin ) @@ -399,12 +410,12 @@ class CardInputWidget @JvmOverloads constructor( private fun updatePostalCodeEditText(isEnabled: Boolean) { if (isEnabled) { postalCodeEditText.isEnabled = true - postalCodeEditText.visibility = View.VISIBLE + postalCodeTextInputLayout.visibility = View.VISIBLE cvcNumberEditText.imeOptions = EditorInfo.IME_ACTION_NEXT } else { postalCodeEditText.isEnabled = false - postalCodeEditText.visibility = View.GONE + postalCodeTextInputLayout.visibility = View.GONE cvcNumberEditText.imeOptions = EditorInfo.IME_ACTION_DONE } @@ -424,7 +435,7 @@ class CardInputWidget @JvmOverloads constructor( * if no such request is necessary. */ @VisibleForTesting - internal fun getFocusRequestOnTouch(touchX: Int): StripeEditText? { + internal fun getFocusRequestOnTouch(touchX: Int): View? { val frameStart = frameLayout.left return when { @@ -547,6 +558,20 @@ class CardInputWidget @JvmOverloads constructor( postalCodeEditText.setAutofillHints(View.AUTOFILL_HINT_POSTAL_CODE) } + ViewCompat.setAccessibilityDelegate( + cardNumberEditText, + object : AccessibilityDelegateCompat() { + override fun onInitializeAccessibilityNodeInfo( + host: View, + info: AccessibilityNodeInfoCompat + ) { + super.onInitializeAccessibilityNodeInfo(host, info) + + // Avoid reading out "1234 1234 1234 1234" + info.hintText = null + } + }) + ViewCompat.setAccessibilityDelegate( cvcNumberEditText, object : AccessibilityDelegateCompat() { @@ -675,19 +700,19 @@ class CardInputWidget @JvmOverloads constructor( updateSpaceSizes(isCardViewed = true) val slideCardLeftAnimation = CardNumberSlideLeftAnimation( - view = cardNumberEditText + view = cardNumberTextInputLayout ) val dateDestination = placementParameters.getDateLeftMargin(isFullCard = true) val slideDateLeftAnimation = ExpiryDateSlideLeftAnimation( - view = expiryDateEditText, + view = expiryDateTextInputLayout, startPosition = dateStartPosition, destination = dateDestination ) val cvcDestination = cvcStartPosition + (dateDestination - dateStartPosition) val slideCvcLeftAnimation = CvcSlideLeftAnimation( - view = cvcNumberEditText, + view = cvcNumberTextInputLayout, startPosition = cvcStartPosition, destination = cvcDestination, newWidth = placementParameters.cvcWidth @@ -696,7 +721,7 @@ class CardInputWidget @JvmOverloads constructor( val postalCodeDestination = postalCodeStartPosition + (cvcDestination - cvcStartPosition) val slidePostalCodeLeftAnimation = if (postalCodeEnabled) { PostalCodeSlideLeftAnimation( - view = postalCodeEditText, + view = postalCodeTextInputLayout, startPosition = postalCodeStartPosition, destination = postalCodeDestination, newWidth = placementParameters.postalCodeWidth @@ -726,14 +751,14 @@ class CardInputWidget @JvmOverloads constructor( updateSpaceSizes(isCardViewed = false) val slideCardRightAnimation = CardNumberSlideRightAnimation( - view = cardNumberEditText, + view = cardNumberTextInputLayout, hiddenCardWidth = placementParameters.hiddenCardWidth, focusOnEndView = expiryDateEditText ) val dateDestination = placementParameters.getDateLeftMargin(isFullCard = false) val slideDateRightAnimation = ExpiryDateSlideRightAnimation( - view = expiryDateEditText, + view = expiryDateTextInputLayout, startMargin = dateStartMargin, destination = dateDestination ) @@ -741,7 +766,7 @@ class CardInputWidget @JvmOverloads constructor( val cvcDestination = placementParameters.getCvcLeftMargin(isFullCard = false) val cvcStartMargin = cvcDestination + (dateStartMargin - dateDestination) val slideCvcRightAnimation = CvcSlideRightAnimation( - view = cvcNumberEditText, + view = cvcNumberTextInputLayout, startMargin = cvcStartMargin, destination = cvcDestination, newWidth = placementParameters.cvcWidth @@ -751,7 +776,7 @@ class CardInputWidget @JvmOverloads constructor( val postalCodeStartMargin = postalCodeDestination + (cvcStartMargin - cvcDestination) val slidePostalCodeRightAnimation = if (postalCodeEnabled) { PostalCodeSlideRightAnimation( - view = postalCodeEditText, + view = postalCodeTextInputLayout, startMargin = postalCodeStartMargin, destination = postalCodeDestination, newWidth = placementParameters.postalCodeWidth @@ -793,7 +818,7 @@ class CardInputWidget @JvmOverloads constructor( updateSpaceSizes(cardNumberIsViewed) updateFieldLayout( - view = cardNumberEditText, + view = cardNumberTextInputLayout, width = placementParameters.cardWidth, leftMargin = if (cardNumberIsViewed) { 0 @@ -803,19 +828,19 @@ class CardInputWidget @JvmOverloads constructor( ) updateFieldLayout( - view = expiryDateEditText, + view = expiryDateTextInputLayout, width = placementParameters.dateWidth, leftMargin = placementParameters.getDateLeftMargin(cardNumberIsViewed) ) updateFieldLayout( - view = cvcNumberEditText, + view = cvcNumberTextInputLayout, width = placementParameters.cvcWidth, leftMargin = placementParameters.getCvcLeftMargin(cardNumberIsViewed) ) updateFieldLayout( - view = postalCodeEditText, + view = postalCodeTextInputLayout, width = placementParameters.postalCodeWidth, leftMargin = placementParameters.getPostalCodeLeftMargin(cardNumberIsViewed) ) diff --git a/stripe/src/main/java/com/stripe/android/view/StripeEditText.kt b/stripe/src/main/java/com/stripe/android/view/StripeEditText.kt index b4229b205bc..88a3eccd3d1 100644 --- a/stripe/src/main/java/com/stripe/android/view/StripeEditText.kt +++ b/stripe/src/main/java/com/stripe/android/view/StripeEditText.kt @@ -11,7 +11,6 @@ import android.view.inputmethod.InputConnection import android.view.inputmethod.InputConnectionWrapper import androidx.annotation.ColorInt import androidx.annotation.StringRes -import androidx.appcompat.widget.AppCompatEditText import androidx.core.content.ContextCompat import com.google.android.material.textfield.TextInputEditText import com.stripe.android.R @@ -27,7 +26,7 @@ open class StripeEditText @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = androidx.appcompat.R.attr.editTextStyle -) : AppCompatEditText(context, attrs, defStyleAttr) { +) : TextInputEditText(context, attrs, defStyleAttr) { private var afterTextChangedListener: AfterTextChangedListener? = null private var deleteEmptyListener: DeleteEmptyListener? = null