Advertisement
Guest User

Untitled

a guest
Apr 6th, 2020
190
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Kotlin 4.88 KB | None | 0 0
  1. package com.moovel.theme.widget
  2.  
  3. import android.content.Context
  4. import android.text.Editable
  5. import android.text.TextWatcher
  6. import android.util.AttributeSet
  7. import android.widget.LinearLayout
  8. import androidx.annotation.VisibleForTesting
  9. import androidx.annotation.VisibleForTesting.PRIVATE
  10. import androidx.constraintlayout.widget.ConstraintLayout
  11. import androidx.core.view.isVisible
  12. import com.moovel.theme.R
  13. import kotlinx.android.synthetic.main.theme_valid_until_input.view.placeHolder
  14. import kotlinx.android.synthetic.main.theme_valid_until_input.view.placeHolderHint
  15. import kotlinx.android.synthetic.main.theme_valid_until_input.view.textInput
  16. import kotlinx.android.synthetic.main.theme_valid_until_input.view.textInputWrapper
  17.  
  18. class ThemeValidUntilInput @JvmOverloads constructor(
  19.   context: Context,
  20.   attrs: AttributeSet? = null
  21. ) : ConstraintLayout(context, attrs) {
  22.  
  23.   var headlineText: String? = null
  24.     set(value) {
  25.       field = value
  26.       textInputWrapper.hint = value
  27.     }
  28.  
  29.   var hintText: String? = null
  30.     set(value) {
  31.       if (value != null && value.length != MAX_CHARS) throw IllegalArgumentException("The format needs 5 chars. E.g. MM/YY")
  32.       if (value != null && value.elementAt(2).toString() != SLASH) {
  33.         throw IllegalArgumentException("The format needs a slash at position 2. E.g. MM/YY")
  34.       }
  35.       field = value
  36.       placeHolderHint.text = value
  37.     }
  38.  
  39.   private val textWatcher = object : TextWatcher {
  40.     var cursorPos = NOT_FOUND_POS
  41.  
  42.     override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
  43.  
  44.     override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
  45.       cursorPos = cursorPos(start, count)
  46.     }
  47.  
  48.     override fun afterTextChanged(editable: Editable) {
  49.       val input = editable.toString()
  50.       val correctedInput = updateSlashPosition(input)
  51.       val corrupted = correctedInput != input
  52.       val cursorPosAfter = cursorPos
  53.  
  54.       updatePlaceHolder(correctedInput)
  55.  
  56.       if (corrupted) {
  57.         textInput.setText(correctedInput)
  58.         textInput.setSelection(cursorPosAfter)
  59.       }
  60.     }
  61.  
  62.     private fun updatePlaceHolder(text: String) {
  63.       placeHolder.text = text
  64.       placeHolderHint.text = hintText?.substring(text.length, MAX_CHARS)
  65.     }
  66.   }
  67.  
  68.   init {
  69.     LinearLayout.inflate(context, R.layout.theme_valid_until_input, this)
  70.  
  71.     attrs?.let {
  72.       context.obtainStyledAttributes(it, R.styleable.ThemeValidUntilInput, 0, 0).apply {
  73.         headlineText = getString(R.styleable.ThemeValidUntilInput_theme_headline)
  74.         hintText = getString(R.styleable.ThemeValidUntilInput_theme_hint)
  75.         recycle()
  76.       }
  77.     }
  78.  
  79.     textInput.setOnFocusChangeListener { _, hasFocus -> placeHolderHint.isVisible = hasFocus }
  80.     textInput.addTextChangedListener(textWatcher)
  81.   }
  82.  
  83.   fun getYear() = parseYear(textInput.text.toString())
  84.  
  85.   fun getMonth() = parseMonth(textInput.text.toString())
  86.  
  87.   companion object {
  88.     private const val SLASH = "/"
  89.     private const val NOT_FOUND_POS = -1
  90.     private const val START_POS = 0
  91.     private const val CORRECT_POS = 2
  92.     private const val FIRST_AFTER_SLASH = 3
  93.     private const val MAX_DIGITS = 4
  94.     private const val MAX_CHARS = 5
  95.     private const val CURRENT_MILLENNIUM = 2000 // need to be fixed before year 2100
  96.  
  97.     /**
  98.      * Updates the position of the slash after text changed.
  99.      */
  100.     @VisibleForTesting(otherwise = PRIVATE)
  101.     fun updateSlashPosition(input: String): String {
  102.       val slashPos = input.indexOf(SLASH)
  103.       val clearedInput = if (slashPos > NOT_FOUND_POS && (slashPos != CORRECT_POS || slashPos == input.lastIndex)) {
  104.         input.replace(SLASH, "")
  105.       } else if (slashPos == NOT_FOUND_POS && input.length == MAX_CHARS) {
  106.         input.substring(START_POS, MAX_DIGITS)
  107.       } else {
  108.         input
  109.       }
  110.  
  111.       return if (clearedInput.indexOf(SLASH) == NOT_FOUND_POS && clearedInput.length > CORRECT_POS) {
  112.         clearedInput.substring(START_POS, CORRECT_POS) + SLASH + clearedInput.substring(CORRECT_POS, clearedInput.length)
  113.       } else {
  114.         clearedInput
  115.       }
  116.     }
  117.  
  118.     /**
  119.      * returns correct cursor position on text changed.
  120.      */
  121.     @VisibleForTesting(otherwise = PRIVATE)
  122.     fun cursorPos(start: Int, count: Int) = if (count > 0) {
  123.       val pos = start + count
  124.       if (pos > CORRECT_POS) pos + 1 else pos
  125.     } else {
  126.       if (start == FIRST_AFTER_SLASH) start - 1 else start
  127.     }
  128.  
  129.     @VisibleForTesting(otherwise = PRIVATE)
  130.     fun parseYear(input: String): Int? {
  131.       if (input.length < MAX_CHARS) return null
  132.       return CURRENT_MILLENNIUM + input.substring(FIRST_AFTER_SLASH, MAX_CHARS).toInt()
  133.     }
  134.  
  135.     @VisibleForTesting(otherwise = PRIVATE)
  136.     fun parseMonth(input: String): Int? {
  137.       if (input.length < CORRECT_POS) return null
  138.       return input.substring(0, CORRECT_POS).toInt()
  139.     }
  140.   }
  141. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement