Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-21 09:42:01 +03:00
parent c1e1458f3f
commit 0de667c1d1
5 changed files with 94 additions and 10 deletions

View file

@ -143,7 +143,8 @@ fun BigDecimalCryptoFormatStyled.defaultAmount(spanStyleReference: SpanStyleRefe
val formattedAmount = formatter.format(value)
val decimalSeparator = (formatter as? DecimalFormat)?.decimalFormatSymbols?.decimalSeparator
val separatorIndex = decimalSeparator?.let { formattedAmount.indexOf(it) } ?: formattedAmount.length
val separatorIndex = decimalSeparator?.let { formattedAmount.indexOf(it).takeIf { i -> i >= 0 } }
?: formattedAmount.length
combinedReference(
stringReference(formattedAmount.take(separatorIndex)),
@ -165,8 +166,9 @@ fun BigDecimalCryptoFormatStyled.defaultAmount(spanStyleReference: SpanStyleRefe
cryptoCurrencySymbol = symbol,
)
val decimalSeparator = (formatter as? DecimalFormat)?.decimalFormatSymbols?.decimalSeparator
val separatorIndex = decimalSeparator?.let { formattedAmount.indexOf(it) } ?: formattedAmount.length
val decimalSeparator = (formatter as? DecimalFormat)?.decimalFormatSymbols?.monetaryDecimalSeparator
val separatorIndex = decimalSeparator?.let { formattedAmount.indexOf(it).takeIf { i -> i >= 0 } }
?: formattedAmount.length
combinedReference(
stringReference(formattedAmount.take(separatorIndex)),

View file

@ -97,7 +97,7 @@ fun BigDecimalFiatFormatStyled.defaultAmount(spanStyleReference: SpanStyleRefere
value.zeroIfRoundsToZero(FIAT_MARKET_DEFAULT_DIGITS)
}
val decimalSeparator = (formatter as? DecimalFormat)?.decimalFormatSymbols?.decimalSeparator
val decimalSeparator = (formatter as? DecimalFormat)?.decimalFormatSymbols?.monetaryDecimalSeparator
val currencySymbol = formatterCurrency.getSymbol(locale)
val rawFormatted = formatter.format(formattingAmount)
@ -200,7 +200,7 @@ private fun BigDecimalFiatFormatStyled.price(spanStyleReference: SpanStyleRefere
roundingMode = RoundingMode.HALF_UP
}
val decimalSeparator = (formatter as? DecimalFormat)?.decimalFormatSymbols?.decimalSeparator
val decimalSeparator = (formatter as? DecimalFormat)?.decimalFormatSymbols?.monetaryDecimalSeparator
val currencySymbol = formatterCurrency.getSymbol(locale)
val rawFormatted = formatter.format(priceAmount)

View file

@ -1,8 +1,13 @@
package com.tangem.core.ui.format.bigdecimal
import androidx.compose.ui.text.SpanStyle
import com.google.common.truth.Truth
import com.tangem.core.ui.extensions.SpanStyleReference
import com.tangem.core.ui.extensions.TextReference
import org.junit.jupiter.api.Test
import java.math.BigDecimal
import java.text.DecimalFormat
import java.text.NumberFormat
import java.util.Locale
internal class BigDecimalCryptoFormatTest {
@ -10,6 +15,7 @@ internal class BigDecimalCryptoFormatTest {
private val testLocale = Locale.US
private val testLocale2 = Locale.GERMANY
private val symbol = "BTC"
private val spanStyleStub = SpanStyleReference { SpanStyle() }
// === defaultAmount() ===
@ -125,6 +131,39 @@ internal class BigDecimalCryptoFormatTest {
.isEqualTo("12,345,678.11".addSymbolWithSpaceLeft(symbol))
}
// === defaultAmount() styled ===
@Test
fun `GIVEN locale with distinct monetary separator WHEN styled defaultAmount THEN fraction split without crash`() {
// Arrange
// Regression: fr_CH plain separator is ',' but currency output uses '.' — indexOf(',') returned -1,
// and formattedAmount.take(-1) threw IllegalArgumentException
val swissLocale = Locale("fr", "CH")
val symbols = (NumberFormat.getCurrencyInstance(swissLocale) as DecimalFormat).decimalFormatSymbols
Truth.assertThat(symbols.monetaryDecimalSeparator).isNotEqualTo(symbols.decimalSeparator)
val testValue = BigDecimal("12.34")
// Act
val formatted = testValue.formatStyled {
cryptoStyled(
symbol = symbol,
decimals = 8,
spanStyleReference = spanStyleStub,
locale = swissLocale,
)
}
// Assert
val refs = (formatted as TextReference.Combined).refs.data
Truth.assertThat(refs).hasSize(2)
Truth.assertThat((refs[0] as TextReference.Str).value).isEqualTo("12")
val fraction = refs[1] as TextReference.StyledStr
Truth.assertThat(fraction.value).startsWith("${symbols.monetaryDecimalSeparator}34")
Truth.assertThat(fraction.value).endsWith(symbol)
}
// === shorted() ===
@Test

View file

@ -1,11 +1,16 @@
package com.tangem.core.ui.format.bigdecimal
import androidx.compose.ui.text.SpanStyle
import com.google.common.truth.Truth
import com.tangem.core.ui.extensions.SpanStyleReference
import com.tangem.core.ui.extensions.TextReference
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.math.BigDecimal
import java.text.DecimalFormat
import java.text.NumberFormat
import java.util.Locale
internal class BigDecimalFiatFormatTest {
@ -16,6 +21,8 @@ internal class BigDecimalFiatFormatTest {
val usdCurrencyCode = "USD"
val usdSymbol = "$"
private val spanStyleStub = SpanStyleReference { SpanStyle() }
private fun String.addUsdSymbolLeft() = usdSymbol + this
// === defaultAmount() ===
@ -132,6 +139,40 @@ internal class BigDecimalFiatFormatTest {
.isEqualTo("-" + "0.01".addUsdSymbolLeft())
}
// === defaultAmount() styled ===
@Test
fun `GIVEN locale with distinct monetary separator WHEN styled defaultAmount THEN fraction split at monetary separator`() {
// Arrange
// fr_CH plain separator is ',' but currency output uses '.' — searching for the plain one
// failed to split the amount into whole and styled fractional parts
val swissLocale = Locale("fr", "CH")
val symbols = (NumberFormat.getCurrencyInstance(swissLocale) as DecimalFormat).decimalFormatSymbols
Truth.assertThat(symbols.monetaryDecimalSeparator).isNotEqualTo(symbols.decimalSeparator)
val testValue = BigDecimal("12.34")
// Act
val formatted = testValue.formatStyled {
fiat(
fiatCurrencyCode = usdCurrencyCode,
fiatCurrencySymbol = usdSymbol,
spanStyleReference = spanStyleStub,
locale = swissLocale,
)
}
// Assert
val refs = (formatted as TextReference.Combined).refs.data
Truth.assertThat(refs).hasSize(3)
Truth.assertThat(refs[0]).isEqualTo(TextReference.EMPTY)
Truth.assertThat((refs[1] as TextReference.Str).value).isEqualTo("12")
val fraction = refs[2] as TextReference.StyledStr
Truth.assertThat(fraction.value).startsWith("${symbols.monetaryDecimalSeparator}34")
Truth.assertThat(fraction.value).endsWith(usdSymbol)
}
// === approximateAmount() ===
@Test

View file

@ -2,17 +2,19 @@
Generates Kotlin (Jetpack Compose) source files from design tokens and icons defined in the `ds-tokens` git submodule.
## Making sure submodule is at the pinned commit
***For the most cases*** (a fresh checkout, or making sure the submodule is at the pinned commit), use:
```bash
git submodule update --init --recursive
```
## Updating tokens
> **Note:** You only need `git submodule update --remote` when you want to pull **new** design tokens
> from the remote `ds-tokens` repository. If you're just regenerating Kotlin from the tokens already
> checked out (e.g. changing the generation script), **skip step 1** — don't run it without the need,
> as it moves the submodule pointer to the latest remote commit and pulls in unrelated token changes.
>
> For all other cases (a fresh checkout, or making sure the submodule is at the pinned commit), use:
> ```bash
> git submodule update --init --recursive
> ```
> This checks out the submodule at the commit already recorded in the repo, without pulling anything new.
1. *(Only if you need newer tokens)* Update the `ds-tokens` submodule to the latest commit: