Warm the region display-name cache at startup

Previously the cache only filled lazily on first search use, so the
very first character typed into currency search still paid for every
region code at once. Kicking off warmRegionDisplayNameCache() in its
own coroutine from PriceViewModel's init moves that cost to app
startup, well before the user can reach the search field, without
delaying the ViewModel's own startup sequence (separate launch{}, not
folded into it).

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 12:39:24 +03:00
co-authored by Claude Sonnet 5
parent 3bdf320f5c
commit 4f190308a3
2 changed files with 20 additions and 0 deletions
@@ -40,6 +40,21 @@ private fun localizedRegionName(regionCode: String): String? {
/** The current platform's localized display name for ISO 3166-1 alpha-2 [regionCode], if known. */ /** The current platform's localized display name for ISO 3166-1 alpha-2 [regionCode], if known. */
expect fun regionDisplayName(regionCode: String): String? expect fun regionDisplayName(regionCode: String): String?
/**
* Pre-populates [regionDisplayNameCache] for every region code [currencies] issue from, so the
* first character typed into currency search doesn't pay for all of them at once. Intended to be
* called once, asynchronously, before the user has a chance to reach the search field — e.g. from
* a coroutine launched at startup, not blocking that launch's other work. Not thread-safe:
* [regionDisplayNameCache] is a plain, unsynchronized map, so this (like [matchesCurrencySearch])
* must only ever run on the single thread/dispatcher UI state changes are made from.
*/
fun warmRegionDisplayNameCache(currencies: List<CurrencyInfo>) {
currencies.asSequence()
.flatMap { issuingCountryCodes(it.code).asSequence() }
.distinct()
.forEach { regionCode -> localizedRegionName(regionCode) }
}
/** /**
* ISO 4217 codes for the precious metals actively traded today. These aren't tied to any * ISO 4217 codes for the precious metals actively traded today. These aren't tied to any
* country, so a currently-used-currency filter derived from country/locale data (as the * country, so a currently-used-currency filter derived from country/locale data (as the
@@ -33,6 +33,7 @@ import xyz.tyiu.satsprice.domain.sanitizeIntegerInput
import xyz.tyiu.satsprice.domain.toBigDecimalOrNull import xyz.tyiu.satsprice.domain.toBigDecimalOrNull
import xyz.tyiu.satsprice.localeCurrencyCode import xyz.tyiu.satsprice.localeCurrencyCode
import xyz.tyiu.satsprice.systemCurrencies import xyz.tyiu.satsprice.systemCurrencies
import xyz.tyiu.satsprice.warmRegionDisplayNameCache
import kotlin.time.Instant import kotlin.time.Instant
private const val AUTO_REFRESH_INTERVAL_MILLIS = 60_000L private const val AUTO_REFRESH_INTERVAL_MILLIS = 60_000L
@@ -106,6 +107,10 @@ class PriceViewModel(
init { init {
manualSource.currencyCode = defaultCurrencyCode manualSource.currencyCode = defaultCurrencyCode
// Fired off separately (rather than folded into the launch below) so it doesn't delay
// that one's own startup work — this just needs to finish before the user reaches the
// currency picker's search field, not before anything else.
viewModelScope.launch { warmRegionDisplayNameCache(systemCurrencyList) }
viewModelScope.launch { viewModelScope.launch {
selectedCurrenciesStore.loadSelectedCurrencies().takeIf { it.isNotEmpty() }?.let { persisted -> selectedCurrenciesStore.loadSelectedCurrencies().takeIf { it.isNotEmpty() }?.let { persisted ->
lastPersistedSelection = persisted lastPersistedSelection = persisted