@Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE) { if (mCardMultilineWidget.getCard() != null) { ((InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE)) .hideSoftInputFromWindow(mViewStub.getWindowToken(), 0); } onActionSave(); return true; } return false; } };
private void initView(AttributeSet attrs) { setOrientation(VERTICAL); inflate(getContext(), R.layout.card_multiline_widget, this); mCardNumberEditText = findViewById(R.id.et_add_source_card_number_ml); mExpiryDateEditText = findViewById(R.id.et_add_source_expiry_ml); mCvcEditText = findViewById(R.id.et_add_source_cvc_ml); mPostalCodeEditText = findViewById(R.id.et_add_source_postal_ml); mTintColorInt = mCardNumberEditText.getHintTextColors().getDefaultColor(); checkAttributeSet(attrs); mCardNumberTextInputLayout = findViewById(R.id.tl_add_source_card_number_ml); mExpiryTextInputLayout = findViewById(R.id.tl_add_source_expiry_ml); mCvcTextInputLayout = findViewById(R.id.tl_add_source_cvc_ml); mPostalInputLayout = findViewById(R.id.tl_add_source_postal_ml); mExpiryTextInputLayout.setHint(getResources().getString(R.string.expiry_label_short)); initTextInputLayoutErrorHandlers( mCardNumberTextInputLayout, mExpiryTextInputLayout, initErrorMessages(); initFocusChangeListeners(); initDeleteEmptyListeners(); adjustViewForPostalCodeAttribute();
@Override public void onTextChanged(String text) { if (isPostalCodeMaximalLength(true, text) && mCardInputListener != null) { mCardInputListener.onPostalCodeComplete(); } mPostalCodeEditText.setShouldShowError(false); } });
@Test public void setCardNumber_whenHasNoSpaces_canCreateValidCard() { mCardMultilineWidget.setCardNumber(VALID_VISA_WITH_SPACES); mFullGroup.expiryDateEditText.append("12"); mFullGroup.expiryDateEditText.append("50"); mFullGroup.cvcEditText.append("123"); mFullGroup.postalCodeEditText.append("12345"); Card card = mCardMultilineWidget.getCard(); assertNotNull(card); assertEquals(VALID_VISA_NO_SPACES, card.getNumber()); }
@Test public void onCreate_setsCorrectHintForExpiry() { TextInputLayout shortExpiryContainer = mCardMultilineWidget .findViewById(R.id.tl_add_source_expiry_ml); TextInputLayout longExpiryContainer = mNoZipCardMultilineWidget .findViewById(R.id.tl_add_source_expiry_ml); String shortExpiryHint = mCardMultilineWidget .getResources().getString(R.string.expiry_label_short); String longExpiryHint = mCardMultilineWidget .getResources().getString(R.string.acc_label_expiry_date); assertNotNull(shortExpiryContainer.getHint()); assertEquals(shortExpiryHint, shortExpiryContainer.getHint().toString()); assertNotNull(longExpiryContainer.getHint()); assertEquals(longExpiryHint, longExpiryContainer.getHint().toString()); }
@Test public void initView_whenZipHiddenThenSetToRequired_secondRowAddsPostalCodeAndAdjustsMargin() { assertEquals(View.GONE, mNoZipGroup.postalCodeInputLayout.getVisibility()); mNoZipCardMultilineWidget.setShouldShowPostalCode(true); assertEquals(View.VISIBLE, mNoZipGroup.postalCodeInputLayout.getVisibility()); int expectedMargin = mNoZipCardMultilineWidget.getResources() .getDimensionPixelSize(R.dimen.add_card_expiry_middle_margin); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mNoZipGroup.cvcInputLayout.getLayoutParams(); assertEquals(expectedMargin, params.rightMargin); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { assertEquals(expectedMargin, params.getMarginEnd()); } }
private void initEnterListeners() { ((TextView) mCardMultilineWidget.findViewById(R.id.et_add_source_card_number_ml)) .setOnEditorActionListener(mOnEditorActionListener); ((TextView) mCardMultilineWidget.findViewById(R.id.et_add_source_expiry_ml)) .setOnEditorActionListener(mOnEditorActionListener); ((TextView) mCardMultilineWidget.findViewById(R.id.et_add_source_cvc_ml)) .setOnEditorActionListener(mOnEditorActionListener); ((TextView) mCardMultilineWidget.findViewById(R.id.et_add_source_postal_ml)) .setOnEditorActionListener(mOnEditorActionListener); }
private int getDynamicBufferInPixels() { float pixelsToAdjust = getResources() .getDimension(R.dimen.card_icon_multiline_padding_bottom); BigDecimal bigDecimal = new BigDecimal(pixelsToAdjust); BigDecimal pixels = bigDecimal.setScale(0, RoundingMode.HALF_DOWN); return pixels.intValue(); }
/** * Validates all fields and shows error messages if appropriate. * * @return {@code true} if all shown fields are valid, {@code false} otherwise */ public boolean validateAllFields() { boolean cardNumberIsValid = CardUtils.isValidCardNumber(mCardNumberEditText.getCardNumber()); boolean expiryIsValid = mExpiryDateEditText.getValidDateFields() != null && mExpiryDateEditText.isDateValid(); boolean cvcIsValid = isCvcLengthValid(); mCardNumberEditText.setShouldShowError(!cardNumberIsValid); mExpiryDateEditText.setShouldShowError(!expiryIsValid); mCvcEditText.setShouldShowError(!cvcIsValid); boolean postalCodeIsValidOrGone; if (mShouldShowPostalCode) { postalCodeIsValidOrGone = isPostalCodeMaximalLength(true, mPostalCodeEditText.getText().toString()); mPostalCodeEditText.setShouldShowError(!postalCodeIsValidOrGone); } else { postalCodeIsValidOrGone = true; } return cardNumberIsValid && expiryIsValid && cvcIsValid && postalCodeIsValidOrGone; }
@Test public void clear_whenFieldsInErrorState_clearsFieldsAndHidesErrors() { // The input date here will be invalid after 2050. Please update the test. assertTrue(Calendar.getInstance().get(Calendar.YEAR) < 2050); String badVisa = VALID_VISA_WITH_SPACES.substring(VALID_VISA_WITH_SPACES.length() - 1); badVisa += 3; // Makes this 4242 4242 4242 4243 mFullGroup.cardNumberEditText.setText(badVisa); mFullGroup.expiryDateEditText.append("01"); mFullGroup.expiryDateEditText.append("11"); mFullGroup.cvcEditText.append("12"); mFullGroup.postalCodeEditText.append("1234"); mCardMultilineWidget.validateAllFields(); assertTrue(mFullGroup.cardNumberEditText.getShouldShowError()); assertTrue(mFullGroup.expiryDateEditText.getShouldShowError()); assertTrue(mFullGroup.cvcEditText.getShouldShowError()); assertTrue(mFullGroup.postalCodeEditText.getShouldShowError()); mCardMultilineWidget.clear(); assertFalse(mFullGroup.cardNumberEditText.getShouldShowError()); assertFalse(mFullGroup.expiryDateEditText.getShouldShowError()); assertFalse(mFullGroup.cvcEditText.getShouldShowError()); assertFalse(mFullGroup.postalCodeEditText.getShouldShowError()); }
@Test public void setEnabled_setsEnabledPropertyOnAllChildWidgets() { assertTrue(mCardMultilineWidget.isEnabled()); assertTrue(mFullGroup.cardInputLayout.isEnabled()); assertTrue(mFullGroup.expiryInputLayout.isEnabled()); assertTrue(mFullGroup.postalCodeEditText.isEnabled()); mCardMultilineWidget.setEnabled(false); assertFalse(mCardMultilineWidget.isEnabled()); assertFalse(mFullGroup.cardInputLayout.isEnabled()); assertFalse(mFullGroup.expiryInputLayout.isEnabled()); assertFalse(mFullGroup.postalCodeEditText.isEnabled()); mCardMultilineWidget.setEnabled(true); assertTrue(mCardMultilineWidget.isEnabled()); assertTrue(mFullGroup.cardInputLayout.isEnabled()); assertTrue(mFullGroup.expiryInputLayout.isEnabled());
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mViewStub.setLayoutResource(R.layout.activity_add_source); mViewStub.inflate(); mCardMultilineWidget = findViewById(R.id.add_source_card_entry_widget); initEnterListeners(); mErrorLayout = findViewById(R.id.add_source_error_container); boolean showZip = getIntent().getBooleanExtra(EXTRA_SHOW_ZIP, false); mUpdatesCustomer = getIntent().getBooleanExtra(EXTRA_UPDATE_CUSTOMER, false); mStartedFromPaymentSession = getIntent().getBooleanExtra(EXTRA_PAYMENT_SESSION_ACTIVE, true); mCardMultilineWidget.setShouldShowPostalCode(showZip); if (mUpdatesCustomer && !getIntent().getBooleanExtra(EXTRA_PROXY_DELAY, false)) { initCustomerSessionTokens(); } setTitle(R.string.title_add_a_card); }
@Override protected void setCommunicatingProgress(boolean communicating) { super.setCommunicatingProgress(communicating); if (mCardMultilineWidget != null) { mCardMultilineWidget.setEnabled(!communicating); } }
/** * Gets a {@link Card} object from the user input, if all fields are valid. If not, returns * {@code null}. * * @return a valid {@link Card} object based on user input, or {@code null} if any field is * invalid */ @Nullable public Card getCard() { if (validateAllFields()) { String cardNumber = mCardNumberEditText.getCardNumber(); int[] cardDate = mExpiryDateEditText.getValidDateFields(); String cvcValue = mCvcEditText.getText().toString(); Card card = new Card(cardNumber, cardDate[0], cardDate[1], cvcValue); if (mShouldShowPostalCode) { card.setAddressZip(mPostalCodeEditText.getText().toString()); } return card.addLoggingToken(CARD_MULTILINE_TOKEN); } return null; }
public void setShouldShowPostalCode(boolean shouldShowPostalCode) { mShouldShowPostalCode = shouldShowPostalCode; adjustViewForPostalCodeAttribute(); }
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTheme(R.style.StripeDefaultTheme); mCardInputWidget = new CardInputWidget(this); mCardMultilineWidget = new CardMultilineWidget(this, true); mNoZipCardMulitlineWidget = new CardMultilineWidget(this, false); mMaskedCardView = new MaskedCardView(this); LinearLayout linearLayout = new LinearLayout(this); linearLayout.addView(mCardInputWidget); linearLayout.addView(mCardMultilineWidget); linearLayout.addView(mNoZipCardMulitlineWidget); linearLayout.addView(mMaskedCardView); setContentView(linearLayout); }
@Test public void setCardNumber_whenHasSpaces_canCreateValidCard() { mCardMultilineWidget.setCardNumber(VALID_VISA_NO_SPACES); mFullGroup.expiryDateEditText.append("12"); mFullGroup.expiryDateEditText.append("50"); mFullGroup.cvcEditText.append("123"); mFullGroup.postalCodeEditText.append("12345"); Card card = mCardMultilineWidget.getCard(); assertNotNull(card); assertEquals(VALID_VISA_NO_SPACES, card.getNumber()); }
WidgetControlGroup(@NonNull CardMultilineWidget parentWidget) { cardNumberEditText = parentWidget.findViewById(R.id.et_add_source_card_number_ml); cardInputLayout = parentWidget.findViewById(R.id.tl_add_source_card_number_ml); expiryDateEditText = parentWidget.findViewById(R.id.et_add_source_expiry_ml); expiryInputLayout = parentWidget.findViewById(R.id.tl_add_source_expiry_ml); cvcEditText = parentWidget.findViewById(R.id.et_add_source_cvc_ml); cvcInputLayout = parentWidget.findViewById(R.id.tl_add_source_cvc_ml); postalCodeEditText = parentWidget.findViewById(R.id.et_add_source_postal_ml); postalCodeInputLayout = parentWidget.findViewById(R.id.tl_add_source_postal_ml); secondRowLayout = parentWidget.findViewById(R.id.second_row_layout); } }
private void updateCvc(@NonNull @Card.CardBrand String brand) { if (Card.AMERICAN_EXPRESS.equals(brand)) { mCvcEditText.setFilters( new InputFilter[]{ new InputFilter.LengthFilter(Card.CVC_LENGTH_AMERICAN_EXPRESS) }); mCvcTextInputLayout.setHint(getResources().getString(R.string.cvc_amex_hint)); } else { mCvcEditText.setFilters( new InputFilter[]{ new InputFilter.LengthFilter(Card.CVC_LENGTH_COMMON)}); mCvcTextInputLayout.setHint(getResources().getString(R.string.cvc_number_hint)); } }
@Test public void initView_whenZipRequiredThenSetToHidden_secondRowLosesPostalCodeAndAdjustsMargin() { assertEquals(View.VISIBLE, mFullGroup.postalCodeInputLayout.getVisibility()); mCardMultilineWidget.setShouldShowPostalCode(false); assertEquals(View.GONE, mFullGroup.postalCodeInputLayout.getVisibility()); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mFullGroup.cvcInputLayout.getLayoutParams(); assertEquals(0, params.rightMargin); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { assertEquals(0, params.getMarginEnd()); } }