From 4f190308a3128fa7f7d427e702c1b8889782813b Mon Sep 17 00:00:00 2001 From: Terry Yiu Date: Thu, 10 Sep 2026 12:39:24 +0300 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy --- .../kotlin/xyz/tyiu/satsprice/SystemCurrencies.kt | 15 +++++++++++++++ .../xyz/tyiu/satsprice/ui/PriceViewModel.kt | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.kt b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.kt index 8182859..bf029a1 100644 --- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.kt +++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.kt @@ -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. */ 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) { + 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 * country, so a currently-used-currency filter derived from country/locale data (as the 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 039a1ba..1ec75ba 100644 --- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceViewModel.kt +++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/ui/PriceViewModel.kt @@ -33,6 +33,7 @@ import xyz.tyiu.satsprice.domain.sanitizeIntegerInput import xyz.tyiu.satsprice.domain.toBigDecimalOrNull import xyz.tyiu.satsprice.localeCurrencyCode import xyz.tyiu.satsprice.systemCurrencies +import xyz.tyiu.satsprice.warmRegionDisplayNameCache import kotlin.time.Instant private const val AUTO_REFRESH_INTERVAL_MILLIS = 60_000L @@ -106,6 +107,10 @@ class PriceViewModel( init { 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 { selectedCurrenciesStore.loadSelectedCurrencies().takeIf { it.isNotEmpty() }?.let { persisted -> lastPersistedSelection = persisted