Fix non-standard numeral entry.
This commit is contained in:
parent
b981ac4fe4
commit
f9a2208832
5 changed files with 68 additions and 5 deletions
|
@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||||
|
import org.thoughtcrime.securesms.util.StringUtil
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
import java.lang.Integer.min
|
import java.lang.Integer.min
|
||||||
import java.text.DecimalFormatSymbols
|
import java.text.DecimalFormatSymbols
|
||||||
|
@ -197,7 +198,19 @@ data class Boost(
|
||||||
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
|
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
|
||||||
val separatorCount = min(1, currency.defaultFractionDigits)
|
val separatorCount = min(1, currency.defaultFractionDigits)
|
||||||
val symbol: String = currency.getSymbol(Locale.getDefault())
|
val symbol: String = currency.getSymbol(Locale.getDefault())
|
||||||
val pattern: Pattern = "[0-9]*([$separator]){0,$separatorCount}[0-9]{0,${currency.defaultFractionDigits}}".toPattern()
|
|
||||||
|
/**
|
||||||
|
* From Character.isDigit:
|
||||||
|
*
|
||||||
|
* * '\u0030' through '\u0039', ISO-LATIN-1 digits ('0' through '9')
|
||||||
|
* * '\u0660' through '\u0669', Arabic-Indic digits
|
||||||
|
* * '\u06F0' through '\u06F9', Extended Arabic-Indic digits
|
||||||
|
* * '\u0966' through '\u096F', Devanagari digits
|
||||||
|
* * '\uFF10' through '\uFF19', Fullwidth digits
|
||||||
|
*/
|
||||||
|
val digitsGroup: String = "[\\u0030-\\u0039]|[\\u0660-\\u0669]|[\\u06F0-\\u06F9]|[\\u0966-\\u096F]|[\\uFF10-\\uFF19]"
|
||||||
|
|
||||||
|
val pattern: Pattern = "($digitsGroup)*([$separator]){0,$separatorCount}($digitsGroup){0,${currency.defaultFractionDigits}}".toPattern()
|
||||||
val symbolPattern: Regex = """\s*${Regex.escape(symbol)}\s*""".toRegex()
|
val symbolPattern: Regex = """\s*${Regex.escape(symbol)}\s*""".toRegex()
|
||||||
val leadingZeroesPattern: Regex = """^0*""".toRegex()
|
val leadingZeroesPattern: Regex = """^0*""".toRegex()
|
||||||
|
|
||||||
|
@ -211,7 +224,7 @@ data class Boost(
|
||||||
): CharSequence? {
|
): CharSequence? {
|
||||||
|
|
||||||
val result = dest.subSequence(0, dstart).toString() + source.toString() + dest.subSequence(dend, dest.length)
|
val result = dest.subSequence(0, dstart).toString() + source.toString() + dest.subSequence(dend, dest.length)
|
||||||
val resultWithoutCurrencyPrefix = result.removePrefix(symbol).removeSuffix(symbol).trim()
|
val resultWithoutCurrencyPrefix = StringUtil.stripBidiIndicator(result.removePrefix(symbol).removeSuffix(symbol).trim())
|
||||||
|
|
||||||
if (resultWithoutCurrencyPrefix.length == 1 && !resultWithoutCurrencyPrefix.isDigitsOnly() && resultWithoutCurrencyPrefix != separator.toString()) {
|
if (resultWithoutCurrencyPrefix.length == 1 && !resultWithoutCurrencyPrefix.isDigitsOnly() && resultWithoutCurrencyPrefix != separator.toString()) {
|
||||||
return dest.subSequence(dstart, dend)
|
return dest.subSequence(dstart, dend)
|
||||||
|
@ -262,7 +275,14 @@ data class Boost(
|
||||||
}
|
}
|
||||||
|
|
||||||
val withoutSymbol = s.removePrefix(symbol).removeSuffix(symbol).trim().toString()
|
val withoutSymbol = s.removePrefix(symbol).removeSuffix(symbol).trim().toString()
|
||||||
val withoutLeadingZeroes = withoutSymbol.replace(leadingZeroesPattern, "")
|
val withoutLeadingZeroes: String = try {
|
||||||
|
NumberFormat.getInstance().apply {
|
||||||
|
isGroupingUsed = false
|
||||||
|
}.format(withoutSymbol.toBigDecimal()) + (if (withoutSymbol.endsWith(separator)) separator else "")
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
withoutSymbol
|
||||||
|
}.replace(leadingZeroesPattern, "")
|
||||||
|
|
||||||
if (withoutSymbol != withoutLeadingZeroes) {
|
if (withoutSymbol != withoutLeadingZeroes) {
|
||||||
text?.removeTextChangedListener(this)
|
text?.removeTextChangedListener(this)
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.DonationP
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.util.InternetConnectionObserver
|
import org.thoughtcrime.securesms.util.InternetConnectionObserver
|
||||||
import org.thoughtcrime.securesms.util.PlatformCurrencyUtil
|
import org.thoughtcrime.securesms.util.PlatformCurrencyUtil
|
||||||
|
import org.thoughtcrime.securesms.util.StringUtil
|
||||||
import org.thoughtcrime.securesms.util.livedata.Store
|
import org.thoughtcrime.securesms.util.livedata.Store
|
||||||
import java.lang.NumberFormatException
|
import java.lang.NumberFormatException
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
@ -196,7 +197,8 @@ class BoostViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCustomAmount(amount: String) {
|
fun setCustomAmount(rawAmount: String) {
|
||||||
|
val amount = StringUtil.stripBidiIndicator(rawAmount)
|
||||||
val bigDecimalAmount: BigDecimal = if (amount.isEmpty() || amount == DecimalFormatSymbols.getInstance().decimalSeparator.toString()) {
|
val bigDecimalAmount: BigDecimal = if (amount.isEmpty() || amount == DecimalFormatSymbols.getInstance().decimalSeparator.toString()) {
|
||||||
BigDecimal.ZERO
|
BigDecimal.ZERO
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -233,6 +233,10 @@ public final class StringUtil {
|
||||||
return text.replaceAll("[\\u2068\\u2069\\u202c]", "");
|
return text.replaceAll("[\\u2068\\u2069\\u202c]", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull String stripBidiIndicator(@NonNull String text) {
|
||||||
|
return text.replace("\u200F", "");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trims a {@link CharSequence} of starting and trailing whitespace. Behavior matches
|
* Trims a {@link CharSequence} of starting and trailing whitespace. Behavior matches
|
||||||
* {@link String#trim()} to preserve expectations around results.
|
* {@link String#trim()} to preserve expectations around results.
|
||||||
|
|
|
@ -20,6 +20,7 @@ class BoostTest__MoneyFilter {
|
||||||
|
|
||||||
private val usd = Currency.getInstance("USD")
|
private val usd = Currency.getInstance("USD")
|
||||||
private val yen = Currency.getInstance("JPY")
|
private val yen = Currency.getInstance("JPY")
|
||||||
|
private val inr = Currency.getInstance("INR")
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
@ -142,4 +143,40 @@ class BoostTest__MoneyFilter {
|
||||||
|
|
||||||
assertNotNull(filterResult)
|
assertNotNull(filterResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Given MR and INR, when I enter 5dot55, then I expect localized`() {
|
||||||
|
Locale.setDefault(Locale.forLanguageTag("mr"))
|
||||||
|
|
||||||
|
val testSubject = Boost.MoneyFilter(inr)
|
||||||
|
val editable = SpannableStringBuilder("5.55")
|
||||||
|
|
||||||
|
testSubject.afterTextChanged(editable)
|
||||||
|
|
||||||
|
assertEquals("₹५.५५", editable.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Given MR and INR, when I enter dot, then I expect it to be retained in output`() {
|
||||||
|
Locale.setDefault(Locale.forLanguageTag("mr"))
|
||||||
|
|
||||||
|
val testSubject = Boost.MoneyFilter(inr)
|
||||||
|
val editable = SpannableStringBuilder("₹५.")
|
||||||
|
|
||||||
|
testSubject.afterTextChanged(editable)
|
||||||
|
|
||||||
|
assertEquals("₹५.", editable.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Given RTL indicator, when I enter five, then I expect successful match`() {
|
||||||
|
val testSubject = Boost.MoneyFilter(yen)
|
||||||
|
val editable = SpannableStringBuilder("\u200F5")
|
||||||
|
val dest = SpannableStringBuilder()
|
||||||
|
|
||||||
|
testSubject.afterTextChanged(editable)
|
||||||
|
val filterResult = testSubject.filter(editable, 0, editable.length, dest, 0, 0)
|
||||||
|
|
||||||
|
assertNull(filterResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ public class FiatMoney {
|
||||||
* @return amount, in smallest possible units (cents, yen, etc.)
|
* @return amount, in smallest possible units (cents, yen, etc.)
|
||||||
*/
|
*/
|
||||||
public @NonNull String getMinimumUnitPrecisionString() {
|
public @NonNull String getMinimumUnitPrecisionString() {
|
||||||
NumberFormat formatter = NumberFormat.getInstance();
|
NumberFormat formatter = NumberFormat.getInstance(Locale.US);
|
||||||
formatter.setMaximumFractionDigits(0);
|
formatter.setMaximumFractionDigits(0);
|
||||||
formatter.setGroupingUsed(false);
|
formatter.setGroupingUsed(false);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue