refactor: WalletViewModel BalancePrefs

This commit is contained in:
2026-06-01 19:35:29 +02:00
parent 395f540516
commit 8ce83356ec
4 changed files with 213 additions and 127 deletions
@@ -0,0 +1,67 @@
package com.bitcointxoko.gudariwallet.data
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.bitcointxoko.gudariwallet.util.WalletConstants
/**
* Wraps the single SharedPreferences file used for persisting wallet UI state:
* balance, balance visibility, fiat currency selection, fiat rates, and
* lightning address.
*
* Passed into [WalletViewModel] instead of a raw [Context] so the ViewModel
* holds no Android framework references.
*/
class BalancePrefsStore(context: Context) {
// Exposed to FiatRateCache so both share the same prefs file
internal val prefs: SharedPreferences = context.getSharedPreferences(
WalletConstants.BALANCE_PREFS_NAME, Context.MODE_PRIVATE
)
// ── Balance ───────────────────────────────────────────────────────────────
val savedBalanceSats: Long
get() = prefs.getLong(WalletConstants.BALANCE_PREFS_KEY, -1L)
val balanceSavedAt: Long
get() = prefs.getLong(WalletConstants.BALANCE_PREFS_SAVED_AT, 0L)
fun persistBalance(sats: Long) {
prefs.edit {
putLong(WalletConstants.BALANCE_PREFS_KEY, sats)
putLong(WalletConstants.BALANCE_PREFS_SAVED_AT, System.currentTimeMillis())
}
}
// ── Balance visibility ────────────────────────────────────────────────────
val isBalanceHidden: Boolean
get() = prefs.getBoolean("balance_hidden", false)
fun setBalanceHidden(hidden: Boolean) {
prefs.edit { putBoolean("balance_hidden", hidden) }
}
// ── Fiat currency selection ───────────────────────────────────────────────
val selectedCurrency: String?
get() = prefs.getString("fiat_currency", null)
fun setSelectedCurrency(currency: String?) {
prefs.edit {
if (currency != null) putString("fiat_currency", currency)
else remove("fiat_currency")
}
}
// ── Lightning address ─────────────────────────────────────────────────────
val lightningAddress: String?
get() = prefs.getString("lightning_address", null)
fun setLightningAddress(address: String) {
prefs.edit { putString("lightning_address", address) }
}
}
@@ -0,0 +1,109 @@
package com.bitcointxoko.gudariwallet.data
import android.content.SharedPreferences
import android.util.Log
import androidx.core.content.edit
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
private const val TAG = "FiatRateCache"
/**
* Two-tier (in-memory + SharedPreferences) cache for fiat exchange rates
* and the supported-currency list.
*
* Receives the already-open [prefs] instance from [BalancePrefsStore] so that
* all wallet prefs stay in a single file.
*/
class FiatRateCache(store: BalancePrefsStore) {
private val prefs = store.prefs
// ── Rate ─────────────────────────────────────────────────────────────────
private var cachedRate : Double? = null
private var rateLastFetchedAt : Long = 0L
private var cachedRateCurrency : String = ""
/**
* Returns a fresh in-memory rate for [currency] if it is younger than
* [ttlMs], then falls back to the last persisted float, then returns null.
*/
fun getRate(currency: String, ttlMs: Long): Double? {
val now = System.currentTimeMillis()
val age = now - rateLastFetchedAt
if (cachedRate != null && cachedRateCurrency == currency && age < ttlMs) {
Log.d(TAG, "FIAT [RATE CACHE ] $currency — in-memory hit (age ${age / 1000}s)")
return cachedRate
}
// Cold-start fallback: last persisted rate
val persisted = prefs.getFloat("fiat_rate_$currency", 0f).toDouble()
if (persisted > 0.0) {
Log.d(TAG, "FIAT [RATE STALE ] $currency — using persisted rate $persisted")
return persisted
}
return null
}
/**
* Stores a freshly fetched [rate] for [currency] in memory and on disk.
*/
fun putRate(currency: String, rate: Double) {
val now = System.currentTimeMillis()
cachedRate = rate
rateLastFetchedAt = now
cachedRateCurrency = currency
prefs.edit {
putFloat("fiat_rate_$currency", rate.toFloat())
putLong("fiat_rate_fetched_at_$currency", now)
}
Log.d(TAG, "FIAT [RATE FRESH ] 1 $currency = $rate sats — cached + persisted")
}
/** Clears the in-memory rate (e.g. when the user switches currency). */
fun invalidateRate() {
cachedRate = null
rateLastFetchedAt = 0L
cachedRateCurrency = ""
}
// ── Currency list ─────────────────────────────────────────────────────────
private var cachedCurrencies: List<String>? = null
private val gson = Gson()
/**
* Returns the cached currency list (memory → disk → null).
* Callers are responsible for fetching from the network on a null result.
*/
fun getCurrencies(): List<String>? {
cachedCurrencies?.let {
Log.d(TAG, "FIAT [CURRENCIES] in-memory hit (${it.size} currencies)")
return it
}
val json = prefs.getString("fiat_currencies_json", null) ?: return null
return runCatching {
val type = object : TypeToken<List<String>>() {}.type
val list: List<String> = gson.fromJson(json, type)
Log.d(TAG, "FIAT [CURRENCIES] prefs hit (${list.size} currencies)")
list.also { cachedCurrencies = it }
}.getOrElse { e ->
Log.w(TAG, "FIAT [CURRENCIES] prefs parse failed — ${e.message}")
null
}
}
/** Stores [currencies] in memory and on disk. */
fun putCurrencies(currencies: List<String>) {
if (currencies.isEmpty()) return
cachedCurrencies = currencies
prefs.edit { putString("fiat_currencies_json", gson.toJson(currencies)) }
Log.d(TAG, "FIAT [CURRENCIES] cached ${currencies.size} currencies")
}
val currentRate: Double?
get() = cachedRate.takeIf { cachedRateCurrency.isNotBlank() }
}