All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.tinkoff.acquiring.sdk.views.EditCardView Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2016 Tinkoff Bank
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ru.tinkoff.acquiring.sdk.views;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.CharacterStyle;
import android.text.style.UpdateAppearance;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.EditText;

import org.w3c.dom.Text;

import ru.tinkoff.acquiring.sdk.R;
import ru.tinkoff.acquiring.sdk.utils.CardValidator;

/**
 * @author a.shishkin1
 */
public class EditCardView extends ViewGroup {


    private static final int CARD_SYSTEM_LOGO = 1;
    private static final int FULL_CARD_NUMBER = 1 << 1;
    private static final int SCAN_CARD_BUTTON = 1 << 2;
    private static final int CHANGE_MODE_BUTTON = 1 << 3;
    private static final int IN_ANIMATION = 1 << 4;
    private static final int ONLY_NUMBER_STATE = 1 << 5;
    private static final int SAVED_CARD_STATE = 1 << 6;

    private Runnable update;
    private int flags;

    private static final String DATE_MASK = "\u2022\u2022/\u2022\u2022";

    private int textColor;

    private String cardHint = null;

    private CardValidator cardValidator;

    private CardNumberEditText etCardNumber;
    private EditText etDate;
    private EditText etCvc;
    private Bitmap cardSystemLogo;
    private Paint cardSystemLogoPaint;
    private Paint paint;

    private int additionalPadding;

    private SimpleButton btnChangeMode;
    private SimpleButton btnScanCard;

    private float cardSystemLogoAnimationFactor = 1f;

    private CardFormatter cardFormatter;

    private boolean buttonsAvailable = true;

    private boolean forbidTextWatcher = false;

    private int scanResId = 0;
    private int nextResId = 0;


    public EditCardView(Context context) {
        super(context);
        init(null);
    }

    public EditCardView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public EditCardView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    public String getCardNumber() {
        return cardFormatter.getNormalizedNumber(etCardNumber.getText().toString(), " ");
    }

    public String getCvc() {
        return etCvc.getText().toString();
    }

    public String getExpireDate() {
        return etDate.getText().toString();
    }

    public boolean isFilledAndCorrect() {
        if (check(SAVED_CARD_STATE)) {
            return cardValidator.validateSecurityCode(etCvc.getText().toString());
        }

        boolean cardNumberReady = cardValidator.validateNumber(getCardNumber());
        if (!cardNumberReady)
            return false;
        return check(ONLY_NUMBER_STATE) ||
                (cardValidator.validateExpirationDate(etDate.getText().toString()) && cardValidator.validateSecurityCode(etCvc.getText().toString()));
    }

    private void init(AttributeSet attributeSet) {

        setAddStatesFromChildren(true);

        additionalPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());
        btnChangeMode = new SimpleButton(null);
        btnScanCard = new SimpleButton(null);

        flags = 0;
        Context context = getContext();
        cardValidator = new CardValidator();

        if (attributeSet == null) {
            etCardNumber = new CardNumberEditText(context);
            etDate = new EditText(context);
            etCvc = new EditText(context);
        } else {
            etCardNumber = new CardNumberEditText(context, attributeSet);
            etDate = new EditText(context, attributeSet);
            etCvc = new EditText(context, attributeSet);
        }

        textColor = etCardNumber.getCurrentTextColor();

        applyBehaviour(etCardNumber, etCvc, etDate);

        etCvc.setInputType(etCvc.getInputType() | InputType.TYPE_NUMBER_VARIATION_PASSWORD);

        update = new Runnable() {
            @Override
            public void run() {
                String number = etCardNumber.getText().toString();
                boolean isCorrect = cardValidator.validateNumber(cardFormatter.getNormalizedNumber(number, " ")) || check(SAVED_CARD_STATE);
                if (!isCorrect && check(CHANGE_MODE_BUTTON)) {
                    hideChangeModeButton();
                }
                if (isCorrect && !cardFormatter.isLimited() && !check(SAVED_CARD_STATE)) {
                    showChangeModeButton();
                }
                etCardNumber.setTextColor(cardFormatter.isNeedToCheck(etCardNumber.length()) && !isCorrect ? Color.RED : textColor);
                etDate.setTextColor(etDate.length() == 5 && !cardValidator.validateExpirationDate(etDate.getText().toString()) && !check(SAVED_CARD_STATE) ? Color.RED : textColor);
                etCvc.setTextColor(etCvc.length() == 3 && !cardValidator.validateSecurityCode(etCvc.getText().toString()) ? Color.RED : textColor);
                actions.onUpdate(EditCardView.this);
            }
        };

