feat: migrate BalancePrefs to DataStore

This commit is contained in:
2026-06-02 15:00:32 +02:00
parent fabf18760d
commit 0846b1768c
4 changed files with 165 additions and 72 deletions
@@ -1,58 +1,135 @@
package com.bitcointxoko.gudariwallet.data
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.bitcointxoko.gudariwallet.util.WalletConstants
import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import java.io.IOException
private const val TAG = "BalancePrefsStore"
private const val DATASTORE_NAME = "balance_prefs"
// Top-level extension — one instance per process, mirrors fiatDataStore pattern
private val Context.balanceDataStore: DataStore<Preferences> by preferencesDataStore(
name = DATASTORE_NAME
)
/**
* 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
)
private val dataStore = context.applicationContext.balanceDataStore
// ── Keys ──────────────────────────────────────────────────────────────────
private companion object {
val BALANCE_SATS = longPreferencesKey("balance_sats")
val BALANCE_SAVED_AT = longPreferencesKey("balance_saved_at")
val BALANCE_HIDDEN = booleanPreferencesKey("balance_hidden")
val FIAT_CURRENCY = stringPreferencesKey("fiat_currency")
}
// ── Balance ───────────────────────────────────────────────────────────────
val savedBalanceSats: Long
get() = prefs.getLong(WalletConstants.BALANCE_PREFS_KEY, -1L)
/**
* Returns the last persisted balance in satoshis, or -1 if none exists.
* Used as a cold-start fallback while a fresh network fetch is in flight.
*/
suspend fun getSavedBalanceSats(): Long {
val prefs = dataStore.data
.catch { e ->
if (e is IOException) {
Log.w(TAG, "BALANCE [SATS ] read error — ${e.message}")
emit(emptyPreferences())
} else throw e
}
.first()
return prefs[BALANCE_SATS] ?: -1L
}
val balanceSavedAt: Long
get() = prefs.getLong(WalletConstants.BALANCE_PREFS_SAVED_AT, 0L)
/**
* Returns the timestamp of the last balance persist, or 0 if never saved.
*/
suspend fun getBalanceSavedAt(): Long {
val prefs = dataStore.data
.catch { e ->
if (e is IOException) {
Log.w(TAG, "BALANCE [SAVED_AT] read error — ${e.message}")
emit(emptyPreferences())
} else throw e
}
.first()
return prefs[BALANCE_SAVED_AT] ?: 0L
}
fun persistBalance(sats: Long) {
prefs.edit {
putLong(WalletConstants.BALANCE_PREFS_KEY, sats)
putLong(WalletConstants.BALANCE_PREFS_SAVED_AT, System.currentTimeMillis())
/**
* Atomically persists [sats] and the current timestamp.
*/
suspend fun persistBalance(sats: Long) {
runCatching {
dataStore.edit { prefs ->
prefs[BALANCE_SATS] = sats
prefs[BALANCE_SAVED_AT] = System.currentTimeMillis()
}
}.onFailure { e ->
Log.w(TAG, "BALANCE [SATS ] persist failed — ${e.message}")
}
Log.d(TAG, "BALANCE [SATS ] $sats sats — persisted to DataStore")
}
// ── Balance visibility ────────────────────────────────────────────────────
val isBalanceHidden: Boolean
get() = prefs.getBoolean("balance_hidden", false)
/**
* Emits the current hidden state and all subsequent changes as a [Flow].
* Collect in the ViewModel via stateIn() for reactive UI updates.
*/
val isBalanceHiddenFlow: Flow<Boolean> = dataStore.data
.catch { e ->
if (e is IOException) {
Log.w(TAG, "BALANCE [HIDDEN ] read error — ${e.message}")
emit(emptyPreferences())
} else throw e
}
.map { prefs -> prefs[BALANCE_HIDDEN] ?: false }
fun setBalanceHidden(hidden: Boolean) {
prefs.edit { putBoolean("balance_hidden", hidden) }
suspend fun setBalanceHidden(hidden: Boolean) {
runCatching {
dataStore.edit { prefs -> prefs[BALANCE_HIDDEN] = hidden }
}.onFailure { e ->
Log.w(TAG, "BALANCE [HIDDEN ] persist failed — ${e.message}")
}
}
// ── Fiat currency selection ───────────────────────────────────────────────
val selectedCurrency: String?
get() = prefs.getString("fiat_currency", null)
suspend fun getSelectedCurrency(): String? {
val prefs = dataStore.data
.catch { e ->
if (e is IOException) {
Log.w(TAG, "BALANCE [CURRENCY] read error — ${e.message}")
emit(emptyPreferences())
} else throw e
}
.first()
return prefs[FIAT_CURRENCY]
}
fun setSelectedCurrency(currency: String?) {
prefs.edit {
if (currency != null) putString("fiat_currency", currency)
else remove("fiat_currency")
suspend fun setSelectedCurrency(currency: String?) {
runCatching {
dataStore.edit { prefs ->
if (currency != null) prefs[FIAT_CURRENCY] = currency
else prefs.remove(FIAT_CURRENCY)
}
}.onFailure { e ->
Log.w(TAG, "BALANCE [CURRENCY] persist failed — ${e.message}")
}
}
}
}