PinEntryWrapper.java

  1package eu.siacs.conversations.ui.util;
  2
  3import android.text.Editable;
  4import android.text.TextWatcher;
  5import android.view.KeyEvent;
  6import android.view.View;
  7import android.widget.EditText;
  8import android.widget.LinearLayout;
  9
 10import java.util.ArrayList;
 11import java.util.List;
 12import java.util.regex.Pattern;
 13
 14
 15public class PinEntryWrapper {
 16
 17    private static final Pattern PIN_STRING_PATTERN = Pattern.compile("^[0-9]{6}$");
 18
 19    private final List<EditText> digits = new ArrayList<>();
 20
 21    private final TextWatcher textWatcher = new TextWatcher() {
 22        @Override
 23        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
 24
 25        }
 26
 27        @Override
 28        public void onTextChanged(CharSequence s, int start, int before, int count) {
 29
 30        }
 31
 32        @Override
 33        public void afterTextChanged(Editable s) {
 34            int current = -1;
 35            for (int i = 0; i < digits.size(); ++i) {
 36                if (s.hashCode() == digits.get(i).getText().hashCode()) {
 37                    current = i;
 38                }
 39            }
 40            if (current == -1) {
 41                return;
 42            }
 43            if (s.length() > 0) {
 44                if (current < digits.size() - 1) {
 45                    digits.get(current + 1).requestFocus();
 46                }
 47            } else {
 48                int focusOn = current;
 49                for (int i = current - 1; i >= 0; --i) {
 50                    if (digits.get(i).getText().length() == 0) {
 51                        focusOn = i;
 52                    } else {
 53                        break;
 54                    }
 55                }
 56                digits.get(focusOn).requestFocus();
 57            }
 58        }
 59    };
 60
 61    private final View.OnKeyListener keyListener = (v, keyCode, event) -> {
 62        if (event.getAction() != KeyEvent.ACTION_DOWN) {
 63            return false;
 64        }
 65        if (v instanceof EditText) {
 66            final EditText editText = (EditText) v;
 67            final boolean cursorAtZero = editText.getSelectionEnd() == 0 && editText.getSelectionStart() == 0;
 68            if (keyCode == KeyEvent.KEYCODE_DEL && (cursorAtZero || editText.getText().length() == 0)) {
 69                final int current = digits.indexOf(editText);
 70                for (int i = current - 1; i >= 0; --i) {
 71                    if (digits.get(i).getText().length() > 0) {
 72                        digits.get(i).getText().clear();
 73                        return true;
 74                    }
 75                }
 76                if (current != 0) {
 77                    digits.get(0).requestFocus();
 78                    return true;
 79                }
 80            }
 81        }
 82        return false;
 83    };
 84
 85    public PinEntryWrapper(LinearLayout linearLayout) {
 86        for (int i = 0; i < linearLayout.getChildCount(); ++i) {
 87            View view = linearLayout.getChildAt(i);
 88            if (view instanceof EditText) {
 89                this.digits.add((EditText) view);
 90            }
 91        }
 92        registerListeners();
 93    }
 94
 95    private void registerListeners() {
 96        for (EditText editText : digits) {
 97            editText.addTextChangedListener(textWatcher);
 98            editText.setOnKeyListener(keyListener);
 99        }
100    }
101
102    public String getPin() {
103        char[] chars = new char[digits.size()];
104        for (int i = 0; i < chars.length; ++i) {
105            final String input = digits.get(i).getText().toString();
106            chars[i] = input.length() != 1 ? ' ' : input.charAt(0);
107        }
108        return String.valueOf(chars);
109    }
110
111    public void setPin(String pin) {
112        char[] chars = pin.toCharArray();
113        for (int i = 0; i < digits.size(); ++i) {
114            if (i < chars.length) {
115                final Editable editable = digits.get(i).getText();
116                editable.clear();
117                editable.append(Character.isDigit(chars[i]) ? String.valueOf(chars[i]) : "");
118            }
119        }
120    }
121
122    public void setEnabled(final boolean enabled) {
123        for (EditText digit : digits) {
124            digit.setEnabled(enabled);
125            digit.setCursorVisible(enabled);
126            digit.setFocusable(enabled);
127            digit.setFocusableInTouchMode(enabled);
128        }
129        if (enabled) {
130            final EditText last = digits.get(digits.size() - 1);
131            if (last.getEditableText().length() > 0) {
132                last.requestFocus();
133            }
134        }
135    }
136
137    public boolean isEmpty() {
138        for (EditText digit : digits) {
139            if (digit.getText().length() > 0) {
140                return false;
141            }
142        }
143        return true;
144    }
145
146    public static boolean isValidPin(CharSequence pin) {
147        return pin != null && PIN_STRING_PATTERN.matcher(pin).matches();
148    }
149
150    public void clear() {
151        for (int i = digits.size() - 1; i >= 0; --i) {
152            digits.get(i).getText().clear();
153        }
154    }
155
156    public void requestFocus() {
157        digits.get(0).requestFocus();
158    }
159}