diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift
index 50d2153..53ed6eb 100644
--- a/iosApp/iosApp/ContentView.swift
+++ b/iosApp/iosApp/ContentView.swift
@@ -105,6 +105,17 @@ struct ContentView: View {
}
}
}
+
+ if !state.oneCurrencyToSats.isEmpty {
+ HStack {
+ Text(IosLocalizationKt.localizedFormattedString(
+ resource: MR.strings.shared.currency_to_sats,
+ args: [state.defaultCurrencyCode]
+ ))
+ Spacer()
+ Text(NumberFormatKt.groupDigits(value: state.oneCurrencyToSats))
+ }
+ }
}
if let error = state.errorMessage {
diff --git a/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/IosPriceViewModel.kt b/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/IosPriceViewModel.kt
index e3eccd4..13e7a31 100644
--- a/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/IosPriceViewModel.kt
+++ b/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/IosPriceViewModel.kt
@@ -9,6 +9,7 @@ import xyz.tyiu.satsprice.ui.PriceViewModel
import xyz.tyiu.satsprice.ui.currentCurrency
import xyz.tyiu.satsprice.ui.defaultCurrencyRate
import xyz.tyiu.satsprice.ui.exceedsMaxSupply
+import xyz.tyiu.satsprice.ui.oneCurrencyToSats
import xyz.tyiu.satsprice.ui.selectedOtherCurrencies
import xyz.tyiu.satsprice.ui.statusLine
import xyz.tyiu.satsprice.ui.unselectedCurrencies
@@ -34,6 +35,7 @@ data class IosConverterState(
val errorMessage: String?,
val statusLine: String,
val defaultCurrencyRate: String,
+ val oneCurrencyToSats: String,
)
/**
@@ -85,4 +87,5 @@ private fun ConverterUiState.toIosState(): IosConverterState = IosConverterState
errorMessage = errorMessage,
statusLine = statusLine(),
defaultCurrencyRate = defaultCurrencyRate(),
+ oneCurrencyToSats = oneCurrencyToSats(),
)
diff --git a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/domain/CurrencyConverter.kt b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/domain/CurrencyConverter.kt
index 2166c11..c3e0ec4 100644
--- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/domain/CurrencyConverter.kt
+++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/domain/CurrencyConverter.kt
@@ -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)
}
diff --git a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/ConverterUiStateDisplay.kt b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/ConverterUiStateDisplay.kt
index fcc0194..0f503ad 100644
--- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/ConverterUiStateDisplay.kt
+++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/ConverterUiStateDisplay.kt
@@ -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)
diff --git a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceScreen.kt b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceScreen.kt
index dc411e1..d68e1d7 100644
--- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceScreen.kt
+++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceScreen.kt
@@ -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,
+ )
+ }
}
}
diff --git a/shared/src/commonMain/moko-resources/base/strings.xml b/shared/src/commonMain/moko-resources/base/strings.xml
index 85998ae..fdbb31f 100644
--- a/shared/src/commonMain/moko-resources/base/strings.xml
+++ b/shared/src/commonMain/moko-resources/base/strings.xml
@@ -11,6 +11,7 @@
%1$s - %2$s
%1$s - %2$s
Options for %1$s
+ 1 %1$s to Sats
Done
Exceeds the %1$s maximum supply
Move down
diff --git a/shared/src/commonTest/kotlin/xyz/tyiu/satsprice/domain/CurrencyConverterTest.kt b/shared/src/commonTest/kotlin/xyz/tyiu/satsprice/domain/CurrencyConverterTest.kt
index 6e49a92..4cce7c6 100644
--- a/shared/src/commonTest/kotlin/xyz/tyiu/satsprice/domain/CurrencyConverterTest.kt
+++ b/shared/src/commonTest/kotlin/xyz/tyiu/satsprice/domain/CurrencyConverterTest.kt
@@ -57,6 +57,17 @@ class CurrencyConverterTest {
assertEquals(BigDecimal.fromLong(100_000_000), sats)
}
+ @Test
+ fun satsPerCurrencyUnit_isInverseOfTheBtcRate() {
+ // 1 BTC = 50,000 USD, so 1 USD is worth 100,000,000 / 50,000 = 2,000 Sats.
+ assertEquals(BigDecimal.fromLong(2_000), CurrencyConverter.satsPerCurrencyUnit(BigDecimal.fromLong(50_000)))
+ }
+
+ @Test
+ fun satsPerCurrencyUnit_returnsNullForZeroRate() {
+ assertNull(CurrencyConverter.satsPerCurrencyUnit(BigDecimal.fromLong(0)))
+ }
+
@Test
fun handlesAmountsFarBeyondDoubleOrLongRange() {
// 10^30 BTC: overflows both Double's exact-integer range and Long.MAX_VALUE.