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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy
This commit is contained in:
2026-09-09 23:42:23 +03:00
co-authored by Claude Sonnet 5
parent 583b9bd14e
commit 0927a04fe8
4 changed files with 24 additions and 13 deletions
+5 -1
View File
@@ -36,7 +36,11 @@ struct ContentView: View {
resource: MR.strings.shared.btc_to_currency, resource: MR.strings.shared.btc_to_currency,
args: [state.defaultCurrencyCode] args: [state.defaultCurrencyCode]
)), )),
footer: Text(state.statusLine) footer: Group {
if !state.statusLine.isEmpty {
Text(state.statusLine)
}
}
) { ) {
Picker( Picker(
IosLocalizationKt.localizedString(resource: MR.strings.shared.price_source), IosLocalizationKt.localizedString(resource: MR.strings.shared.price_source),
@@ -10,7 +10,7 @@ import kotlin.time.Instant
/** Derived display strings/flags shared between the Compose UI and the iOS SwiftUI bridge. */ /** Derived display strings/flags shared between the Compose UI and the iOS SwiftUI bridge. */
fun ConverterUiState.statusLine(): String { 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…" val updated = lastUpdated?.let { "updated ${it.toDateTimeString()}" } ?: "loading rates…"
return if (sourceName.isEmpty()) updated else "via $sourceName, $updated" return if (sourceName.isEmpty()) updated else "via $sourceName, $updated"
} }
@@ -138,10 +138,13 @@ fun PriceScreen(
) { ) {
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) { Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) {
Text("SatsPrice", style = MaterialTheme.typography.headlineMedium) Text("SatsPrice", style = MaterialTheme.typography.headlineMedium)
Text( val statusLine = state.statusLine()
text = state.statusLine(), if (statusLine.isNotEmpty()) {
style = MaterialTheme.typography.bodySmall, Text(
) text = statusLine,
style = MaterialTheme.typography.bodySmall,
)
}
} }
Card(modifier = Modifier.fillMaxWidth(), colors = SectionColors) { Card(modifier = Modifier.fillMaxWidth(), colors = SectionColors) {
@@ -142,7 +142,12 @@ class PriceViewModel(
fun refresh() { fun refresh() {
if (currentSource === manualSource && manualSource.rate == null) { 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 return
} }
viewModelScope.launch { viewModelScope.launch {
@@ -230,8 +235,6 @@ class PriceViewModel(
_uiState.update { _uiState.update {
it.copy(manualRateInput = formatAmount(seedRate, decimalDigitsFor(defaultCurrencyCode))) it.copy(manualRateInput = formatAmount(seedRate, decimalDigitsFor(defaultCurrencyCode)))
} }
} else {
_uiState.update { it.copy(pricedCurrencyCodes = emptySet()) }
} }
refresh() refresh()
} }
@@ -254,10 +257,11 @@ class PriceViewModel(
fun onManualRateChanged(value: String) { fun onManualRateChanged(value: String) {
val sanitized = sanitizeDecimalInput(value) val sanitized = sanitizeDecimalInput(value)
_uiState.update { it.copy(manualRateInput = sanitized) } _uiState.update { it.copy(manualRateInput = sanitized) }
sanitized.toBigDecimalOrNull()?.let { parsed -> // Also cleared (rather than left as the last valid rate) when sanitized fails to parse —
manualSource.rate = parsed // e.g. the field is emptied, or is mid-edit on an incomplete number — so refresh() then
refresh() // 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) { private fun updateAmount(field: EditedField, rawValue: String) {