Hide refresh for Manual, seed its rate from another source

Refreshing has no meaning for a manually-typed rate, so the button
(and its loading spinner) is now hidden while Manual is selected.

Switching to Manual for the first time now seeds its rate field from
whichever source was active before, falling back to any other
source's last cached rate for the default currency. When neither is
available, the rate and amount fields are left empty instead of
showing stale values.

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 09:03:18 +03:00
co-authored by Claude Sonnet 5
parent f3f018741c
commit 583b9bd14e
3 changed files with 48 additions and 16 deletions
@@ -195,14 +195,16 @@ fun PriceScreen(
Spacer(modifier = Modifier.weight(1f))
}
}
if (state.isLoading) {
CircularProgressIndicator(modifier = Modifier.size(24.dp))
} else {
IconButton(onClick = { viewModel.refresh() }) {
Icon(
Icons.Default.Refresh,
contentDescription = stringResource(MR.strings.refresh_content_description),
)
if (!state.isManualSource) {
if (state.isLoading) {
CircularProgressIndicator(modifier = Modifier.size(24.dp))
} else {
IconButton(onClick = { viewModel.refresh() }) {
Icon(
Icons.Default.Refresh,
contentDescription = stringResource(MR.strings.refresh_content_description),
)
}
}
}
}
@@ -207,6 +207,7 @@ class PriceViewModel(
fun onSourceSelected(displayName: String) {
val selected = sources.firstOrNull { it.displayName == displayName } ?: return
val previousRates = rates
currentSource = selected
rates = null
_uiState.update {
@@ -216,6 +217,27 @@ class PriceViewModel(
rateDisplays = emptyMap(),
)
}
if (selected === manualSource && manualSource.rate == null) {
// Switching to Manual for the first time: seed it with whatever rate is already
// known for the default currency — the source just switched away from, or else
// whatever other source was last fetched — rather than starting blank.
viewModelScope.launch {
selectedSourceStore.saveSelectedSourceId(selected.id)
val seedRate = previousRates?.rates?.get(defaultCurrencyCode) ?: fallbackManualRate()
if (seedRate != null) {
manualSource.rate = seedRate
_uiState.update {
it.copy(manualRateInput = formatAmount(seedRate, decimalDigitsFor(defaultCurrencyCode)))
}
} else {
_uiState.update { it.copy(pricedCurrencyCodes = emptySet()) }
}
refresh()
}
return
}
viewModelScope.launch {
selectedSourceStore.saveSelectedSourceId(selected.id)
seedFromCache(selected)
@@ -223,6 +245,12 @@ class PriceViewModel(
}
}
/** The default currency's last known rate from any non-Manual source's cache, if any. */
private suspend fun fallbackManualRate(): BigDecimal? =
sources.filterNot { it === manualSource }.firstNotNullOfOrNull { source ->
exchangeRateStore.loadLastKnownRates(source.id)?.rates?.get(defaultCurrencyCode)
}
fun onManualRateChanged(value: String) {
val sanitized = sanitizeDecimalInput(value)
_uiState.update { it.copy(manualRateInput = sanitized) }