From 0927a04fe8d28a880c7198f3e6cc70e4d94985cb Mon Sep 17 00:00:00 2001 From: Terry Yiu Date: Wed, 9 Sep 2026 23:42:23 +0300 Subject: [PATCH] Hide the Manual status line, clear fiat amounts when its rate is gone "via Manual" had nothing useful left to say once the "updated" part was dropped, so statusLine() now returns empty for Manual and both UIs skip rendering it entirely instead of showing a blank line/footer. onManualRateChanged also now clears manualSource.rate whenever the field doesn't parse (emptied, or mid-edit on an incomplete number) rather than only ever updating it on a successful parse. refresh()'s early-return for a null Manual rate now also clears the cached rates and pricedCurrencyCodes, so a cleared/incomplete manual rate stops fiat amounts from continuing to show a stale computed value. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy --- iosApp/iosApp/ContentView.swift | 6 +++++- .../satsprice/ui/ConverterUiStateDisplay.kt | 2 +- .../xyz/tyiu/satsprice/ui/PriceScreen.kt | 11 +++++++---- .../xyz/tyiu/satsprice/ui/PriceViewModel.kt | 18 +++++++++++------- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 8edc792..d598274 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -36,7 +36,11 @@ struct ContentView: View { resource: MR.strings.shared.btc_to_currency, args: [state.defaultCurrencyCode] )), - footer: Text(state.statusLine) + footer: Group { + if !state.statusLine.isEmpty { + Text(state.statusLine) + } + } ) { Picker( IosLocalizationKt.localizedString(resource: MR.strings.shared.price_source), 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 7c79f55..fcc0194 100644 --- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/ConverterUiStateDisplay.kt +++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/ConverterUiStateDisplay.kt @@ -10,7 +10,7 @@ import kotlin.time.Instant /** Derived display strings/flags shared between the Compose UI and the iOS SwiftUI bridge. */ fun ConverterUiState.statusLine(): String { - if (isManualSource) return if (sourceName.isEmpty()) "" else "via $sourceName" + if (isManualSource) return "" val updated = lastUpdated?.let { "updated ${it.toDateTimeString()}" } ?: "loading rates…" return if (sourceName.isEmpty()) updated else "via $sourceName, $updated" } 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 0432e46..2921710 100644 --- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceScreen.kt +++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceScreen.kt @@ -138,10 +138,13 @@ fun PriceScreen( ) { Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) { Text("SatsPrice", style = MaterialTheme.typography.headlineMedium) - Text( - text = state.statusLine(), - style = MaterialTheme.typography.bodySmall, - ) + val statusLine = state.statusLine() + if (statusLine.isNotEmpty()) { + Text( + text = statusLine, + style = MaterialTheme.typography.bodySmall, + ) + } } Card(modifier = Modifier.fillMaxWidth(), colors = SectionColors) { diff --git a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceViewModel.kt b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceViewModel.kt index a40c00b..9253e16 100644 --- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceViewModel.kt +++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceViewModel.kt @@ -142,7 +142,12 @@ class PriceViewModel( fun refresh() { if (currentSource === manualSource && manualSource.rate == null) { - _uiState.update { it.copy(isLoading = false, errorMessage = null) } + // No rate typed in (yet): nothing to price fiat currencies with, so clear any stale + // rate this might otherwise still compute fiat amounts from. + rates = null + _uiState.update { + it.copy(isLoading = false, errorMessage = null, pricedCurrencyCodes = emptySet()) + } return } viewModelScope.launch { @@ -230,8 +235,6 @@ class PriceViewModel( _uiState.update { it.copy(manualRateInput = formatAmount(seedRate, decimalDigitsFor(defaultCurrencyCode))) } - } else { - _uiState.update { it.copy(pricedCurrencyCodes = emptySet()) } } refresh() } @@ -254,10 +257,11 @@ class PriceViewModel( fun onManualRateChanged(value: String) { val sanitized = sanitizeDecimalInput(value) _uiState.update { it.copy(manualRateInput = sanitized) } - sanitized.toBigDecimalOrNull()?.let { parsed -> - manualSource.rate = parsed - refresh() - } + // Also cleared (rather than left as the last valid rate) when sanitized fails to parse — + // e.g. the field is emptied, or is mid-edit on an incomplete number — so refresh() then + // wipes any fiat amounts computed from it, rather than leaving stale ones on screen. + manualSource.rate = sanitized.toBigDecimalOrNull() + refresh() } private fun updateAmount(field: EditedField, rawValue: String) {