Add a "1 <currency> to Sats" field

Shows the inverse of the existing "1 BTC to <currency>" rate — how
many Sats one unit of the default currency is worth — right below it.
Useful since many currencies are worth less than, or a low multiple
of, a single Sat.

CurrencyConverter.satsPerCurrencyUnit(rate) does the actual division;
ConverterUiState.oneCurrencyToSats() derives the display string from
the already-fetched rate the same way defaultCurrencyRate() does, so
no new ViewModel state was needed. Wired into both Compose and the
iOS bridge/SwiftUI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy
This commit is contained in:
2026-09-10 10:04:38 +03:00
co-authored by Claude Sonnet 5
parent 2d60fda72b
commit 947f8bf81a
7 changed files with 49 additions and 0 deletions
@@ -44,4 +44,8 @@ object CurrencyConverter {
fun fiatToSats(fiat: BigDecimal, rates: ExchangeRates, currency: String): BigDecimal? =
fiatToBtc(fiat, rates, currency)?.let { btc -> btcToSats(btc) }
/** Sats worth 1 unit of a currency whose "1 BTC = ?" rate is [rate], or null if [rate] is zero. */
fun satsPerCurrencyUnit(rate: BigDecimal): BigDecimal? =
if (rate.isZero()) null else SATS_PER_BTC.divide(rate, DIVISION_MODE)
}
@@ -4,6 +4,7 @@ import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import xyz.tyiu.satsprice.CurrencyInfo
import xyz.tyiu.satsprice.domain.CurrencyConverter
import xyz.tyiu.satsprice.domain.formatAmount
import xyz.tyiu.satsprice.domain.toBigDecimalOrNull
import kotlin.time.Instant
@@ -25,6 +26,12 @@ fun ConverterUiState.exceedsMaxSupply(): Boolean {
fun ConverterUiState.defaultCurrencyRate(): String =
rateDisplays[defaultCurrencyCode]?.takeIf { it.isNotEmpty() } ?: ""
/** The current "1 [ConverterUiState.defaultCurrencyCode] = ? Sats" rate, as a plain number. */
fun ConverterUiState.oneCurrencyToSats(): String {
val rate = rateDisplays[defaultCurrencyCode]?.toBigDecimalOrNull() ?: return ""
return CurrencyConverter.satsPerCurrencyUnit(rate)?.let { formatAmount(it, 2) } ?: ""
}
/** The pinned, non-removable "Current Currency" shown in the currency picker's own section. */
fun ConverterUiState.currentCurrency(): CurrencyInfo =
availableFiatCurrencies.find { it.code == defaultCurrencyCode } ?: CurrencyInfo(defaultCurrencyCode, defaultCurrencyCode)
@@ -213,6 +213,18 @@ fun PriceScreen(
}
}
}
val oneCurrencyToSats = state.oneCurrencyToSats()
if (oneCurrencyToSats.isNotEmpty()) {
Text(
text = stringResource(MR.strings.currency_to_sats, state.defaultCurrencyCode),
style = MaterialTheme.typography.bodyMedium,
)
Text(
text = groupDigits(oneCurrencyToSats),
style = MaterialTheme.typography.headlineSmall,
)
}
}
}