kopia lustrzana https://github.com/ryukoposting/Signal-Android
Improve text entry for boosts.
rodzic
131a400921
commit
84833c9ad3
|
@ -3,15 +3,17 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.boost
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorSet
|
import android.animation.AnimatorSet
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.text.Editable
|
import android.text.InputFilter
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.text.method.DigitsKeyListener
|
import android.text.method.DigitsKeyListener
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.VisibleForTesting
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.appcompat.widget.AppCompatEditText
|
import androidx.appcompat.widget.AppCompatEditText
|
||||||
import androidx.core.animation.doOnEnd
|
import androidx.core.animation.doOnEnd
|
||||||
import androidx.core.text.isDigitsOnly
|
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import org.signal.core.util.money.FiatMoney
|
import org.signal.core.util.money.FiatMoney
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
|
@ -21,14 +23,12 @@ 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 org.thoughtcrime.securesms.util.text.AfterTextChanged
|
||||||
|
import org.thoughtcrime.securesms.util.visible
|
||||||
import java.text.DecimalFormatSymbols
|
import java.text.DecimalFormatSymbols
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import java.util.Currency
|
import java.util.Currency
|
||||||
import java.util.Locale
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Signal Boost is a one-time ephemeral show of support. Each boost level
|
* A Signal Boost is a one-time ephemeral show of support. Each boost level
|
||||||
|
@ -122,8 +122,12 @@ data class Boost(
|
||||||
private val boost4: MaterialButton = itemView.findViewById(R.id.boost_4)
|
private val boost4: MaterialButton = itemView.findViewById(R.id.boost_4)
|
||||||
private val boost5: MaterialButton = itemView.findViewById(R.id.boost_5)
|
private val boost5: MaterialButton = itemView.findViewById(R.id.boost_5)
|
||||||
private val boost6: MaterialButton = itemView.findViewById(R.id.boost_6)
|
private val boost6: MaterialButton = itemView.findViewById(R.id.boost_6)
|
||||||
|
private val currencyStart: TextView = itemView.findViewById(R.id.boost_currency_start)
|
||||||
|
private val currencyEnd: TextView = itemView.findViewById(R.id.boost_currency_end)
|
||||||
private val custom: AppCompatEditText = itemView.findViewById(R.id.boost_custom)
|
private val custom: AppCompatEditText = itemView.findViewById(R.id.boost_custom)
|
||||||
|
|
||||||
|
private var textChangedWatcher: TextWatcher? = null
|
||||||
|
|
||||||
private val boostButtons: List<MaterialButton>
|
private val boostButtons: List<MaterialButton>
|
||||||
get() {
|
get() {
|
||||||
return if (ViewUtil.isLtr(context)) {
|
return if (ViewUtil.isLtr(context)) {
|
||||||
|
@ -133,8 +137,6 @@ data class Boost(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var filter: MoneyFilter? = null
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
custom.filters = emptyArray()
|
custom.filters = emptyArray()
|
||||||
}
|
}
|
||||||
|
@ -157,20 +159,33 @@ data class Boost(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter == null || filter?.currency != model.currency) {
|
currencyStart.text = model.currency.symbol
|
||||||
custom.removeTextChangedListener(filter)
|
currencyEnd.text = model.currency.symbol
|
||||||
|
|
||||||
filter = MoneyFilter(model.currency, custom) {
|
if (model.currency.defaultFractionDigits > 0) {
|
||||||
model.onCustomAmountChanged(it)
|
custom.inputType = EditorInfo.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_FLAG_DECIMAL
|
||||||
}
|
custom.filters = arrayOf(DecimalPlacesFilter(model.currency.defaultFractionDigits, custom.keyListener as DigitsKeyListener))
|
||||||
|
} else {
|
||||||
custom.keyListener = filter
|
custom.inputType = EditorInfo.TYPE_CLASS_NUMBER
|
||||||
custom.addTextChangedListener(filter)
|
custom.filters = arrayOf()
|
||||||
|
|
||||||
custom.setText("")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
custom.removeTextChangedListener(textChangedWatcher)
|
||||||
|
|
||||||
|
textChangedWatcher = AfterTextChanged {
|
||||||
|
model.onCustomAmountChanged(it.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
custom.addTextChangedListener(textChangedWatcher)
|
||||||
|
custom.setText("")
|
||||||
|
|
||||||
custom.setOnFocusChangeListener { _, hasFocus ->
|
custom.setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
val isCurrencyAtFrontOfNumber = currencyIsAtFrontOfNumber(model.currency)
|
||||||
|
|
||||||
|
currencyStart.visible = isCurrencyAtFrontOfNumber && hasFocus
|
||||||
|
currencyEnd.visible = !isCurrencyAtFrontOfNumber && hasFocus
|
||||||
|
|
||||||
|
custom.gravity = if (hasFocus) (Gravity.START or Gravity.CENTER_VERTICAL) else Gravity.CENTER
|
||||||
model.onCustomAmountFocusChanged(hasFocus)
|
model.onCustomAmountFocusChanged(hasFocus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +196,51 @@ data class Boost(
|
||||||
custom.clearFocus()
|
custom.clearFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun currencyIsAtFrontOfNumber(currency: Currency): Boolean {
|
||||||
|
val formatter = NumberFormat.getCurrencyInstance().apply {
|
||||||
|
this.currency = currency
|
||||||
|
}
|
||||||
|
return formatter.format(1).startsWith(currency.symbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restricts output of the given Digits filter to the given number of decimal places.
|
||||||
|
*/
|
||||||
|
private class DecimalPlacesFilter(private val decimalPlaces: Int, private val digitsKeyListener: DigitsKeyListener) : InputFilter {
|
||||||
|
|
||||||
|
private val decimalSeparator = DecimalFormatSymbols.getInstance().decimalSeparator
|
||||||
|
private val builder = SpannableStringBuilder()
|
||||||
|
|
||||||
|
override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
|
||||||
|
val keyListenerResult = digitsKeyListener.filter(source, start, end, dest, dstart, dend)
|
||||||
|
|
||||||
|
builder.clear()
|
||||||
|
builder.clearSpans()
|
||||||
|
|
||||||
|
val toInsert = keyListenerResult ?: source.substring(start, end)
|
||||||
|
|
||||||
|
builder.append(dest)
|
||||||
|
|
||||||
|
if (dstart == dend) {
|
||||||
|
builder.insert(dstart, toInsert)
|
||||||
|
} else {
|
||||||
|
builder.replace(dstart, dend, toInsert)
|
||||||
|
}
|
||||||
|
|
||||||
|
val separatorIndex = builder.indexOf(decimalSeparator)
|
||||||
|
return if (separatorIndex > -1) {
|
||||||
|
val suffix = builder.split(decimalSeparator).last()
|
||||||
|
if (suffix.length > decimalPlaces) {
|
||||||
|
dest.subSequence(dstart, dend)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HeadingViewHolder(itemView: View) : MappingViewHolder<HeadingModel>(itemView) {
|
private class HeadingViewHolder(itemView: View) : MappingViewHolder<HeadingModel>(itemView) {
|
||||||
|
@ -192,121 +252,6 @@ data class Boost(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
class MoneyFilter(val currency: Currency, private val text: AppCompatEditText? = null, private val onCustomAmountChanged: (String) -> Unit = {}) : DigitsKeyListener(false, true), TextWatcher {
|
|
||||||
|
|
||||||
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
|
|
||||||
val separatorCount = min(1, currency.defaultFractionDigits)
|
|
||||||
val symbol: String = currency.getSymbol(Locale.getDefault())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 zeros: String = "\\u0030|\\u0660|\\u06F0|\\u0966|\\uFF10"
|
|
||||||
|
|
||||||
val pattern: Pattern = "($digitsGroup)*([$separator]){0,$separatorCount}($digitsGroup){0,${currency.defaultFractionDigits}}".toPattern()
|
|
||||||
val symbolPattern: Regex = """\s*${Regex.escape(symbol)}\s*""".toRegex()
|
|
||||||
val leadingZeroesPattern: Regex = """^($zeros)*""".toRegex()
|
|
||||||
|
|
||||||
override fun filter(
|
|
||||||
source: CharSequence,
|
|
||||||
start: Int,
|
|
||||||
end: Int,
|
|
||||||
dest: Spanned,
|
|
||||||
dstart: Int,
|
|
||||||
dend: Int
|
|
||||||
): CharSequence? {
|
|
||||||
|
|
||||||
val result = dest.subSequence(0, dstart).toString() + source.toString() + dest.subSequence(dend, dest.length)
|
|
||||||
val resultWithoutCurrencyPrefix = StringUtil.stripBidiIndicator(result.removePrefix(symbol).removeSuffix(symbol).trim())
|
|
||||||
|
|
||||||
if (resultWithoutCurrencyPrefix.length == 1 && !resultWithoutCurrencyPrefix.isDigitsOnly() && resultWithoutCurrencyPrefix != separator.toString()) {
|
|
||||||
return dest.subSequence(dstart, dend)
|
|
||||||
}
|
|
||||||
|
|
||||||
val matcher = pattern.matcher(resultWithoutCurrencyPrefix)
|
|
||||||
|
|
||||||
if (!matcher.matches()) {
|
|
||||||
return dest.subSequence(dstart, dend)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
|
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable?) {
|
|
||||||
if (s.isNullOrEmpty()) return
|
|
||||||
|
|
||||||
val hasSymbol = s.startsWith(symbol) || s.endsWith(symbol)
|
|
||||||
if (hasSymbol && symbolPattern.matchEntire(s.toString()) != null) {
|
|
||||||
s.clear()
|
|
||||||
} else if (!hasSymbol) {
|
|
||||||
val formatter = NumberFormat.getCurrencyInstance()
|
|
||||||
formatter.currency = currency
|
|
||||||
|
|
||||||
if (s.contains(separator)) {
|
|
||||||
formatter.minimumFractionDigits = s.split(separator).last().length
|
|
||||||
} else {
|
|
||||||
formatter.minimumFractionDigits = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
formatter.maximumFractionDigits = currency.defaultFractionDigits
|
|
||||||
|
|
||||||
val value = s.toString().toDoubleOrNull()
|
|
||||||
|
|
||||||
if (value != null) {
|
|
||||||
val formatted = formatter.format(value)
|
|
||||||
|
|
||||||
text?.removeTextChangedListener(this)
|
|
||||||
|
|
||||||
s.replace(0, s.length, formatted)
|
|
||||||
if (formatted.endsWith(symbol)) {
|
|
||||||
val result: MatchResult? = symbolPattern.find(formatted)
|
|
||||||
if (result != null && result.range.first < s.length) {
|
|
||||||
text?.setSelection(result.range.first)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
text?.addTextChangedListener(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val withoutSymbol = s.removePrefix(symbol).removeSuffix(symbol).trim().toString()
|
|
||||||
val withoutLeadingZeroes: String = try {
|
|
||||||
NumberFormat.getInstance().apply {
|
|
||||||
isGroupingUsed = false
|
|
||||||
|
|
||||||
if (s.contains(separator)) {
|
|
||||||
minimumFractionDigits = s.split(separator).last().length
|
|
||||||
}
|
|
||||||
}.format(withoutSymbol.toBigDecimal()) + (if (withoutSymbol.endsWith(separator)) separator else "")
|
|
||||||
} catch (e: NumberFormatException) {
|
|
||||||
withoutSymbol
|
|
||||||
}
|
|
||||||
|
|
||||||
if (withoutSymbol != withoutLeadingZeroes) {
|
|
||||||
text?.removeTextChangedListener(this)
|
|
||||||
|
|
||||||
val start = s.indexOf(withoutSymbol)
|
|
||||||
s.replace(start, start + withoutSymbol.length, withoutLeadingZeroes)
|
|
||||||
|
|
||||||
text?.addTextChangedListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
onCustomAmountChanged(s.removePrefix(symbol).removeSuffix(symbol).trim().toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun register(adapter: MappingAdapter) {
|
fun register(adapter: MappingAdapter) {
|
||||||
adapter.registerFactory(SelectionModel::class.java, MappingAdapter.LayoutFactory({ SelectionViewHolder(it) }, R.layout.boost_preference))
|
adapter.registerFactory(SelectionModel::class.java, MappingAdapter.LayoutFactory({ SelectionViewHolder(it) }, R.layout.boost_preference))
|
||||||
|
|
|
@ -23,7 +23,6 @@ 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.StringUtil
|
||||||
import org.thoughtcrime.securesms.util.livedata.Store
|
import org.thoughtcrime.securesms.util.livedata.Store
|
||||||
import java.lang.NumberFormatException
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.text.DecimalFormatSymbols
|
import java.text.DecimalFormatSymbols
|
||||||
|
@ -194,7 +193,8 @@ class BoostViewModel(
|
||||||
store.update {
|
store.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isCustomAmountFocused = false,
|
isCustomAmountFocused = false,
|
||||||
selectedBoost = boost
|
selectedBoost = boost,
|
||||||
|
customAmount = FiatMoney(BigDecimal.ZERO, it.currencySelection)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,7 +218,9 @@ class BoostViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCustomAmountFocused(isFocused: Boolean) {
|
fun setCustomAmountFocused(isFocused: Boolean) {
|
||||||
store.update { it.copy(isCustomAmountFocused = isFocused) }
|
store.update {
|
||||||
|
it.copy(isCustomAmountFocused = isFocused)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class BoostInfo(val boosts: List<Boost>, val defaultBoost: Boost?, val boostBadge: Badge, val supportedCurrencies: Set<Currency>)
|
private data class BoostInfo(val boosts: List<Boost>, val defaultBoost: Boost?, val boostBadge: Badge, val supportedCurrencies: Set<Currency>)
|
||||||
|
|
|
@ -112,6 +112,23 @@
|
||||||
app:strokeWidth="1.5dp"
|
app:strokeWidth="1.5dp"
|
||||||
tools:text="$100" />
|
tools:text="$100" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/boost_currency_start"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48sp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:background="@drawable/rounded_rectangle_secondary"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="48sp"
|
||||||
|
android:textAppearance="@style/Signal.Text.Body"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/boost_custom"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/boost_custom"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/boost_custom"
|
||||||
|
tools:text="$"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatEditText
|
<androidx.appcompat.widget.AppCompatEditText
|
||||||
android:id="@+id/boost_custom"
|
android:id="@+id/boost_custom"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -122,12 +139,29 @@
|
||||||
android:hint="@string/Boost__enter_custom_amount"
|
android:hint="@string/Boost__enter_custom_amount"
|
||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
android:inputType="numberDecimal"
|
android:inputType="numberDecimal"
|
||||||
|
android:paddingStart="21dp"
|
||||||
|
android:paddingEnd="21dp"
|
||||||
android:textAppearance="@style/Signal.Text.Body"
|
android:textAppearance="@style/Signal.Text.Body"
|
||||||
android:textColor="@color/signal_text_primary"
|
android:textColor="@color/signal_text_primary"
|
||||||
android:textColorHint="@color/signal_text_primary"
|
android:textColorHint="@color/signal_text_primary"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toStartOf="@id/boost_currency_end"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toEndOf="@id/boost_currency_start"
|
||||||
app:layout_constraintTop_toBottomOf="@id/boost_4" />
|
app:layout_constraintTop_toBottomOf="@id/boost_4" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/boost_currency_end"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48sp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:background="@drawable/rounded_rectangle_secondary"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="48sp"
|
||||||
|
android:textAppearance="@style/Signal.Text.Body"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/boost_custom"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/boost_custom"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/boost_custom"
|
||||||
|
tools:text="$" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,234 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.components.settings.app.subscription.boost
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.text.SpannableStringBuilder
|
|
||||||
import junit.framework.Assert.assertEquals
|
|
||||||
import junit.framework.Assert.assertNotNull
|
|
||||||
import junit.framework.Assert.assertNull
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.robolectric.RobolectricTestRunner
|
|
||||||
import org.robolectric.annotation.Config
|
|
||||||
import java.util.Currency
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
@Suppress("ClassName")
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
|
||||||
@Config(manifest = Config.NONE, application = Application::class)
|
|
||||||
class BoostTest__MoneyFilter {
|
|
||||||
|
|
||||||
private val usd = Currency.getInstance("USD")
|
|
||||||
private val yen = Currency.getInstance("JPY")
|
|
||||||
private val inr = Currency.getInstance("INR")
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
Locale.setDefault(Locale.US)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given USD, when I enter 5, then I expect $ 5`() {
|
|
||||||
val testSubject = Boost.MoneyFilter(usd)
|
|
||||||
val editable = SpannableStringBuilder("5")
|
|
||||||
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
|
|
||||||
assertEquals("$5", editable.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given USD, when I enter 5dot00, then I expect successful filter`() {
|
|
||||||
val testSubject = Boost.MoneyFilter(usd)
|
|
||||||
val editable = SpannableStringBuilder("5.00")
|
|
||||||
val dest = SpannableStringBuilder()
|
|
||||||
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
val filterResult = testSubject.filter(editable, 0, editable.length, dest, 0, 0)
|
|
||||||
|
|
||||||
assertNull(filterResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given USD, when I enter 5dot00, then I expect 5 from text change`() {
|
|
||||||
var result = ""
|
|
||||||
val testSubject = Boost.MoneyFilter(usd) {
|
|
||||||
result = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val editable = SpannableStringBuilder("5.00")
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
|
|
||||||
assertEquals("5.00", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given USD, when I enter 00005dot00, then I expect 5 from text change`() {
|
|
||||||
val testSubject = Boost.MoneyFilter(usd)
|
|
||||||
val editable = SpannableStringBuilder("00005.00")
|
|
||||||
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
|
|
||||||
assertEquals("$5.00", editable.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given USD, when I enter 5dot000, then I expect successful filter`() {
|
|
||||||
val testSubject = Boost.MoneyFilter(yen)
|
|
||||||
val editable = SpannableStringBuilder("5.000")
|
|
||||||
val dest = SpannableStringBuilder()
|
|
||||||
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
val filterResult = testSubject.filter(editable, 0, editable.length, dest, 0, 0)
|
|
||||||
|
|
||||||
assertNull(filterResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given USD, when I enter 5dot, then I expect successful filter`() {
|
|
||||||
val testSubject = Boost.MoneyFilter(usd)
|
|
||||||
val editable = SpannableStringBuilder("5.")
|
|
||||||
val dest = SpannableStringBuilder()
|
|
||||||
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
val filterResult = testSubject.filter(editable, 0, editable.length, dest, 0, 0)
|
|
||||||
|
|
||||||
assertNull(filterResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given JPY, when I enter 5, then I expect yen 5`() {
|
|
||||||
val testSubject = Boost.MoneyFilter(yen)
|
|
||||||
val editable = SpannableStringBuilder("5")
|
|
||||||
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
|
|
||||||
assertEquals("¥5", editable.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given JPY, when I enter 5, then I expect 5 from text change`() {
|
|
||||||
var result = ""
|
|
||||||
val testSubject = Boost.MoneyFilter(yen) {
|
|
||||||
result = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val editable = SpannableStringBuilder("5")
|
|
||||||
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
|
|
||||||
assertEquals("5", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given JPY, when I enter 5, then I expect successful filter`() {
|
|
||||||
val testSubject = Boost.MoneyFilter(yen)
|
|
||||||
val editable = SpannableStringBuilder("5")
|
|
||||||
val dest = SpannableStringBuilder()
|
|
||||||
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
val filterResult = testSubject.filter(editable, 0, editable.length, dest, 0, 0)
|
|
||||||
|
|
||||||
assertNull(filterResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given JPY, when I enter 5dot, then I expect unsuccessful filter`() {
|
|
||||||
val testSubject = Boost.MoneyFilter(yen)
|
|
||||||
val editable = SpannableStringBuilder("¥5.")
|
|
||||||
val dest = SpannableStringBuilder()
|
|
||||||
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
val filterResult = testSubject.filter(editable, 0, editable.length, dest, 0, 0)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given USD, when I enter 1dot05, then I expect 1dot05`() {
|
|
||||||
var result = ""
|
|
||||||
val testSubject = Boost.MoneyFilter(usd) {
|
|
||||||
result = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val editable = SpannableStringBuilder("$1.05")
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
|
|
||||||
assertEquals("1.05", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given USD, when I enter 0dot05, then I expect 0dot05`() {
|
|
||||||
var result = ""
|
|
||||||
val testSubject = Boost.MoneyFilter(usd) {
|
|
||||||
result = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val editable = SpannableStringBuilder("$0.05")
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
|
|
||||||
assertEquals("0.05", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given USD, when I enter dot1, then I expect 0dot1`() {
|
|
||||||
var result = ""
|
|
||||||
val testSubject = Boost.MoneyFilter(usd) {
|
|
||||||
result = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val editable = SpannableStringBuilder("$.1")
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
|
|
||||||
assertEquals("0.1", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Given USD, when I enter dot0, then I expect 0dot0`() {
|
|
||||||
var result = ""
|
|
||||||
val testSubject = Boost.MoneyFilter(usd) {
|
|
||||||
result = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val editable = SpannableStringBuilder(".0")
|
|
||||||
testSubject.afterTextChanged(editable)
|
|
||||||
|
|
||||||
assertEquals("0.0", result)
|
|
||||||
}
|
|
||||||
}
|
|
Ładowanie…
Reference in New Issue