diff --git a/app/src/main/java/to/bitkit/data/widgets/WeatherService.kt b/app/src/main/java/to/bitkit/data/widgets/WeatherService.kt
index 81869f75d..dda33e962 100644
--- a/app/src/main/java/to/bitkit/data/widgets/WeatherService.kt
+++ b/app/src/main/java/to/bitkit/data/widgets/WeatherService.kt
@@ -139,8 +139,8 @@ class WeatherService @Inject constructor(
}
private fun formatFeeForDisplay(sats: Long): String {
- val usdValue = currencyRepo.convertSatsToFiat(sats, USD).getOrNull()
- return usdValue?.formatted.orEmpty()
+ val selectedFiatValue = currencyRepo.convertSatsToFiat(sats).getOrNull()
+ return selectedFiatValue?.formattedWithSymbol(withSpace = true).orEmpty()
}
}
diff --git a/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt b/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt
index fb17d1adf..57af17d2b 100644
--- a/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt
+++ b/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt
@@ -111,9 +111,9 @@ class NotifyPaymentReceivedHandler @Inject constructor(
val amountText = converted?.let {
val btcDisplay = it.bitcoinDisplay(settings.displayUnit)
if (settings.primaryDisplay == PrimaryDisplay.BITCOIN) {
- "${btcDisplay.symbol} ${btcDisplay.value} (${it.symbol}${it.formatted})"
+ "${btcDisplay.symbol} ${btcDisplay.value} (${it.formattedWithSymbol()})"
} else {
- "${it.symbol}${it.formatted} (${btcDisplay.symbol} ${btcDisplay.value})"
+ "${it.formattedWithSymbol()} (${btcDisplay.symbol} ${btcDisplay.value})"
}
} ?: "$BITCOIN_SYMBOL ${sats.formatToModernDisplay()}"
diff --git a/app/src/main/java/to/bitkit/models/Currency.kt b/app/src/main/java/to/bitkit/models/Currency.kt
index 9d5256caa..b37612564 100644
--- a/app/src/main/java/to/bitkit/models/Currency.kt
+++ b/app/src/main/java/to/bitkit/models/Currency.kt
@@ -76,6 +76,8 @@ data class ConvertedAmount(
val sats: Long,
val locale: Locale = Locale.getDefault(),
) {
+ val isSymbolSuffix: Boolean get() = currency in SUFFIX_SYMBOL_CURRENCIES
+
data class BitcoinDisplayComponents(
val symbol: String,
val value: String,
@@ -88,6 +90,12 @@ data class ConvertedAmount(
value = formattedValue,
)
}
+
+ fun formattedWithSymbol(withSpace: Boolean = false): String = value.formatCurrencyWithSymbol(
+ currencyCode = currency,
+ currencySymbol = symbol,
+ withSpace = withSpace,
+ )
}
fun Long.formatMoney(
@@ -145,5 +153,42 @@ fun BigDecimal.formatCurrency(decimalPlaces: Int = FIAT_DECIMALS, locale: Locale
return runCatching { formatter.format(this) }.getOrNull()
}
+fun BigDecimal.formatCurrencyWithSymbol(
+ currencyCode: String,
+ currencySymbol: String? = null,
+ withSpace: Boolean = false,
+ decimalPlaces: Int = FIAT_DECIMALS,
+): String {
+ val formatted = formatCurrency(decimalPlaces) ?: "0.00"
+ val symbol = currencySymbol
+ ?: runCatching { java.util.Currency.getInstance(currencyCode) }.getOrNull()?.symbol
+ ?: currencyCode
+ val separator = if (withSpace) " " else ""
+
+ return if (currencyCode in SUFFIX_SYMBOL_CURRENCIES) {
+ "$formatted$separator$symbol"
+ } else {
+ "$symbol$separator$formatted"
+ }
+}
+
+fun isSuffixSymbolCurrency(currencyCode: String): Boolean = currencyCode in SUFFIX_SYMBOL_CURRENCIES
+
+private val SUFFIX_SYMBOL_CURRENCIES = setOf(
+ "BGN",
+ "CHF",
+ "CZK",
+ "DKK",
+ "HRK",
+ "HUF",
+ "ISK",
+ "NOK",
+ "PLN",
+ "RON",
+ "RUB",
+ "SEK",
+ "TRY",
+)
+
/** Represent this sat value in Bitcoin BigDecimal. */
fun Long.asBtc(): BigDecimal = BigDecimal(this).divide(BigDecimal(SATS_IN_BTC), BTC_SCALE, RoundingMode.HALF_UP)
diff --git a/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt b/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt
index 565cbd41b..fe8505b24 100644
--- a/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt
+++ b/app/src/main/java/to/bitkit/ui/components/BalanceHeaderView.kt
@@ -89,10 +89,12 @@ fun BalanceHeaderView(
modifier = modifier,
smallRowSymbol = if (isBitcoinPrimary) fiat.symbol else btc.symbol,
smallRowText = if (isBitcoinPrimary) fiat.formatted else btc.value,
+ smallRowIsSymbolSuffix = if (isBitcoinPrimary) fiat.isSymbolSuffix else false,
smallRowModifier = Modifier.testTag("$testTag-secondary"),
largeRowPrefix = prefix,
largeRowText = if (isBitcoinPrimary) btc.value else fiat.formatted,
largeRowSymbol = if (isBitcoinPrimary) btc.symbol else fiat.symbol,
+ largeRowIsSymbolSuffix = if (isBitcoinPrimary) false else fiat.isSymbolSuffix,
largeRowModifier = Modifier.testTag("$testTag-primary"),
showSymbol = if (isBitcoinPrimary) showBitcoinSymbol else true,
hideBalance = shouldHideBalance,
@@ -110,10 +112,12 @@ fun BalanceHeader(
modifier: Modifier = Modifier,
smallRowSymbol: String? = null,
smallRowText: String,
+ smallRowIsSymbolSuffix: Boolean = false,
smallRowModifier: Modifier = Modifier,
largeRowPrefix: String? = null,
largeRowText: String,
largeRowSymbol: String,
+ largeRowIsSymbolSuffix: Boolean = false,
largeRowModifier: Modifier = Modifier,
showSymbol: Boolean,
hideBalance: Boolean = false,
@@ -137,6 +141,7 @@ fun BalanceHeader(
SmallRow(
symbol = smallRowSymbol,
text = smallRowText,
+ isSymbolSuffix = smallRowIsSymbolSuffix,
hideBalance = hideBalance,
modifier = smallRowModifier,
)
@@ -151,6 +156,7 @@ fun BalanceHeader(
text = largeRowText,
symbol = largeRowSymbol,
showSymbol = showSymbol,
+ isSymbolSuffix = largeRowIsSymbolSuffix,
hideBalance = hideBalance,
modifier = largeRowModifier,
)
@@ -187,6 +193,7 @@ fun LargeRow(
symbol: String,
showSymbol: Boolean,
modifier: Modifier = Modifier,
+ isSymbolSuffix: Boolean = false,
hideBalance: Boolean = false,
) {
Row(
@@ -202,7 +209,7 @@ fun LargeRow(
.testTag("MoneySign")
)
}
- if (showSymbol) {
+ if (showSymbol && !isSymbolSuffix) {
Display(
text = symbol,
color = Colors.White64,
@@ -221,6 +228,15 @@ fun LargeRow(
modifier = Modifier.testTag("MoneyText")
)
}
+ if (showSymbol && isSymbolSuffix) {
+ Display(
+ text = symbol,
+ color = Colors.White64,
+ modifier = Modifier
+ .padding(start = 8.dp)
+ .testTag("MoneyFiatSymbol")
+ )
+ }
}
}
@@ -229,6 +245,7 @@ private fun SmallRow(
symbol: String?,
text: String,
modifier: Modifier = Modifier,
+ isSymbolSuffix: Boolean = false,
hideBalance: Boolean = false,
) {
Row(
@@ -236,7 +253,7 @@ private fun SmallRow(
horizontalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier,
) {
- if (symbol != null) {
+ if (symbol != null && !isSymbolSuffix) {
Caption13Up(
text = symbol,
color = Colors.White64,
@@ -254,6 +271,13 @@ private fun SmallRow(
modifier = Modifier.testTag("MoneyText")
)
}
+ if (symbol != null && isSymbolSuffix) {
+ Caption13Up(
+ text = symbol,
+ color = Colors.White64,
+ modifier = Modifier.testTag("MoneyFiatSymbol")
+ )
+ }
}
}
diff --git a/app/src/main/java/to/bitkit/ui/components/Money.kt b/app/src/main/java/to/bitkit/ui/components/Money.kt
index cc863d7a1..920efa6c3 100644
--- a/app/src/main/java/to/bitkit/ui/components/Money.kt
+++ b/app/src/main/java/to/bitkit/ui/components/Money.kt
@@ -144,8 +144,9 @@ fun rememberMoneyText(
}
} else {
buildString {
- if (showSymbol) append("${converted.symbol} ")
+ if (showSymbol && !converted.isSymbolSuffix) append("${converted.symbol} ")
append(converted.formatted)
+ if (showSymbol && converted.isSymbolSuffix) append("${converted.symbol}")
}
}
}
diff --git a/app/src/main/java/to/bitkit/ui/components/NumberPadTextField.kt b/app/src/main/java/to/bitkit/ui/components/NumberPadTextField.kt
index add65817c..b158ff18f 100644
--- a/app/src/main/java/to/bitkit/ui/components/NumberPadTextField.kt
+++ b/app/src/main/java/to/bitkit/ui/components/NumberPadTextField.kt
@@ -20,6 +20,7 @@ import to.bitkit.models.BITCOIN_SYMBOL
import to.bitkit.models.PrimaryDisplay
import to.bitkit.models.USD_SYMBOL
import to.bitkit.models.formatToModernDisplay
+import to.bitkit.models.isSuffixSymbolCurrency
import to.bitkit.repositories.CurrencyState
import to.bitkit.ui.LocalCurrencies
import to.bitkit.ui.shared.modifiers.clickableAlpha
@@ -48,6 +49,8 @@ fun NumberPadTextField(
showPlaceholder = true,
satoshis = uiState.value.sats,
currencySymbol = currencies.currencySymbol,
+ isSymbolSuffix = currencies.primaryDisplay == PrimaryDisplay.FIAT &&
+ isSuffixSymbolCurrency(currencies.selectedCurrency),
showSecondaryField = showSecondaryField,
)
}
@@ -60,11 +63,14 @@ private fun MoneyAmount(
satoshis: Long,
modifier: Modifier = Modifier,
currencySymbol: String = BITCOIN_SYMBOL,
+ isSymbolSuffix: Boolean = false,
showPlaceholder: Boolean = true,
showSecondaryField: Boolean = true,
valueStyle: SpanStyle = SpanStyle(color = Colors.White),
placeholderStyle: SpanStyle = SpanStyle(color = Colors.White50),
) {
+ val symbol = if (unit == PrimaryDisplay.BITCOIN) BITCOIN_SYMBOL else currencySymbol
+
Column(
modifier = modifier.semantics { contentDescription = value },
horizontalAlignment = Alignment.Start
@@ -76,11 +82,13 @@ private fun MoneyAmount(
Row(
verticalAlignment = Alignment.CenterVertically,
) {
- Display(
- text = if (unit == PrimaryDisplay.BITCOIN) BITCOIN_SYMBOL else currencySymbol,
- color = Colors.White64,
- modifier = Modifier.padding(end = 6.dp)
- )
+ if (!isSymbolSuffix) {
+ Display(
+ text = symbol,
+ color = Colors.White64,
+ modifier = Modifier.padding(end = 6.dp)
+ )
+ }
Display(
text = buildAnnotatedString {
if (value != placeholder) {
@@ -95,6 +103,13 @@ private fun MoneyAmount(
}
}
)
+ if (isSymbolSuffix) {
+ Display(
+ text = symbol,
+ color = Colors.White64,
+ modifier = Modifier.padding(start = 6.dp)
+ )
+ }
}
}
}
diff --git a/app/src/main/java/to/bitkit/ui/components/WalletBalanceView.kt b/app/src/main/java/to/bitkit/ui/components/WalletBalanceView.kt
index db8ca0a9b..f303de52d 100644
--- a/app/src/main/java/to/bitkit/ui/components/WalletBalanceView.kt
+++ b/app/src/main/java/to/bitkit/ui/components/WalletBalanceView.kt
@@ -146,8 +146,13 @@ private fun RowScope.Content(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
- BodyMSB(text = converted.symbol)
- BodyMSB(text = if (isHidden) UiConstants.HIDE_BALANCE_SHORT else converted.formatted)
+ if (converted.isSymbolSuffix) {
+ BodyMSB(text = if (isHidden) UiConstants.HIDE_BALANCE_SHORT else converted.formatted)
+ BodyMSB(text = converted.symbol)
+ } else {
+ BodyMSB(text = converted.symbol)
+ BodyMSB(text = if (isHidden) UiConstants.HIDE_BALANCE_SHORT else converted.formatted)
+ }
}
}
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt
index f9961bdd0..bd2b9ea85 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityRow.kt
@@ -245,6 +245,7 @@ private fun AmountView(
titlePrefix = prefix,
subtitle = converted.formatted,
subtitleSymbol = converted.symbol,
+ isSymbolSuffix = converted.isSymbolSuffix,
hideBalance = hideBalance,
)
} else {
@@ -253,6 +254,7 @@ private fun AmountView(
titleSymbol = converted.symbol,
titlePrefix = prefix,
subtitle = btcValue,
+ isSymbolSuffix = converted.isSymbolSuffix,
hideBalance = hideBalance,
)
}
@@ -267,6 +269,7 @@ private fun AmountViewContent(
modifier: Modifier = Modifier,
titleSymbol: String? = null,
subtitleSymbol: String? = null,
+ isSymbolSuffix: Boolean = false,
hideBalance: Boolean = false,
) {
Column(
@@ -280,7 +283,7 @@ private fun AmountViewContent(
horizontalArrangement = Arrangement.spacedBy(1.dp),
) {
BodyM(text = titlePrefix, color = Colors.White64)
- if (titleSymbol != null) {
+ if (titleSymbol != null && !isSymbolSuffix) {
BodyMSB(text = titleSymbol, color = Colors.White64)
}
Spacer(modifier = Modifier.width(2.dp))
@@ -291,6 +294,9 @@ private fun AmountViewContent(
) { isHidden ->
BodyMSB(text = if (isHidden) UiConstants.HIDE_BALANCE_SHORT else title)
}
+ if (titleSymbol != null && isSymbolSuffix) {
+ BodyMSB(text = titleSymbol, color = Colors.White64)
+ }
}
// Subtitle row with static symbol
@@ -298,7 +304,7 @@ private fun AmountViewContent(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(3.dp),
) {
- if (subtitleSymbol != null) {
+ if (subtitleSymbol != null && !isSymbolSuffix) {
CaptionB(text = subtitleSymbol, color = Colors.White64)
}
AnimatedContent(
@@ -311,6 +317,9 @@ private fun AmountViewContent(
color = Colors.White64,
)
}
+ if (subtitleSymbol != null && isSymbolSuffix) {
+ CaptionB(text = subtitleSymbol, color = Colors.White64)
+ }
}
}
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveConfirmScreen.kt
index d1a93def0..9722288f3 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveConfirmScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveConfirmScreen.kt
@@ -64,13 +64,13 @@ fun ReceiveConfirmScreen(
val networkFeeFormatted = remember(entry.networkFeeSat) {
currency.convert(entry.networkFeeSat)
- ?.let { converted -> "${converted.symbol}${converted.formatted}" }
+ ?.let { converted -> converted.formattedWithSymbol() }
?: entry.networkFeeSat.toString()
}
val serviceFeeFormatted = remember(entry.serviceFeeSat) {
currency.convert(entry.serviceFeeSat)
- ?.let { converted -> "${converted.symbol}${converted.formatted}" }
+ ?.let { converted -> converted.formattedWithSymbol() }
?: entry.serviceFeeSat.toString()
}
@@ -84,7 +84,7 @@ fun ReceiveConfirmScreen(
val btcComponents = converted.bitcoinDisplay(displayUnit)
"${btcComponents.symbol} ${btcComponents.value}"
} else {
- "${converted.symbol} ${converted.formatted}"
+ converted.formattedWithSymbol()
}
} ?: sats.toString()
}
diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionScreen.kt
index 96b032de3..c27f20d2d 100644
--- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionScreen.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionScreen.kt
@@ -213,7 +213,7 @@ private fun UtxoRow(
currency.convert(sats = amount)?.let { converted ->
val btcValue = converted.bitcoinDisplay(displayUnit).value
BodyMSB(text = btcValue)
- BodySSB(text = "${converted.symbol} ${converted.formatted}", color = Colors.White64)
+ BodySSB(text = converted.formattedWithSymbol(), color = Colors.White64)
}
}
diff --git a/app/src/test/java/to/bitkit/models/CurrencyTest.kt b/app/src/test/java/to/bitkit/models/CurrencyTest.kt
index 7b9ad3324..a79ced622 100644
--- a/app/src/test/java/to/bitkit/models/CurrencyTest.kt
+++ b/app/src/test/java/to/bitkit/models/CurrencyTest.kt
@@ -2,6 +2,7 @@ package to.bitkit.models
import org.junit.Assert.assertEquals
import org.junit.Test
+import java.math.BigDecimal
import java.util.Locale
class CurrencyTest {
@@ -37,4 +38,176 @@ class CurrencyTest {
assertEquals("0.00012345", formatted)
}
+
+ @Test
+ fun `formatCurrencyWithSymbol places USD symbol before amount`() {
+ val value = BigDecimal("10.50")
+
+ val formatted = value.formatCurrencyWithSymbol("USD")
+
+ assertEquals("$10.50", formatted)
+ }
+
+ @Test
+ fun `formatCurrencyWithSymbol places GBP symbol before amount`() {
+ val value = BigDecimal("10.50")
+
+ val formatted = value.formatCurrencyWithSymbol("GBP")
+
+ assertEquals("£10.50", formatted)
+ }
+
+ @Test
+ fun `formatCurrencyWithSymbol places PLN symbol after amount`() {
+ val value = BigDecimal("0.35")
+
+ val formatted = value.formatCurrencyWithSymbol("PLN", "zł")
+
+ assertEquals("0.35zł", formatted)
+ }
+
+ @Test
+ fun `formatCurrencyWithSymbol places EUR symbol before amount`() {
+ val value = BigDecimal("10.00")
+
+ val formatted = value.formatCurrencyWithSymbol("EUR", "€")
+
+ assertEquals("€10.00", formatted)
+ }
+
+ @Test
+ fun `formatCurrencyWithSymbol places CZK symbol after amount`() {
+ val value = BigDecimal("250.00")
+
+ val formatted = value.formatCurrencyWithSymbol("CZK", "Kč")
+
+ assertEquals("250.00Kč", formatted)
+ }
+
+ @Test
+ fun `formatCurrencyWithSymbol places SEK symbol after amount`() {
+ val value = BigDecimal("100.00")
+
+ val formatted = value.formatCurrencyWithSymbol("SEK", "kr")
+
+ assertEquals("100.00kr", formatted)
+ }
+
+ @Test
+ fun `formatCurrencyWithSymbol places CHF symbol after amount`() {
+ val value = BigDecimal("50.00")
+
+ val formatted = value.formatCurrencyWithSymbol("CHF", "CHF")
+
+ assertEquals("50.00CHF", formatted)
+ }
+
+ @Test
+ fun `formatCurrencyWithSymbol places USD symbol before amount with space`() {
+ val value = BigDecimal("10.50")
+
+ val formatted = value.formatCurrencyWithSymbol(
+ currencyCode = "USD",
+ withSpace = true,
+ )
+
+ assertEquals("$ 10.50", formatted)
+ }
+
+ @Test
+ fun `formatCurrencyWithSymbol places PLN symbol after amount with space`() {
+ val value = BigDecimal("0.35")
+
+ val formatted = value.formatCurrencyWithSymbol(
+ currencyCode = "PLN",
+ currencySymbol = "zł",
+ withSpace = true,
+ )
+
+ assertEquals("0.35 zł", formatted)
+ }
+
+ @Test
+ fun `formatCurrencyWithSymbol falls back to currency code when no symbol provided`() {
+ val value = BigDecimal("100.00")
+
+ val formatted = value.formatCurrencyWithSymbol("PLN")
+
+ // Without explicit symbol, falls back to Java Currency symbol (PLN in non-Polish locale)
+ assertEquals("100.00PLN", formatted)
+ }
+
+ @Test
+ fun `formatCurrencyWithSymbol handles unknown currency code`() {
+ val value = BigDecimal("100.00")
+
+ val formatted = value.formatCurrencyWithSymbol("XYZ")
+
+ assertEquals("XYZ100.00", formatted)
+ }
+
+ @Test
+ fun `formatCurrencyWithSymbol formats large amounts with grouping`() {
+ val value = BigDecimal("1234567.89")
+
+ val formatted = value.formatCurrencyWithSymbol("USD")
+
+ assertEquals("$1,234,567.89", formatted)
+ }
+
+ @Test
+ fun `ConvertedAmount formattedWithSymbol returns correct format for prefix currency`() {
+ val converted = ConvertedAmount(
+ value = BigDecimal("10.50"),
+ formatted = "10.50",
+ symbol = "$",
+ currency = "USD",
+ flag = "🇺🇸",
+ sats = 1000L,
+ )
+
+ assertEquals("$10.50", converted.formattedWithSymbol())
+ }
+
+ @Test
+ fun `ConvertedAmount formattedWithSymbol returns correct format for suffix currency`() {
+ val converted = ConvertedAmount(
+ value = BigDecimal("0.35"),
+ formatted = "0.35",
+ symbol = "zł",
+ currency = "PLN",
+ flag = "🇵🇱",
+ sats = 100L,
+ )
+
+ assertEquals("0.35zł", converted.formattedWithSymbol())
+ }
+
+ @Test
+ fun `ConvertedAmount formattedWithSymbol returns correct format for prefix currency with space`() {
+ val converted = ConvertedAmount(
+ value = BigDecimal("10.50"),
+ formatted = "10.50",
+ symbol = "$",
+ currency = "USD",
+ flag = "🇺🇸",
+ sats = 1000L,
+ )
+
+ assertEquals("$ 10.50", converted.formattedWithSymbol(withSpace = true))
+ }
+
+ @Test
+ fun `ConvertedAmount formattedWithSymbol returns correct format for suffix currency with space`() {
+ val converted = ConvertedAmount(
+ value = BigDecimal("0.35"),
+ formatted = "0.35",
+ symbol = "zł",
+ currency = "PLN",
+ flag = "🇵🇱",
+ sats = 100L,
+ )
+
+ assertEquals("0.35 zł", converted.formattedWithSymbol(withSpace = true))
+ }
}