feat: migrate LightningAddressStore to DataStore
This commit is contained in:
@@ -55,13 +55,4 @@ class BalancePrefsStore(context: Context) {
|
||||
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,72 @@
|
||||
package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
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 com.bitcointxoko.gudariwallet.util.WalletConstants.LNADDRESS_TTL_MS
|
||||
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 val Context.lightningAddressDataStore: DataStore<Preferences>
|
||||
by preferencesDataStore(name = "lightning_address")
|
||||
|
||||
class LightningAddressStore(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
private val KEY_ADDRESS = stringPreferencesKey("lightning_address")
|
||||
private val KEY_FETCHED_AT = longPreferencesKey("lightning_address_fetched_at")
|
||||
}
|
||||
|
||||
// In-memory snapshot — safe for ClipboardViewModel's synchronous lambda
|
||||
@Volatile private var _currentAddress: String? = null
|
||||
val currentAddress: String? get() = _currentAddress
|
||||
|
||||
val lightningAddressFlow: Flow<String?> = context.lightningAddressDataStore.data
|
||||
.catch { e ->
|
||||
if (e is IOException) emit(emptyPreferences())
|
||||
else throw e
|
||||
}
|
||||
.map { prefs ->
|
||||
prefs[KEY_ADDRESS].also { address ->
|
||||
_currentAddress = address
|
||||
Log.d("LightningAddressStore", "LNADDR [CACHE ] read from cache: " +
|
||||
if (address != null) "\"$address\"" else "(empty — nothing cached yet)") }
|
||||
}
|
||||
|
||||
suspend fun isCacheStale(): Boolean {
|
||||
return context.lightningAddressDataStore.data
|
||||
.catch { e ->
|
||||
if (e is IOException) emit(emptyPreferences())
|
||||
else throw e
|
||||
}
|
||||
.map { prefs ->
|
||||
val address = prefs[KEY_ADDRESS]
|
||||
val fetchedAt = prefs[KEY_FETCHED_AT] ?: 0L
|
||||
val age = System.currentTimeMillis() - fetchedAt
|
||||
val isStale = address == null || age > LNADDRESS_TTL_MS
|
||||
Log.d("LightningAddressStore", "LNADDR [TTL ] cached=${address != null} " +
|
||||
"age=${age / 1000}s ttl=${LNADDRESS_TTL_MS / 1000}s " +
|
||||
if (isStale) "→ cache stale, will fetch from API"
|
||||
else "→ cache fresh, skipping API call")
|
||||
isStale
|
||||
}
|
||||
.first()
|
||||
}
|
||||
|
||||
suspend fun setLightningAddress(address: String) {
|
||||
context.lightningAddressDataStore.edit { prefs ->
|
||||
prefs[KEY_ADDRESS] = address
|
||||
prefs[KEY_FETCHED_AT] = System.currentTimeMillis() // ← add
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,14 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.bitcointxoko.gudariwallet.data.BalancePrefsStore
|
||||
import com.bitcointxoko.gudariwallet.data.LightningAddressStore
|
||||
import com.bitcointxoko.gudariwallet.ui.balance.BalanceViewModel
|
||||
import com.bitcointxoko.gudariwallet.ui.clipboard.ClipboardViewModel
|
||||
import com.bitcointxoko.gudariwallet.ui.fiat.FiatViewModel
|
||||
import com.bitcointxoko.gudariwallet.ui.receive.ReceiveViewModel
|
||||
import com.bitcointxoko.gudariwallet.ui.send.SendViewModel
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
private const val TAG = "WalletViewModel"
|
||||
|
||||
@@ -26,12 +29,12 @@ class WalletViewModel(
|
||||
val aliasRepo : NodeAliasRepository,
|
||||
val paymentCache: PaymentCacheRepository,
|
||||
private val balancePrefs: BalancePrefsStore,
|
||||
private val lnAddressStore : LightningAddressStore,
|
||||
val balanceVm : BalanceViewModel,
|
||||
val fiatVm : FiatViewModel,
|
||||
val receiveVm : ReceiveViewModel,
|
||||
val sendVm : SendViewModel,
|
||||
val clipboardVm : ClipboardViewModel,
|
||||
val lightningAddress: MutableStateFlow<String?>,
|
||||
) : ViewModel() {
|
||||
val balanceState: StateFlow<BalanceState> get() = balanceVm.balanceState
|
||||
val balanceHidden: StateFlow<Boolean> get() = fiatVm.balanceHidden
|
||||
@@ -63,6 +66,12 @@ class WalletViewModel(
|
||||
val clipboardOfferState: StateFlow<ClipboardOfferState> get() = clipboardVm.clipboardOfferState
|
||||
fun checkClipboard(text: String?) = clipboardVm.checkClipboard(text)
|
||||
fun dismissClipboardOffer() = clipboardVm.dismissClipboardOffer()
|
||||
val lightningAddress: StateFlow<String?> = lnAddressStore.lightningAddressFlow
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = null
|
||||
)
|
||||
|
||||
init {
|
||||
observePaymentEvents()
|
||||
@@ -80,22 +89,26 @@ class WalletViewModel(
|
||||
}
|
||||
|
||||
private fun fetchLightningAddress() {
|
||||
// If we already have a persisted address, show it immediately and
|
||||
// still refresh in the background (address could have changed).
|
||||
// lightningAddressFlow already emits the cached DataStore value immediately
|
||||
// via SharingStarted.Eagerly — this coroutine only does the network refresh.
|
||||
viewModelScope.launch {
|
||||
if (!lnAddressStore.isCacheStale()) {
|
||||
Log.d(TAG, "LNADDR [TTL ] cache is fresh — skipping network fetch")
|
||||
return@launch
|
||||
}
|
||||
runCatching { repo.getLightningAddress() }
|
||||
.onSuccess { address ->
|
||||
if (address != null) {
|
||||
Log.d(TAG, "LNADDR [FETCH OK ] $address")
|
||||
lightningAddress.value = address
|
||||
balancePrefs.setLightningAddress(address)
|
||||
Log.d(TAG, "LNADDR [FETCH OK ] fetched from API: \"$address\" — updating cache")
|
||||
lnAddressStore.setLightningAddress(address)
|
||||
// StateFlow updates automatically via the DataStore flow
|
||||
} else {
|
||||
Log.d(TAG, "LNADDR [FETCH OK ] no pay link / username found")
|
||||
Log.d(TAG, "LNADDR [FETCH OK ] fetched from API: no pay link / username configured")
|
||||
}
|
||||
}
|
||||
.onFailure { e ->
|
||||
Log.w(TAG, "LNADDR [FETCH ERR] ${e.message} — keeping cached value")
|
||||
// Keep whatever was loaded from prefs; don't clear it on network error
|
||||
Log.w(TAG, "LNADDR [FETCH ERR] API call failed (${e.message}) — falling back to cached value")
|
||||
// Nothing to do — DataStore flow already holds the last good value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.bitcointxoko.gudariwallet.data.BalancePrefsStore
|
||||
import com.bitcointxoko.gudariwallet.data.FiatDataStore
|
||||
import com.bitcointxoko.gudariwallet.data.NodeAliasServiceRepository
|
||||
import com.bitcointxoko.gudariwallet.data.LNbitsWalletRepository
|
||||
import com.bitcointxoko.gudariwallet.data.LightningAddressStore
|
||||
import com.bitcointxoko.gudariwallet.data.LnurlCacheRepository
|
||||
import com.bitcointxoko.gudariwallet.data.PaymentCacheRepository
|
||||
import com.bitcointxoko.gudariwallet.security.EncryptedSecretStore
|
||||
@@ -32,6 +33,7 @@ class WalletViewModelFactory(private val app: Application) : ViewModelProvider.F
|
||||
val aliasRepo = NodeAliasServiceRepository(nodeAliasSvc)
|
||||
val repo = LNbitsWalletRepository(api = api, secrets = secrets)
|
||||
val balancePrefs = BalancePrefsStore(app)
|
||||
val lnAddressStore = LightningAddressStore(app)
|
||||
val balanceVm = BalanceViewModel(repo, balancePrefs)
|
||||
val fiatVm = FiatViewModel(repo, balancePrefs, balanceVm, fiatDataStore)
|
||||
val receiveVm = ReceiveViewModel(repo)
|
||||
@@ -45,11 +47,11 @@ class WalletViewModelFactory(private val app: Application) : ViewModelProvider.F
|
||||
}
|
||||
)
|
||||
|
||||
val lightningAddress = MutableStateFlow<String?>(balancePrefs.lightningAddress)
|
||||
// val lightningAddress = MutableStateFlow<String?>(balancePrefs.lightningAddress)
|
||||
|
||||
val clipboardVm = ClipboardViewModel(
|
||||
ownInvoice = { (receiveVm.receiveState.value as? ReceiveState.InvoiceReady)?.bolt11 },
|
||||
ownAddress = { lightningAddress.value }
|
||||
ownAddress = { lnAddressStore.currentAddress }
|
||||
)
|
||||
|
||||
balanceVm.onBalanceRefreshed = { fiatVm.onBalanceRefreshed() }
|
||||
@@ -65,7 +67,7 @@ class WalletViewModelFactory(private val app: Application) : ViewModelProvider.F
|
||||
receiveVm = receiveVm,
|
||||
sendVm = sendVm,
|
||||
clipboardVm = clipboardVm,
|
||||
lightningAddress = lightningAddress
|
||||
lnAddressStore = lnAddressStore,
|
||||
) as T
|
||||
}
|
||||
}
|
||||
@@ -45,4 +45,6 @@ object WalletConstants {
|
||||
|
||||
// ── Fiat cache ─────────────────────────────────────────────────────────
|
||||
const val RATE_TTL_MS = 5 * 60 * 1_000L // 5 minutes
|
||||
|
||||
const val LNADDRESS_TTL_MS = 21 * 24 * 60 * 60 * 1_000L // 21 days
|
||||
}
|
||||
Reference in New Issue
Block a user