feat: migrate BalancePrefs to DataStore
This commit is contained in:
@@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user