summaryrefslogtreecommitdiffhomepage
path: root/android/src
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2020-05-07 01:29:03 +0000
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2020-05-11 13:15:50 +0000
commit12037132899485d8ce9d79fa3005e280325f6a50 (patch)
tree3e8da853669a77ceaa3fb2d7f8581c2f00d1851b /android/src
parentb1e8a92d8fbabea29d76aa782663b4388cef8223 (diff)
downloadmullvadvpn-12037132899485d8ce9d79fa3005e280325f6a50.tar.xz
mullvadvpn-12037132899485d8ce9d79fa3005e280325f6a50.zip
Create `SegmentedInputFormatter` helper class
Diffstat (limited to 'android/src')
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/util/SegmentedInputFormatter.kt137
1 files changed, 137 insertions, 0 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/util/SegmentedInputFormatter.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/util/SegmentedInputFormatter.kt
new file mode 100644
index 0000000000..ed471778eb
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/util/SegmentedInputFormatter.kt
@@ -0,0 +1,137 @@
+package net.mullvad.mullvadvpn.util
+
+import android.text.Editable
+import android.text.TextWatcher
+import android.widget.EditText
+
+class SegmentedInputFormatter(val input: EditText, var separator: Char) : TextWatcher {
+ private var editing = false
+ private var removing = false
+ private var separatorSkipCount = 5
+
+ var allCaps = false
+ var isValidInputCharacter: (Char) -> Boolean = { _ -> true }
+
+ var segmentSize = 4
+ set(value) {
+ field = value
+ separatorSkipCount = value + 1
+ }
+
+ init {
+ input.addTextChangedListener(this)
+ }
+
+ override fun beforeTextChanged(text: CharSequence, start: Int, count: Int, after: Int) {
+ if (!editing) {
+ editing = true
+ removing = after < count
+ }
+ }
+
+ override fun onTextChanged(text: CharSequence, start: Int, before: Int, count: Int) {}
+
+ override fun afterTextChanged(text: Editable) {
+ val string = text.toString()
+
+ if (isValidInput(string)) {
+ editing = false
+ maybeUpdateSelection()
+ } else {
+ formatInput(text)
+ }
+ }
+
+ private fun maybeUpdateSelection() {
+ if (removing) {
+ var start = input.selectionStart
+ var end = input.selectionEnd
+ var changed = false
+
+ if (start % separatorSkipCount == 0) {
+ start -= 1
+ changed = true
+ }
+
+ if (end % separatorSkipCount == 0) {
+ end -= 1
+ changed = true
+ }
+
+ if (changed) {
+ input.setSelection(start, end)
+ }
+ }
+ }
+
+ private fun isValidInput(string: String): Boolean {
+ return string
+ .asSequence()
+ .withIndex()
+ .all { item ->
+ val index = item.index
+ val character = item.value
+
+ if ((index + 1) % separatorSkipCount == 0) {
+ character == separator
+ } else {
+ isValidInputCharacter(character)
+ }
+ }
+ }
+
+ private fun formatInput(input: Editable) {
+ var index = 0
+ val length = input.length
+ var changed = false
+
+ while (index < length && !changed) {
+ val segmentStart = index
+ val segmentEnd = index + segmentSize - 1
+ val separatorPosition = segmentEnd + 1
+
+ changed = formatSegment(input, segmentStart..segmentEnd) ||
+ formatSeparator(input, separatorPosition)
+
+ index = separatorPosition + 1
+ }
+ }
+
+ private fun formatSegment(input: Editable, range: IntRange): Boolean {
+ val length = input.length
+ val start = range.start
+ var end = range.endInclusive
+
+ if (start < length) {
+ end = minOf(end, length - 1)
+
+ for (index in start..end) {
+ val character = input[index]
+
+ if (allCaps && character >= 'a' && character <= 'z') {
+ input.replace(index, index + 1, character.toString().toUpperCase())
+ } else if (!isValidInputCharacter(character)) {
+ input.delete(index, index + 1)
+ } else {
+ // Only continue looping if no changes were made to the string
+ continue
+ }
+
+ // Abort loop because the input was edited and `afterTextChanged` will be called
+ // again
+ return true
+ }
+ }
+
+ return false
+ }
+
+ private fun formatSeparator(input: Editable, index: Int): Boolean {
+ if (index < input.length && input[index] != '-') {
+ input.insert(index, "-")
+ return true
+ } else {
+ return false
+ }
+ }
+}