Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package com.moovel.theme.widget
- import android.content.Context
- import android.text.Editable
- import android.text.TextWatcher
- import android.util.AttributeSet
- import android.widget.LinearLayout
- import androidx.annotation.VisibleForTesting
- import androidx.annotation.VisibleForTesting.PRIVATE
- import androidx.constraintlayout.widget.ConstraintLayout
- import androidx.core.view.isVisible
- import com.moovel.theme.R
- import kotlinx.android.synthetic.main.theme_valid_until_input.view.placeHolder
- import kotlinx.android.synthetic.main.theme_valid_until_input.view.placeHolderHint
- import kotlinx.android.synthetic.main.theme_valid_until_input.view.textInput
- import kotlinx.android.synthetic.main.theme_valid_until_input.view.textInputWrapper
- class ThemeValidUntilInput @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null
- ) : ConstraintLayout(context, attrs) {
- var headlineText: String? = null
- set(value) {
- field = value
- textInputWrapper.hint = value
- }
- var hintText: String? = null
- set(value) {
- if (value != null && value.length != MAX_CHARS) throw IllegalArgumentException("The format needs 5 chars. E.g. MM/YY")
- if (value != null && value.elementAt(2).toString() != SLASH) {
- throw IllegalArgumentException("The format needs a slash at position 2. E.g. MM/YY")
- }
- field = value
- placeHolderHint.text = value
- }
- private val textWatcher = object : TextWatcher {
- var cursorPos = NOT_FOUND_POS
- override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
- override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
- cursorPos = cursorPos(start, count)
- }
- override fun afterTextChanged(editable: Editable) {
- val input = editable.toString()
- val correctedInput = updateSlashPosition(input)
- val corrupted = correctedInput != input
- val cursorPosAfter = cursorPos
- updatePlaceHolder(correctedInput)
- if (corrupted) {
- textInput.setText(correctedInput)
- textInput.setSelection(cursorPosAfter)
- }
- }
- private fun updatePlaceHolder(text: String) {
- placeHolder.text = text
- placeHolderHint.text = hintText?.substring(text.length, MAX_CHARS)
- }
- }
- init {
- LinearLayout.inflate(context, R.layout.theme_valid_until_input, this)
- attrs?.let {
- context.obtainStyledAttributes(it, R.styleable.ThemeValidUntilInput, 0, 0).apply {
- headlineText = getString(R.styleable.ThemeValidUntilInput_theme_headline)
- hintText = getString(R.styleable.ThemeValidUntilInput_theme_hint)
- recycle()
- }
- }
- textInput.setOnFocusChangeListener { _, hasFocus -> placeHolderHint.isVisible = hasFocus }
- textInput.addTextChangedListener(textWatcher)
- }
- fun getYear() = parseYear(textInput.text.toString())
- fun getMonth() = parseMonth(textInput.text.toString())
- companion object {
- private const val SLASH = "/"
- private const val NOT_FOUND_POS = -1
- private const val START_POS = 0
- private const val CORRECT_POS = 2
- private const val FIRST_AFTER_SLASH = 3
- private const val MAX_DIGITS = 4
- private const val MAX_CHARS = 5
- private const val CURRENT_MILLENNIUM = 2000 // need to be fixed before year 2100
- /**
- * Updates the position of the slash after text changed.
- */
- @VisibleForTesting(otherwise = PRIVATE)
- fun updateSlashPosition(input: String): String {
- val slashPos = input.indexOf(SLASH)
- val clearedInput = if (slashPos > NOT_FOUND_POS && (slashPos != CORRECT_POS || slashPos == input.lastIndex)) {
- input.replace(SLASH, "")
- } else if (slashPos == NOT_FOUND_POS && input.length == MAX_CHARS) {
- input.substring(START_POS, MAX_DIGITS)
- } else {
- input
- }
- return if (clearedInput.indexOf(SLASH) == NOT_FOUND_POS && clearedInput.length > CORRECT_POS) {
- clearedInput.substring(START_POS, CORRECT_POS) + SLASH + clearedInput.substring(CORRECT_POS, clearedInput.length)
- } else {
- clearedInput
- }
- }
- /**
- * returns correct cursor position on text changed.
- */
- @VisibleForTesting(otherwise = PRIVATE)
- fun cursorPos(start: Int, count: Int) = if (count > 0) {
- val pos = start + count
- if (pos > CORRECT_POS) pos + 1 else pos
- } else {
- if (start == FIRST_AFTER_SLASH) start - 1 else start
- }
- @VisibleForTesting(otherwise = PRIVATE)
- fun parseYear(input: String): Int? {
- if (input.length < MAX_CHARS) return null
- return CURRENT_MILLENNIUM + input.substring(FIRST_AFTER_SLASH, MAX_CHARS).toInt()
- }
- @VisibleForTesting(otherwise = PRIVATE)
- fun parseMonth(input: String): Int? {
- if (input.length < CORRECT_POS) return null
- return input.substring(0, CORRECT_POS).toInt()
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement