From af7e505bc54794b01a231d0f7618cc8fddaa02b6 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Sat, 22 Aug 2020 18:06:08 +0500 Subject: [PATCH] Amount edittext first version --- .../widget/AmountWithDecimalEditText.kt | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 views/src/main/java/ru/touchin/widget/AmountWithDecimalEditText.kt diff --git a/views/src/main/java/ru/touchin/widget/AmountWithDecimalEditText.kt b/views/src/main/java/ru/touchin/widget/AmountWithDecimalEditText.kt new file mode 100644 index 0000000..50b955a --- /dev/null +++ b/views/src/main/java/ru/touchin/widget/AmountWithDecimalEditText.kt @@ -0,0 +1,151 @@ +package ru.touchin.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatEditText +import androidx.core.widget.doOnTextChanged +import ru.touchin.roboswag.components.views.R +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import kotlin.math.max +import kotlin.math.min +import kotlin.math.pow + +class AmountWithDecimalEditText @JvmOverloads constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int = R.attr.editTextStyle +) : AppCompatEditText(context, attrs, defStyleAttr) { + + companion object { + + private const val COMMON_MONEY_MASK = "###,##0" + private const val DEFAULT_DECIMAL_SEPARATOR = "," + private const val GROUPING_SEPARATOR = ' ' + private const val DEFAULT_DECIMAL_PART_LENGTH = 2 + private val hardcodedSymbols = listOf(GROUPING_SEPARATOR) + private val possibleDecimalSeparators = listOf(",", ".") + } + + private var textBefore = "" + private var isTextWasArtificiallyChanged = true + + var decimalSeparator = DEFAULT_DECIMAL_SEPARATOR + set(value) { + if (!possibleDecimalSeparators.contains(value)) + throw Exception("Not allowed decimal separator. Supports only: $possibleDecimalSeparators") + field = value + } + + + var decimalPartLength = DEFAULT_DECIMAL_PART_LENGTH + var isSeparatorCutInvalidDecimalLength = false + + init { + doOnTextChanged { text, _, _, _ -> + if (isTextWasArtificiallyChanged) { + isTextWasArtificiallyChanged = false + val cursorPos = selectionStart + var text = text.toString() + possibleDecimalSeparators.forEach { + text = text.replace(it, decimalSeparator) + } + + if (text == decimalSeparator || text.count { it == decimalSeparator[0] } > 1) { + setText(textBefore) + setSelection(max(cursorPos - 1, 0)) + return@doOnTextChanged + } + + if (text.take(2) == "00") { + setText(textBefore) + setSelection(max(cursorPos - 1, 0)) + return@doOnTextChanged + } + + if (text.length >= 2 && text[0] == '0' && text[1] != decimalSeparator[0]) { + setText(text[1].toString()) + setSelection(max(cursorPos - 1, 0)) + return@doOnTextChanged + } + + + val decimalPartLength_ = text.split(decimalSeparator).getOrNull(1)?.length + if (!isSeparatorCutInvalidDecimalLength && decimalPartLength_ != null && decimalPartLength_ > decimalPartLength) { + setText(textBefore) + setSelection(max(cursorPos - 1, 0)) + return@doOnTextChanged + } + + val textAfter = if (text.isNotEmpty()) { + text.withoutFormatting().formatMoney(decimalPartLength_) + } else "" + + if (!isTextErased(textBefore, textAfter)) { + val diff = textAfter.length - textBefore.length - 1 + setText(textAfter) + setSelection(min(cursorPos + diff, textAfter.length)) + } else { + if (!textBefore.contains(decimalSeparator) + && textAfter.contains(decimalSeparator) + ) { + setText(textAfter) + setSelection( + min( + textAfter.length, + textAfter.indexOf(decimalSeparator) + 1 + ) + ) + return@doOnTextChanged + } + val diff = textBefore.length - textAfter.length + if (diff == 0) { + setText(textAfter) + setSelection(min(cursorPos, textAfter.length)) + } else { + setText(textAfter) + setSelection(max(cursorPos - diff + 1, 0)) + } + + } + } else { + textBefore = text.toString() + isTextWasArtificiallyChanged = true + } + } + } + + + fun getTextWithoutFormatting() = text.toString().withoutFormatting() + + private fun String.withoutFormatting(): String { + var result = this + hardcodedSymbols.forEach { result = this.replace(it.toString(), "") } + return result + } + + private fun isTextErased(textBefore: String, textAfter: String) = + textAfter.length <= textBefore.length + + private fun String.formatMoney(decimalPartLength_: Int?): String { + var mask = COMMON_MONEY_MASK + if (decimalPartLength_ != null && decimalPartLength != 0) mask += "." + "0".repeat( + min( + decimalPartLength_, + decimalPartLength + ) + ) + + val formatter = DecimalFormat(mask) + formatter.decimalFormatSymbols = DecimalFormatSymbols().also { + it.decimalSeparator = decimalSeparator[0] + it.groupingSeparator = GROUPING_SEPARATOR + } + return formatter.format(this.replace(",", ".").toDouble().floor()) + } + + private fun Double.floor() = + (this * 10.toDouble().pow(decimalPartLength)).toLong() / 10.toDouble() + .pow(decimalPartLength) + +}