mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
add currency mask
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
package com.yandex.div.core.util.mask
|
||||
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
import kotlin.math.abs
|
||||
|
||||
internal class CurrencyInputMask(
|
||||
locale: Locale,
|
||||
private val onError: ((Exception) -> Unit)
|
||||
) : BaseInputMask(MaskData("", emptyList(), false)) {
|
||||
private val currencyKey = '¤'
|
||||
|
||||
private val separators = listOf('.', ',')
|
||||
|
||||
private var currencyFormatter: NumberFormat = NumberFormat
|
||||
.getCurrencyInstance(locale)
|
||||
.clearFormatter()
|
||||
|
||||
private val decimalFormatSymbols: DecimalFormatSymbols
|
||||
get() = (currencyFormatter as DecimalFormat).decimalFormatSymbols
|
||||
|
||||
private val String.withNbsp: String
|
||||
get() = replace(' ', ' ')
|
||||
|
||||
fun updateCurrencyParams(locale: Locale) {
|
||||
val currentValue = currencyFormatter.parse(value) ?: 0
|
||||
|
||||
currencyFormatter = NumberFormat.getCurrencyInstance(locale).clearFormatter()
|
||||
|
||||
invalidateMaskDataForFormatted(currentValue)
|
||||
|
||||
applyChangeFrom(currencyFormatter.format(currentValue))
|
||||
}
|
||||
|
||||
private fun invalidateMaskDataForFormatted(forValue: Number) {
|
||||
val formatted = currencyFormatter.format(forValue)
|
||||
val maskPattern = formatPattern(formatted)
|
||||
|
||||
val decoding = listOf(
|
||||
MaskKey(
|
||||
key = '#',
|
||||
filter = "\\d",
|
||||
placeholder = '0'
|
||||
),
|
||||
MaskKey(
|
||||
key = decimalFormatSymbols.decimalSeparator,
|
||||
filter = "[${decimalFormatSymbols.decimalSeparator}]",
|
||||
placeholder = decimalFormatSymbols.decimalSeparator
|
||||
)
|
||||
)
|
||||
|
||||
updateMaskData(MaskData(maskPattern, decoding, maskData.alwaysVisible), false)
|
||||
}
|
||||
|
||||
override fun applyChangeFrom(newValue: String, position: Int?) {
|
||||
val diff = TextDiff.build(value, newValue.withNbsp)
|
||||
|
||||
val decimalSeparator = decimalFormatSymbols.decimalSeparator
|
||||
val oldSeparatorIndex = value.indexOfLast { it == decimalSeparator }
|
||||
val newSeparatorIndex = newValue.indexOfLast { it == decimalSeparator }
|
||||
val needInvalidateMask = oldSeparatorIndex != newSeparatorIndex ||
|
||||
(oldSeparatorIndex == -1 && newSeparatorIndex == -1)
|
||||
|
||||
val clearedValue = newValue.toValidFormat(diff)
|
||||
|
||||
cleanup(diff)
|
||||
|
||||
val rawValue = currencyFormatter.parse(clearedValue) ?: 0
|
||||
|
||||
if (needInvalidateMask) {
|
||||
invalidateMaskDataForFormatted(rawValue)
|
||||
}
|
||||
|
||||
replaceChars(clearedValue, 0)
|
||||
|
||||
cursorPosition =
|
||||
if (value.length > diff.start && value[diff.start] == decimalFormatSymbols.groupingSeparator) {
|
||||
position ?: cursorPosition
|
||||
} else {
|
||||
abs(value.length - (newValue.length - (position ?: cursorPosition)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun NumberFormat.clearFormatter(): NumberFormat {
|
||||
(this as? DecimalFormat)?.apply {
|
||||
val pattern = toPattern().filter { it != currencyKey }.trim()
|
||||
applyPattern(pattern)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun String.toValidFormat(diff: TextDiff): String {
|
||||
if (isBlank()) return decimalFormatSymbols.zeroDigit.toString()
|
||||
|
||||
val separatorChar = decimalFormatSymbols.decimalSeparator
|
||||
|
||||
var separatorOutOfDiffIndex: Int = -1
|
||||
|
||||
var index = 0
|
||||
|
||||
while (index < this.length) {
|
||||
if (this[index] == separatorChar && !inDiff(diff, index)) {
|
||||
separatorOutOfDiffIndex = index
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
index++
|
||||
}
|
||||
|
||||
val replaceCharInDiff =
|
||||
if (diff.added == 1 && diff.removed == 0) {
|
||||
val diffChar = this[diff.start]
|
||||
|
||||
if (diffChar in separators) diff.start else -1
|
||||
} else -1
|
||||
|
||||
val maxSeparatorOffset = currencyFormatter.maximumFractionDigits
|
||||
|
||||
var leftToInsert = maxSeparatorOffset
|
||||
|
||||
if (separatorOutOfDiffIndex != -1) {
|
||||
index = separatorOutOfDiffIndex
|
||||
|
||||
while (index < length) {
|
||||
if (this[index].isDigit() && !inDiff(diff, index)) leftToInsert--
|
||||
index++
|
||||
}
|
||||
} else {
|
||||
var oldSeparatorLeft = false
|
||||
|
||||
forEachIndexed { i, char ->
|
||||
val inDiff = inDiff(diff, i)
|
||||
|
||||
when {
|
||||
char == separatorChar -> {
|
||||
oldSeparatorLeft = true
|
||||
}
|
||||
!inDiff && oldSeparatorLeft && char.isDigit() -> {
|
||||
leftToInsert--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val containsSeparator = contains(separatorChar) || replaceCharInDiff != -1
|
||||
|
||||
return buildString {
|
||||
index = this@toValidFormat.length - 1
|
||||
|
||||
var separatorInserted = false
|
||||
|
||||
while (index >= 0) {
|
||||
val char = this@toValidFormat[index]
|
||||
|
||||
val canInsertSeparator = length <= maxSeparatorOffset
|
||||
|
||||
if (char.isDigit()) {
|
||||
if (inDiff(diff, index) && !separatorInserted && containsSeparator) {
|
||||
if (leftToInsert > 0) {
|
||||
append(char)
|
||||
|
||||
leftToInsert--
|
||||
}
|
||||
} else {
|
||||
append(char)
|
||||
}
|
||||
} else if (canInsertSeparator && separatorOutOfDiffIndex == -1 && index == replaceCharInDiff) {
|
||||
append(separatorChar)
|
||||
|
||||
separatorInserted = true
|
||||
} else if (canInsertSeparator && char == separatorChar && (separatorOutOfDiffIndex == index || separatorOutOfDiffIndex == -1)) {
|
||||
append(separatorChar)
|
||||
|
||||
separatorInserted = true
|
||||
separatorOutOfDiffIndex = index
|
||||
}
|
||||
|
||||
index--
|
||||
}
|
||||
}
|
||||
.reversed()
|
||||
.trimStart(decimalFormatSymbols.zeroDigit)
|
||||
}
|
||||
|
||||
private fun inDiff(diff: TextDiff, index: Int): Boolean =
|
||||
diff.start <= index && index < diff.start + diff.added
|
||||
|
||||
private fun formatPattern(pattern: String): String {
|
||||
return buildString {
|
||||
pattern.forEach { char ->
|
||||
when {
|
||||
char.isDigit() -> append('#')
|
||||
else -> append(char)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onException(exception: Exception) {
|
||||
onError.invoke(exception)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -2,9 +2,9 @@ package com.yandex.div.core.util.mask
|
||||
|
||||
internal open class FixedLengthInputMask(
|
||||
initialMaskData: MaskData,
|
||||
private val onError: ((Exception) -> Unit)? = null
|
||||
private val onError: ((Exception) -> Unit)
|
||||
) : BaseInputMask(initialMaskData) {
|
||||
override fun onException(exception: Exception) {
|
||||
onError?.invoke(exception)
|
||||
onError.invoke(exception)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ package com.yandex.div.core.view2.divs
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.InputType
|
||||
import android.text.method.DigitsKeyListener
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import com.yandex.div.core.dagger.DivScope
|
||||
import com.yandex.div.core.expression.variables.TwoWayStringVariableBinder
|
||||
import com.yandex.div.core.util.mask.BaseInputMask
|
||||
import com.yandex.div.core.util.mask.CurrencyInputMask
|
||||
import com.yandex.div.core.util.mask.FixedLengthInputMask
|
||||
import com.yandex.div.core.util.toIntSafely
|
||||
import com.yandex.div.core.view2.Div2View
|
||||
@@ -16,9 +18,11 @@ import com.yandex.div.core.view2.DivViewBinder
|
||||
import com.yandex.div.core.view2.divs.widgets.DivInputView
|
||||
import com.yandex.div.core.view2.errors.ErrorCollectors
|
||||
import com.yandex.div.json.expressions.ExpressionResolver
|
||||
import com.yandex.div2.DivCurrencyInputMask
|
||||
import com.yandex.div2.DivFixedLengthInputMask
|
||||
import com.yandex.div2.DivInput
|
||||
import com.yandex.div2.DivSizeUnit
|
||||
import java.util.Locale
|
||||
import java.util.regex.PatternSyntaxException
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -278,11 +282,24 @@ internal class DivInputBinder @Inject constructor(
|
||||
|
||||
val errorCollector = errorCollectors.getOrCreate(divView.dataTag, divView.divData)
|
||||
|
||||
val defaultKeyListener = keyListener
|
||||
|
||||
val catchCommonMaskException = { exception: Exception, other: () -> Unit ->
|
||||
when (exception) {
|
||||
is PatternSyntaxException -> errorCollector.logError(
|
||||
IllegalArgumentException("Invalid regex pattern '${exception.pattern}'.")
|
||||
)
|
||||
else -> other()
|
||||
}
|
||||
}
|
||||
|
||||
val updateMaskData = { _: Any ->
|
||||
val divInputMask = div.mask?.value()
|
||||
|
||||
inputMask = when (divInputMask) {
|
||||
is DivFixedLengthInputMask -> {
|
||||
keyListener = defaultKeyListener
|
||||
|
||||
val maskData = BaseInputMask.MaskData(
|
||||
divInputMask.pattern.evaluate(resolver),
|
||||
divInputMask.patternElements.map {
|
||||
@@ -296,14 +313,41 @@ internal class DivInputBinder @Inject constructor(
|
||||
)
|
||||
|
||||
inputMask?.apply { updateMaskData(maskData) } ?: FixedLengthInputMask(maskData) {
|
||||
when (it) {
|
||||
is PatternSyntaxException -> errorCollector.logError(
|
||||
IllegalArgumentException("Invalid regex pattern '${it.pattern}'.")
|
||||
)
|
||||
}
|
||||
catchCommonMaskException(it) { }
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
is DivCurrencyInputMask -> {
|
||||
val evaluatedLocaleTag = divInputMask.locale?.evaluate(resolver)
|
||||
|
||||
val locale = if (evaluatedLocaleTag != null) {
|
||||
Locale.forLanguageTag(evaluatedLocaleTag)
|
||||
.apply {
|
||||
val finalLanguageTag = toLanguageTag()
|
||||
|
||||
if (finalLanguageTag != evaluatedLocaleTag) {
|
||||
val exception = IllegalArgumentException("Original locale tag '$evaluatedLocaleTag' is not equals to final one '$finalLanguageTag'")
|
||||
|
||||
errorCollector.logWarning(exception)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Locale.getDefault()
|
||||
}
|
||||
|
||||
keyListener = DigitsKeyListener.getInstance("1234567890.,")
|
||||
|
||||
inputMask?.apply {
|
||||
(inputMask as CurrencyInputMask)
|
||||
.updateCurrencyParams(locale)
|
||||
} ?: CurrencyInputMask(locale) {
|
||||
catchCommonMaskException(it) { }
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
keyListener = defaultKeyListener
|
||||
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
onMaskUpdate(inputMask)
|
||||
@@ -311,7 +355,7 @@ internal class DivInputBinder @Inject constructor(
|
||||
|
||||
when (val inputMask = div.mask?.value()) {
|
||||
is DivFixedLengthInputMask -> {
|
||||
addSubscription(inputMask.pattern.observeAndGet(resolver, updateMaskData))
|
||||
addSubscription(inputMask.pattern.observe(resolver, updateMaskData))
|
||||
inputMask.patternElements.forEach { patternElement ->
|
||||
addSubscription(patternElement.key.observe(resolver, updateMaskData))
|
||||
patternElement.regex?.let { addSubscription(it.observe(resolver, updateMaskData)) }
|
||||
@@ -319,7 +363,12 @@ internal class DivInputBinder @Inject constructor(
|
||||
}
|
||||
addSubscription(inputMask.alwaysVisible.observe(resolver, updateMaskData))
|
||||
}
|
||||
is DivCurrencyInputMask -> {
|
||||
inputMask.locale?.observe(resolver, updateMaskData)?.let { addSubscription(it) }
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
updateMaskData(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
package com.yandex.div.core.util.mask
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import java.util.Locale
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class CurrencyMaskTest {
|
||||
private val russianLocale = Locale.forLanguageTag("ru-RU")
|
||||
private val deutschLocale = Locale.forLanguageTag("de-DE")
|
||||
private val englishLocale = Locale.forLanguageTag("en-US")
|
||||
|
||||
@Test
|
||||
fun `insert decimal to empty string`() {
|
||||
val currencyMask = createCurrencyMask()
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1", 1)
|
||||
|
||||
currencyMask.assertMask("1", 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insert multiple decimals at once to empty string`() {
|
||||
val currencyMask = createCurrencyMask()
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("12345", 5)
|
||||
|
||||
currencyMask.assertMask("12 345", 6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insert one decimal`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("12", 2)
|
||||
|
||||
currencyMask.assertMask("12", 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insert multiple decimals`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("12345", 5)
|
||||
|
||||
currencyMask.assertMask("12 345", 6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remove one decimal`() {
|
||||
val currencyMask = createCurrencyMask().withValue("12345")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("12 45", 3)
|
||||
|
||||
currencyMask.assertMask("1 245", 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remove multiple decimals`() {
|
||||
val currencyMask = createCurrencyMask().withValue("123456")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("12 56", 3)
|
||||
|
||||
currencyMask.assertMask("1 256", 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deutsch currency mask`() {
|
||||
val currencyMask = createCurrencyMask(deutschLocale)
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1.234,56", 8)
|
||||
|
||||
currencyMask.assertMask("1.234,56", 8)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `english currency mask`() {
|
||||
val currencyMask = createCurrencyMask(englishLocale)
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1,234.56", 8)
|
||||
|
||||
currencyMask.assertMask("1,234.56", 8)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insert comma english currency mask`() {
|
||||
val currencyMask = createCurrencyMask(englishLocale).withValue("123456")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("123,4,56", 6)
|
||||
|
||||
currencyMask.assertMask("1,234.56", 6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `input separator`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1234")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1 234,", 6)
|
||||
|
||||
currencyMask.assertMask("1 234,", 6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remove separator`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1234,")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1 234", 5)
|
||||
|
||||
currencyMask.assertMask("1 234", 5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insert decimal after separator`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1234,")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1 234,5", 7)
|
||||
|
||||
currencyMask.assertMask("1 234,5", 7)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remove decimal after separator`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1234,56")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1 234,6", 6)
|
||||
|
||||
currencyMask.assertMask("1 234,6", 6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insert separator with overflow not allowed`() {
|
||||
val currencyMask = createCurrencyMask().withValue("12345678")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("12 34,5 678", 6)
|
||||
|
||||
currencyMask.assertMask("12 345 678", 5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insert separator without overflow allowed`() {
|
||||
val currencyMask = createCurrencyMask().withValue("123456")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("123 4,56", 6)
|
||||
|
||||
currencyMask.assertMask("1 234,56", 6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remove separator from a middle`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1234,56")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1 23456", 5)
|
||||
|
||||
currencyMask.assertMask("123 456", 5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `change locale with value`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1 234,56")
|
||||
|
||||
currencyMask.assertMask("1 234,56", 0)
|
||||
|
||||
currencyMask.updateCurrencyParams(deutschLocale)
|
||||
|
||||
currencyMask.assertMask("1.234,56", 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `input dot as separator for deutsch locale`() {
|
||||
val currencyMask = createCurrencyMask(deutschLocale).withValue("1234567")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1.234.5.67", 8)
|
||||
|
||||
currencyMask.assertMask("12.345,67", 7)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `input dot as separator for english locale`() {
|
||||
val currencyMask = createCurrencyMask(englishLocale).withValue("1234567")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1,234,5.67", 8)
|
||||
|
||||
currencyMask.assertMask("12,345.67", 7)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `input comma as separator for english locale`() {
|
||||
val currencyMask = createCurrencyMask(englishLocale).withValue("1234567")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1,234,5,67", 8)
|
||||
|
||||
currencyMask.assertMask("12,345.67", 7)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `input invalid character`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1234,5")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1 23a4,5", 5)
|
||||
|
||||
currencyMask.assertMask("1 234,5", 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `input second separator before one`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1234,56")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1 2,34,56", 4)
|
||||
|
||||
currencyMask.assertMask("1 234,56", 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `input second separator after one`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1234,56")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("1 234,5,6", 8)
|
||||
|
||||
currencyMask.assertMask("1 234,56", 7)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fully clear field`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1234,56")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("", 0)
|
||||
|
||||
currencyMask.assertMask("0", 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type digit after leading zero`() {
|
||||
val currencyMask = createCurrencyMask()
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("01", 1)
|
||||
|
||||
currencyMask.assertMask("1", 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type zero digits as leading`() {
|
||||
val currencyMask = createCurrencyMask().withValue("1 234")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("0 001 234", 4)
|
||||
|
||||
currencyMask.assertMask("1 234", 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type char at end with overflow`() {
|
||||
val currencyMask = createCurrencyMask().withValue("123,45")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("123,465", 6)
|
||||
|
||||
currencyMask.assertMask("123,45", 5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type char and separator at end with overflow`() {
|
||||
val currencyMask = createCurrencyMask().withValue("12345")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("12 3,645", 6)
|
||||
|
||||
currencyMask.assertMask("123,45", 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type char and separator at end without overflow`() {
|
||||
val currencyMask = createCurrencyMask().withValue("123,4")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("123,45", 6)
|
||||
|
||||
currencyMask.assertMask("123,45", 6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `add character in repeated substring`() {
|
||||
val currencyMask = createCurrencyMask().withValue("11111")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("11 1111", 5)
|
||||
|
||||
currencyMask.assertMask("111 111", 5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remove character in repeated substring`() {
|
||||
val currencyMask = createCurrencyMask().withValue("11111")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("11 11", 4)
|
||||
|
||||
currencyMask.assertMask("1 111", 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remove cluster separator`() {
|
||||
val currencyMask = createCurrencyMask().withValue("12345")
|
||||
|
||||
currencyMask.applyChangesFromWithNbsp("12345", 2)
|
||||
|
||||
currencyMask.assertMask("12 345", 2)
|
||||
}
|
||||
|
||||
private fun createCurrencyMask(locale: Locale = russianLocale): CurrencyInputMask {
|
||||
return CurrencyInputMask(locale) { }
|
||||
}
|
||||
|
||||
private fun CurrencyInputMask.applyChangesFromWithNbsp(value: String, position: Int?) {
|
||||
applyChangeFrom(value.withNbsp(), position)
|
||||
}
|
||||
|
||||
private fun CurrencyInputMask.withValue(value: String): CurrencyInputMask {
|
||||
applyChangeFrom(value.withNbsp())
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun CurrencyInputMask.assertMask(
|
||||
expectedMaskedValue: String,
|
||||
expectedCursorPosition: Int
|
||||
) {
|
||||
Assert.assertEquals(expectedMaskedValue.withNbsp(), value)
|
||||
Assert.assertEquals(expectedCursorPosition, cursorPosition)
|
||||
}
|
||||
|
||||
private fun String.withNbsp(): String = replace(' ', ' ')
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@ package com.yandex.div.core.util.mask
|
||||
|
||||
internal class TestFixedLengthInputMask(
|
||||
initialMaskData: MaskData
|
||||
) : FixedLengthInputMask(initialMaskData) {
|
||||
) : FixedLengthInputMask(initialMaskData, { }) {
|
||||
fun publicCalculateInsertableSubstring(substring: String, start: Int): String =
|
||||
calculateInsertableSubstring(substring, start)
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"templates": {
|
||||
"pretty_input": {
|
||||
"type": "input",
|
||||
"height": {
|
||||
"type": "wrap_content"
|
||||
},
|
||||
"background": [
|
||||
{
|
||||
"type": "solid",
|
||||
"color": "#0e000000"
|
||||
}
|
||||
],
|
||||
"border": {
|
||||
"corner_radius": 8
|
||||
},
|
||||
"margins": {
|
||||
"left": 16,
|
||||
"top": 8,
|
||||
"right": 16,
|
||||
"bottom": 8
|
||||
},
|
||||
"paddings": {
|
||||
"left": 16,
|
||||
"top": 10,
|
||||
"right": 16,
|
||||
"bottom": 10
|
||||
},
|
||||
"font_size": 16,
|
||||
"font_weight": "medium",
|
||||
"text_color": "#000000",
|
||||
"hint_color": "#888888",
|
||||
"highlight_color": "#e0bae3",
|
||||
"line_height": 22
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"log_id": "ui_test_card",
|
||||
"variables": [
|
||||
{
|
||||
"name": "input",
|
||||
"type": "string",
|
||||
"value": "1234567"
|
||||
},
|
||||
{
|
||||
"name": "raw_input",
|
||||
"type": "string",
|
||||
"value": "12345"
|
||||
},
|
||||
{
|
||||
"name": "locale",
|
||||
"type": "string",
|
||||
"value": "en-US"
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
{
|
||||
"state_id": 0,
|
||||
"div": {
|
||||
"type": "container",
|
||||
"orientation": "vertical",
|
||||
"width": {
|
||||
"type": "match_parent"
|
||||
},
|
||||
"height": {
|
||||
"type": "wrap_content"
|
||||
},
|
||||
"paddings": {
|
||||
"top": 8,
|
||||
"bottom": 8
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Raw value: @{input}",
|
||||
"font_size": 20,
|
||||
"margins": {
|
||||
"left": 16,
|
||||
"top": 8,
|
||||
"right": 16,
|
||||
"bottom": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "pretty_input",
|
||||
"text_variable": "input",
|
||||
"hint_text": "0",
|
||||
"keyboard_type": "number",
|
||||
"mask": {
|
||||
"type": "currency",
|
||||
"raw_text_variable": "raw_input",
|
||||
"locale": "@{locale == '' ? 'en-US' : locale}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "pretty_input",
|
||||
"text_variable": "locale",
|
||||
"hint_text": "Currency locale"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1742,6 +1742,16 @@
|
||||
"DivInput"
|
||||
],
|
||||
"file": "phone_input_mask.json"
|
||||
},
|
||||
{
|
||||
"title": "Currency input mask",
|
||||
"platforms": [
|
||||
"android"
|
||||
],
|
||||
"tags": [
|
||||
"DivInput"
|
||||
],
|
||||
"file": "currency_input_mask.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user