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}