        etDate.setGravity(Gravity.CENTER);
        etCvc.setGravity(Gravity.CENTER);
        etCardNumber.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);

        addView(etCardNumber);
        addView(etCvc);
        addView(etDate);
        etCardNumber.addTextChangedListener(new TextWatcher() {

            int[] sel = new int[2];

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (forbidTextWatcher) {
                    return;
                }

                String number = etCardNumber.getText().toString();

                if (!TextUtils.isEmpty(number)) {
                    etCardNumber.removeTextChangedListener(this);
                    char firstChar = number.charAt(0);
                    if (firstChar == '2' || firstChar == '4' || firstChar == '5') {
                        cardFormatter.setType(CardFormatter.DEFAULT); // master card and visa xxxx xxxx xxxx xxxx
                    } else if (firstChar == '6') {
                        cardFormatter.setType(CardFormatter.MAESTRO); // maestro xxxxxxxx xxxx...x
                    } else {
                        cardFormatter.setType(CardFormatter.UNKNOWN);
                    }

                    etCardNumber.setFilters(new InputFilter[]{new InputFilter.LengthFilter(cardFormatter.getMaxLength())});

                    String formatted = cardFormatter.format(number, " ");
                    populateCardNumber(formatted, before > count);
                    etCardNumber.addTextChangedListener(this);
                    String normalizedNumber = cardFormatter.getNormalizedNumber(number, " ");
                    if (!check(IN_ANIMATION)) {
                        boolean isFullCardMode = check(FULL_CARD_NUMBER);
                        boolean isLimited = cardFormatter.isLimited();

                        if (isFullCardMode) {
                            boolean isInReadyDataMode = check(SAVED_CARD_STATE);
                            if (((isLimited && cardValidator.validateNumber(normalizedNumber)) || isInReadyDataMode) && !check(ONLY_NUMBER_STATE)) {
                                showCvcAndDate();
                            }

                            if (check(SCAN_CARD_BUTTON) && normalizedNumber.length() > 15) {
                                hideScanButton();
                            }

                            if (!check(SCAN_CARD_BUTTON) && normalizedNumber.length() <= 15) {
                                showScanButton();
                            }
                        }
                    }

                }

                boolean noCardLogoCondition = number == null || number.length() == 0;

                if (noCardLogoCondition && check(CARD_SYSTEM_LOGO)) {
                    hideCardSystemLogo();
                    return;
                }

                if (!noCardLogoCondition && !check(CARD_SYSTEM_LOGO)) {
                    showCardSystemLogo();
                    return;
                }
                update.run();
            }

            private void populateCardNumber(String formattedCardNumber, boolean delete) {
                sel[0] = etCardNumber.getSelectionStart();
                sel[1] = etCardNumber.getSelectionEnd();
                int correction = 0;
                String text = etCardNumber.getText().toString();

                correction += countMatchesBeforeIndex(formattedCardNumber, " ", sel[0]) - countMatchesBeforeIndex(text, " ", sel[0]);

                etCardNumber.setText(formattedCardNumber);

                int pos = Math.max(sel[0] + correction, 0);

                etCardNumber.setSelection(Math.min(formattedCardNumber.length(), pos));
            }

            public int countMatchesBeforeIndex(String str, String sub, int border) {
                if (TextUtils.isEmpty(str) || TextUtils.isEmpty(sub)) {
                    return 0;
                }
                int count = 0;
                int idx = 0;
                while ((idx = str.indexOf(sub, idx)) != -1 && idx < border) {
                    count++;
                    idx += sub.length();
                }
                return count;
            }


            @Override
            public void afterTextChanged(Editable s) {

            }
        });
        etCardNumber.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN && !check(FULL_CARD_NUMBER))
                    return true;
                if (event.getAction() == MotionEvent.ACTION_UP && !check(IN_ANIMATION) && !check(FULL_CARD_NUMBER) && !check(SAVED_CARD_STATE)) {
                    hideCvcAndDate();
                }
                return false;
            }
        });
        etCardNumber.setCustomOnFocusChangedListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus && !check(IN_ANIMATION) && !check(FULL_CARD_NUMBER) && !check(SAVED_CARD_STATE)) {
                    hideCvcAndDate();
                }
            }
        });
        etDate.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence string, int start, int before, int count) {
                if (forbidTextWatcher) {
                    return;
                }

                if (check(SAVED_CARD_STATE)) {
                    return;
                }
                etDate.removeTextChangedListener(this);

                String result = string.toString().replace("/", "");
                if (count != 0 && string.length() > 1) {      // set delimiter before next sign
                    result = result.substring(0, 2) + "/" + result.substring(2);
                    etDate.setText(result);
                    etDate.setSelection((start + count + 1 > result.length()) ? result.length() : (start + count + 1));
                } else {
                    if (start == 2) {
                        etDate.setText(result.substring(0, 1));
                        etDate.setSelection(etDate.length());
                    } else {
                        etDate.setSelection(start + count);
                    }
                }
                update.run();
                if (cardValidator.validateExpirationDate(etDate.getText().toString())) {
                    dispatchFocus();
                }
                etDate.addTextChangedListener(this);
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
        etCvc.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (forbidTextWatcher) {
                    return;
                }
                update.run();
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
        showScanButton();
        setMode(true);
        etCardNumber.requestFocus();
        etCardNumber.setHint("");
        etDate.setHint("");
        etCvc.setHint("");

        etDate.setFilters(new InputFilter[]{new InputFilter.LengthFilter(5)});
        etCvc.setFilters(new InputFilter[]{new InputFilter.LengthFilter(3)});

        cardSystemLogoPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        cardFormatter = new CardFormatter();

        setBtnScanIcon(R.drawable.acq_scan_grey);
        setChangeModeIcon(R.drawable.acq_next_grey);

        if (attributeSet != null) {
            TypedArray a = context.getTheme().obtainStyledAttributes(
                    attributeSet,
                    R.styleable.EditCardView,
                    0, 0);

            try {
                etCardNumber.setHint(a.getString(R.styleable.EditCardView_numberHint));
                etDate.setHint(a.getString(R.styleable.EditCardView_dateHint));
                etCvc.setHint(a.getString(R.styleable.EditCardView_cvcHint));
            } finally {
                a.recycle();
            }
        }
    }



    public void dispatchFocus() {
        if (check(SAVED_CARD_STATE)) {
            activate(etCvc);
        } else {
            if (check(FULL_CARD_NUMBER)) {
                activate(etCardNumber);
            } else if (etDate.length() == 5) {
                activate(etCvc);
            } else {
                activate(etDate);
            }
        }
    }

    protected EditText onCreateField(Context context, AttributeSet attributeSet) {
        return  (attributeSet == null) ? new EditText(context) : new EditText(context, attributeSet);
    }

    public void setFullCardNumberModeEnable(boolean enable) {
        if (enable) {
            flags &= ~ONLY_NUMBER_STATE;
        } else {
            flags |= ONLY_NUMBER_STATE;
        }
        requestLayout();
        invalidate();
    }




    public void setSavedCardState(boolean savedCardState) {

        if ( check(SAVED_CARD_STATE) == savedCardState ) {
            normalizeMode();
            return;
        }

        if (savedCardState) {
            setMode(false);
            hideChangeModeButton();
            hideScanButton();
            flags |= SAVED_CARD_STATE;
            etDate.setText(DATE_MASK);
            etCvc.setText("");
            etDate.setEnabled(false);
            etCardNumber.setEnabled(false);
        } else {
            setMode(true);
            showScanButton();
            flags &= ~SAVED_CARD_STATE;
            etDate.setEnabled(true);
            etCardNumber.setEnabled(true);
            etCardNumber.setMode(CardNumberEditText.FULL_MODE);
            etCardNumber.setText("");
            etCvc.setText("");
            etDate.setText("");
        }

        requestLayout();
    }


    protected void applyBehaviour(EditText... fields) {
        int id = 1;
        for (EditText et : fields) {
            et.setId(id); // dirty avoid id duplication
            id++;
            et.setSingleLine(true);
            et.setPadding(0, 0, 0, 0);
            if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
                et.setBackgroundDrawable(null);
            } else {
                et.setBackground(null);
            }
            et.setInputType(InputType.TYPE_CLASS_NUMBER);
        }
    }


    public void setHints(String cardHint, String dateHint, String cvcHint) {
        etCardNumber.setHint(cardHint);
        etDate.setHint(dateHint);
        etCvc.setHint(cvcHint);
    }

    public void setCardNumber(String number) {
        if (number == null || number.length() == 0) {
            flags &= ~CARD_SYSTEM_LOGO;
        } else {
            flags |= CARD_SYSTEM_LOGO;
            cardSystemLogo = cardSystemIconsHolder == null ? null : cardSystemIconsHolder.getCardSystemBitmap(number);
        }
        String old = getCardNumber();
        if (old != null && old.length() > 0 && !old.equals(number)) {
            etCvc.setText("");
        }

        etCardNumber.setText(number);
        if (check(SAVED_CARD_STATE)) {
            etCardNumber.setMode(CardNumberEditText.SHORT_MODE);
        }
        requestLayout();
        dispatchFocus();
    }

    public void setExpireDate(String date) {
        etDate.setText(date);
        dispatchFocus();
    }

    public void clear() {
        setMode(true);
        etCardNumber.setMode(CardNumberEditText.FULL_MODE);
        etCardNumber.setText("");
        etDate.setText("");
        etCvc.setText("");
        dispatchFocus();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        if (cardSystemIconsHolder != null && cardSystemLogo == null && check(CARD_SYSTEM_LOGO)) {
            cardSystemLogo = cardSystemIconsHolder.getCardSystemBitmap(etCardNumber.getText().toString());
        }

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        boolean isFullCardNumberMode = check(FULL_CARD_NUMBER);

        int logoWidth = check(CARD_SYSTEM_LOGO) ? calculateCardLogoWidth() : 0;

        int additionalRightSpace = 0;

        if (check(SCAN_CARD_BUTTON)) {
            additionalRightSpace += calculateScanButtonWidth();
        }
        if (check(CHANGE_MODE_BUTTON)) {
            additionalRightSpace += calculateChangeModeWidth();
        }

        int accessWidth = widthSize - logoWidth - additionalRightSpace - (check(CARD_SYSTEM_LOGO) ? additionalPadding : 0) - (getPaddingRight() + getPaddingLeft());

        int contentsWidth = accessWidth / 3;

        int contentWidthSpec = MeasureSpec.makeMeasureSpec(contentsWidth, MeasureSpec.EXACTLY);
        int contentHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

        etCardNumber.measure(isFullCardNumberMode ? MeasureSpec.makeMeasureSpec(accessWidth , MeasureSpec.EXACTLY) : contentWidthSpec, contentHeightSpec);
        etDate.measure(contentWidthSpec, contentHeightSpec);
        etCvc.measure(contentWidthSpec, contentHeightSpec);
        int btnsHeight = Math.max(calculateChangeModeHeight(), calculateScanButtonHeight());
        int iconsHeight = Math.max(cardSystemLogo == null ? 0 : cardSystemLogo.getHeight(), btnsHeight);
        int fieldHeight = Math.max(etCardNumber.getMeasuredHeight(), Math.max(etCvc.getMeasuredHeight(), etDate.getMeasuredHeight()));
        int height = Math.max(iconsHeight, fieldHeight);
        height += (getPaddingTop() + getPaddingBottom());
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            height = Math.min(heightSize, height);
        }

        setMeasuredDimension(widthSize, height);

    }

    private int calculateCardLogoWidth() {
        return cardSystemLogo == null ? 0 : (int) (cardSystemLogo.getWidth() * cardSystemLogoAnimationFactor);
    }

    private int calculateScanButtonWidth() {
        return btnScanCard.getWidth();
    }

    private int calculateChangeModeWidth() {
        return btnChangeMode.getWidth();
    }

    private int calculateScanButtonHeight() {
        return btnScanCard.getHeight();
    }

    private int calculateChangeModeHeight() {
        return btnChangeMode.getHeight();
    }

    private boolean check(int flags) {
        return (this.flags & flags) == flags;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        int logoWidth = check(CARD_SYSTEM_LOGO) ? calculateCardLogoWidth() : 0;
        int t;
        int l = getPaddingLeft();
        int startLabels = logoWidth + l + (check(CARD_SYSTEM_LOGO) ? additionalPadding : 0);
        int w = getWidth() - getPaddingRight() - getPaddingLeft();
        int hh = (getHeight() - getPaddingTop() - getPaddingBottom()) >> 1;

        int additionalRightSpace = 0;
        if (check(SCAN_CARD_BUTTON)) {
            additionalRightSpace += calculateScanButtonWidth();
            btnScanCard.layoutIn(w - additionalRightSpace + (btnScanCard.getWidth() >> 1) + getPaddingLeft(), hh + getPaddingTop());
        }
        if (check(CHANGE_MODE_BUTTON)) {
            additionalRightSpace += calculateChangeModeWidth();
            btnChangeMode.layoutIn(w - additionalRightSpace + (btnChangeMode.getWidth() >> 1) + getPaddingLeft(), hh + getPaddingTop());
        }
        int startLabel;
        int endLabel;
        t = (getHeight() - etCardNumber.getMeasuredHeight()) >> 1;
        startLabel = startLabels;
        endLabel = startLabel + etCardNumber.getMeasuredWidth();
        etCardNumber.layout(startLabel, t, endLabel, t + etCardNumber.getMeasuredHeight());

        t = (getHeight() - etDate.getMeasuredHeight()) >> 1;
        startLabel = endLabel;
        endLabel = startLabel + etDate.getMeasuredWidth();
        etDate.layout(startLabel, t, endLabel, t + etDate.getMeasuredHeight());
        t = (getHeight() - etCvc.getMeasuredHeight()) >> 1;
        startLabel = endLabel;
        endLabel = startLabel + etCvc.getMeasuredWidth();
        etCvc.layout(startLabel, t, endLabel, t + etCvc.getMeasuredHeight());
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (check(CARD_SYSTEM_LOGO) && cardSystemLogo != null) {
            int yOffset = (getHeight() - cardSystemLogo.getHeight()) >> 1;
            int xOffset = additionalPadding >> 1;
            canvas.drawBitmap(cardSystemLogo, xOffset, yOffset, cardSystemLogoPaint);
        }
        if (check(CHANGE_MODE_BUTTON)) {
            btnChangeMode.drawWithPaint(canvas, paint);
        }
        if (check(SCAN_CARD_BUTTON)) {
            btnScanCard.drawWithPaint(canvas, paint);
        }

        super.dispatchDraw(canvas);

    }



    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            if (check(CHANGE_MODE_BUTTON) && btnChangeMode.handleAction(event) && buttonsAvailable) {
                showCvcAndDate();
                return true;
            }
            if (check(SCAN_CARD_BUTTON) && btnScanCard.handleAction(event) && buttonsAvailable) {
                actions.onPressScanCard(this);
                return true;
            }
        }
        return super.onTouchEvent(event);
    }


    public void setMode(boolean isFullNumber) {
        if (isFullNumber) {
            flags |= FULL_CARD_NUMBER;
        } else {
            flags &= ~FULL_CARD_NUMBER;
        }
        normalizeMode();
    }

    private void normalizeMode() {
        if (check(FULL_CARD_NUMBER)) {
            etCvc.setVisibility(GONE);
            etDate.setVisibility(GONE);
        } else {
            etCvc.setVisibility(VISIBLE);
            etDate.setVisibility(VISIBLE);
        }
        requestLayout();
    }


    private void showCardSystemLogo() {
        cardSystemLogo = cardSystemIconsHolder.getCardSystemBitmap(etCardNumber.getText().toString());
        flags |= CARD_SYSTEM_LOGO;
        cardSystemLogoPaint.setAlpha(0);

        ObjectAnimator animatorAlpha = ObjectAnimator.ofInt(cardSystemLogoPaint, "alpha", 0, 255);
        animatorAlpha.setDuration(150);
        animatorAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                invalidate();
            }
        });
        ObjectAnimator animatorEditField = ObjectAnimator.ofFloat(this, "cardSystemLogoAnimationFactor", 0f, 1f);
        animatorEditField.setDuration(150);
        animatorEditField.setInterpolator(new OvershootInterpolator());
        AnimatorSet set = new AnimatorSet();
        set.playSequentially(animatorEditField, animatorAlpha);
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                requestLayout();
                invalidate();
            }
        });
        set.start();
    }

    private void hideCardSystemLogo() {
        ObjectAnimator animatorAlpha = ObjectAnimator.ofInt(cardSystemLogoPaint, "alpha", 255, 0);
        animatorAlpha.setDuration(150);
        animatorAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                invalidate();
            }
        });
        ObjectAnimator animatorEditField = ObjectAnimator.ofFloat(this, "cardSystemLogoAnimationFactor", 1f, 0f);
        animatorEditField.setDuration(150);
        animatorEditField.setInterpolator(new OvershootInterpolator());
        AnimatorSet set = new AnimatorSet();
        set.playSequentially(animatorAlpha, animatorEditField);
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                flags &= ~CARD_SYSTEM_LOGO;
                requestLayout();
                invalidate();
            }
        });
        set.start();
    }


    private void showCvcAndDate() {
        hideChangeModeButton();
        final MutableColorSpan span = new MutableColorSpan(etCardNumber.getPaint().getColor());

        etCardNumber.getText().setSpan(span, 0, Math.max(etCardNumber.length() - 4, 0), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        ObjectAnimator animatorText = ObjectAnimator.ofInt(span, "alpha", 255, 0);
        animatorText.setDuration(200);
        animatorText.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                if (etCardNumber.getMode() == CardNumberEditText.FULL_MODE) {
                    etCardNumber.getText().setSpan(span, 0, Math.max(etCardNumber.length() - 4, 0), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            }
        });
        ObjectAnimator toLeftAnimator = ObjectAnimator.ofFloat(etCardNumber, "animationFactor", 0f, 1f);
        toLeftAnimator.setStartDelay(140);
        toLeftAnimator.setDuration(210);
        toLeftAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                etCardNumber.setMode(CardNumberEditText.SHORT_MODE);
            }
        });

        etDate.setVisibility(VISIBLE);
        etDate.setAlpha(0f);
        ObjectAnimator animatorDate = ObjectAnimator.ofFloat(etDate, "alpha", 0f, 1f);
        animatorDate.setDuration(200);
        animatorDate.setStartDelay(200);

        etCvc.setVisibility(VISIBLE);
        etCvc.setAlpha(0f);
        ObjectAnimator animatorCvc = ObjectAnimator.ofFloat(etCvc, "alpha", 0f, 1f);
        animatorCvc.setDuration(200);
        animatorCvc.setStartDelay(280);

        AnimatorSet set = new AnimatorSet();
        set.playTogether(animatorText, toLeftAnimator, animatorCvc, animatorDate);
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                setMode(false);
                flags &= ~IN_ANIMATION;
                dispatchFocus();
            }
        });
        flags |= IN_ANIMATION;
        set.start();

    }

    private void hideCvcAndDate() {
        final MutableColorSpan span = new MutableColorSpan(etCardNumber.getPaint().getColor());
        span.setAlpha(0);
        ObjectAnimator animatorText = ObjectAnimator.ofInt(span, "alpha", 0, 255);
        animatorText.setDuration(250);
        animatorText.setInterpolator(new AccelerateInterpolator());
        animatorText.setStartDelay(200);
        animatorText.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                if (etCardNumber.getMode() == CardNumberEditText.FULL_MODE) {
                    etCardNumber.getText().setSpan(span, 0, etCardNumber.length() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }

            }
        });
        ObjectAnimator toRightAnimator = ObjectAnimator.ofFloat(etCardNumber, "animationFactor", 1f, 0f);
        toRightAnimator.setDuration(200);

        ObjectAnimator animatorDate = ObjectAnimator.ofFloat(etDate, "alpha", 1f, 0f);
        animatorDate.setDuration(150);
        animatorDate.setStartDelay(80);
        animatorDate.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                etCardNumber.setMode(CardNumberEditText.FULL_MODE);
                etCardNumber.setSelection(etCardNumber.length());
                etCardNumber.getText().setSpan(span, 0, etCardNumber.length() - 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                setMode(true);
            }
        });

        ObjectAnimator animatorCvc = ObjectAnimator.ofFloat(etCvc, "alpha", 1f, 0f);
        animatorCvc.setDuration(150);

        AnimatorSet set = new AnimatorSet();
        set.play(animatorCvc).with(animatorDate).before(toRightAnimator).before(animatorText);
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                flags &= ~IN_ANIMATION;
                activate(etCardNumber);
                showChangeModeButton();
            }
        });
        flags |= IN_ANIMATION;

        set.start();
    }

    private void showChangeModeButton() {
        flags |= CHANGE_MODE_BUTTON;
        requestLayout();
    }


    private void hideChangeModeButton() {
        flags &= ~CHANGE_MODE_BUTTON;
        requestLayout();
    }

    private void showScanButton() {
        flags |= SCAN_CARD_BUTTON;
        requestLayout();
    }


    private void hideScanButton() {
        flags &= ~SCAN_CARD_BUTTON;
        requestLayout();
    }

    private void activate(final EditText et) {
        et.requestFocus();
        et.post(new Runnable() {
            @Override
            public void run() {
                et.setSelection(et.length());
            }
        });
    }

    public void setCardHint(String cardHint) {
        this.cardHint = cardHint;
        etCardNumber.setHint(cardHint);
    }

    public void setCardSystemLogoAnimationFactor(float cardSystemLogoAnimationFactor) {
        this.cardSystemLogoAnimationFactor = cardSystemLogoAnimationFactor;
        requestLayout();
        invalidate();
    }

    public float getCardSystemLogoAnimationFactor() {
        return cardSystemLogoAnimationFactor;
    }

    private CardSystemIconsHolder cardSystemIconsHolder;

    public void setCardSystemIconsHolder(CardSystemIconsHolder cardSystemIconsHolder) {
        this.cardSystemIconsHolder = cardSystemIconsHolder;
        requestLayout();
    }

    public void setBtnScanIcon(int resId) {
        scanResId = resId;
        btnScanCard.setBitmap(BitmapFactory.decodeResource(getResources(), resId));
        requestLayout();
    }

    public void setChangeModeIcon(int resId) {
        nextResId = resId;
        btnChangeMode.setBitmap(BitmapFactory.decodeResource(getResources(), resId));
        requestLayout();
    }

    public interface CardSystemIconsHolder {
        Bitmap getCardSystemBitmap(String cardNumber);
    }


    public class CardFormatter {

        public static final int UNKNOWN = 0;
        public static final int DEFAULT = 1;
        public static final int MAESTRO = 2;

        private int type;
        private int maxLength;
        private int[] defaultRangers = new int[]{19};
        private int[] maestroRangers = new int[]{14, 15, 16, 17, 18, 19, 20};
        private int[] unknownRangers = new int[]{Integer.MAX_VALUE - 3};

        public String format(String input, CharSequence delimiter) {
            return doFormat(getNormalizedNumber(input, delimiter), delimiter);
        }

        public void setType(int type) {
            this.type = type;
            maxLength = 0;
            for (int i : getValidationRanges()) {
                if (i > maxLength) {
                    maxLength = i;
                }
            }
        }

        public boolean isLimited() {
            return type == DEFAULT;
        }

        public boolean isNeedToCheck(int digits) {
            int[] ranges = getValidationRanges();
            for (int range : ranges) {
                if (range == digits) {
                    return true;
                }
            }

            return false;
        }

        public int getMaxLength() {
            return maxLength;
        }

        public int[] getValidationRanges() {
            if (type == DEFAULT)
                return defaultRangers;
            if (type == MAESTRO)
                return maestroRangers;
            return unknownRangers;
        }

        private String getNormalizedNumber(String string, CharSequence delimiter) {
            return string.replace(delimiter, "");
        }

        protected String doFormat(String cardNumber, CharSequence delimiter) {
            if (type == DEFAULT) {
                char[] chars = cardNumber.toCharArray();
                StringBuilder cardNumberBuilder = new StringBuilder(cardNumber);

                for (int i = 1, index = 0; i < chars.length; i++) {
                    if (i % 4 == 0) {
                        cardNumberBuilder.insert(i + index, delimiter);
                        index++;
                    }
                }

                return cardNumberBuilder.toString().trim();
            }
            if (type == MAESTRO) {
                int length = cardNumber.length();

                if (length < 8)
                    return cardNumber;

                StringBuilder cardNumberBuilder = new StringBuilder(cardNumber);
                cardNumberBuilder.insert(8, delimiter);
                return cardNumberBuilder.toString().trim();
            }
            if (type == UNKNOWN) {
                return cardNumber;
            }

            return null;
        }
    }

    private Actions actions = NO_ACTIONS;


    private static Actions NO_ACTIONS = new Actions() {

        @Override
        public void onUpdate(EditCardView ecv) {

        }

        @Override
        public void onPressScanCard(EditCardView ecv) {

        }
    };

    public void setActions(Actions actions) {
        if (actions != null) {
            this.actions = actions;
        } else {
            this.actions = NO_ACTIONS;
        }
    }

    public interface Actions {
        void onUpdate(EditCardView editCardView);

        void onPressScanCard(EditCardView editCardView);
    }

    private class SimpleButton {
        private Rect rect;
        private Bitmap bitmap;
        private boolean isVisible;

        public SimpleButton(Bitmap bitmap) {
            this.bitmap = bitmap;
            this.rect = new Rect();
            this.isVisible = true;
            if (bitmap != null) {
                layoutIn(bitmap.getWidth() >> 1, bitmap.getHeight() >> 1);
            }
        }

        private boolean handleAction(MotionEvent ev) {
            int x = (int) ev.getX();
            return x > rect.left && x < rect.right;
        }

        protected void layoutIn(int centerX, int centerY) {
            if (bitmap != null) {
                int hx = bitmap.getWidth() >> 1;
                int hy = bitmap.getHeight() >> 1;
                rect.set(centerX - hx, centerY - hy, centerX + hx, centerY + hy);
            } else {
                rect.set(centerX, centerY, centerX, centerY);
            }

        }

        public void setBitmap(Bitmap bitmap) {
            this.bitmap = bitmap;
            layoutIn(rect.centerX(), rect.centerY());
        }

        protected void drawWithPaint(Canvas canvas, Paint paint) {
            if (bitmap == null || !isVisible)
                return;
            canvas.drawBitmap(bitmap, null, rect, paint);
        }

        public int getWidth() {
            return rect.width();
        }

        public int getHeight() {
            return rect.height();
        }

        public boolean isVisible() {
            return isVisible;
        }

        public void setVisibility(boolean visible) {
            this.isVisible = visible;
        }
    }


    public static class CardNumberEditText extends EditText {


        public static final int FULL_MODE = 0;
        public static final int SHORT_MODE = 1;

        private int charsCount = 4;
        private int mode;
        private float animationFactor = 0f;


        private OnFocusChangeListener customOnFocusChangedListener;

        public CardNumberEditText(Context context) {
            super(context);
        }

        public CardNumberEditText(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public void setMode(int mode) {
            this.mode = mode;
            setAnimationFactor(mode == FULL_MODE ? 0f : 1f);
        }

        @Override
        protected void onDraw(Canvas canvas) {

            String text = getText().toString();
            int l = text.length();
            Paint p = getPaint();
            float dist = p.measureText(text.substring(0, Math.max(0, l - charsCount)));
            if (mode == FULL_MODE ) {
                canvas.save();
                canvas.translate(-dist * animationFactor, 0);
                super.onDraw(canvas);
                canvas.restore();
            } else if (mode == SHORT_MODE){
                canvas.drawText(text, Math.max(0, l - charsCount), l, 0, getBaseline(), p);
            }
        }

        @Override
        public boolean bringPointIntoView(int offset) {
            return false;
        }

        public void setAnimationFactor(float animationFactor) {
            this.animationFactor = animationFactor;
            invalidate();
        }

        public int getMode() {
            return mode;
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (mode == SHORT_MODE)
                return false;
            return super.onTouchEvent(event);
        }

        @Override
        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
            super.onFocusChanged(focused, direction, previouslyFocusedRect);
            if(customOnFocusChangedListener != null){
                customOnFocusChangedListener.onFocusChange(this, focused);
            }
        }

        public OnFocusChangeListener getCustomOnFocusChangedListener() {
            return customOnFocusChangedListener;
        }

        public void setCustomOnFocusChangedListener(OnFocusChangeListener customOnFocusChangedListener) {
            this.customOnFocusChangedListener = customOnFocusChangedListener;
        }
    }

    private static class MutableColorSpan extends CharacterStyle implements UpdateAppearance {


        private int color;

        public MutableColorSpan(int color) {
            super();
            this.color = color;
        }


        public void setAlpha(int alpha) {
            color = (color & 0x00FFFFFF) | (alpha << 24);
        }


        @Override
        public void updateDrawState(TextPaint tp) {
            tp.setColor(color);
        }
    }


    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.flags = flags;
        ss.pan = getCardNumber();
        ss.date = getExpireDate();
        ss.scanResId = scanResId;
        ss.nextResId = nextResId;
        ss.animationFactor = etCardNumber.animationFactor;
        ss.cardNumberMode = etCardNumber.mode;
        return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        flags = ss.flags;
        setCardNumber(ss.pan);
        setExpireDate(ss.date);
        setChangeModeIcon(ss.nextResId);
        setBtnScanIcon(ss.scanResId);
        etCardNumber.animationFactor = ss.animationFactor;
        etCardNumber.setMode(ss.cardNumberMode);
        boolean enableFields = !check(SAVED_CARD_STATE);
        etDate.setEnabled(enableFields);
        etCardNumber.setEnabled(enableFields);
    }

    static class SavedState extends BaseSavedState {
        int flags;
        String pan;
        String date;
        int nextResId;
        int scanResId;
        float animationFactor;
        int cardNumberMode;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            flags = in.readInt();
            pan = in.readString();
            date = in.readString();
            nextResId = in.readInt();
            scanResId = in.readInt();
            animationFactor = in.readFloat();
            cardNumberMode = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(this.flags);
            out.writeString(pan);
            out.writeString(date);
            out.writeInt(nextResId);
            out.writeInt(scanResId);
            out.writeFloat(animationFactor);
            out.writeInt(cardNumberMode);
        }

        public static final Parcelable.Creator CREATOR
                = new Parcelable.Creator() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }


    @Override
    public boolean onCheckIsTextEditor() {
        return super.onCheckIsTextEditor();
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy