Unify payment item #1
@@ -1,5 +1,8 @@
|
||||
package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import timber.log.Timber
|
||||
|
||||
private const val TAG = "FiatRateCache"
|
||||
@@ -13,6 +16,12 @@ private const val TAG = "FiatRateCache"
|
||||
*/
|
||||
class FiatRateCache(private val fiatDataStore: FiatDataStore) {
|
||||
|
||||
private val _fiatFlow = MutableStateFlow<Pair<String?, Double?>>(Pair(null, null))
|
||||
|
||||
/** Emits the latest (currency, satsPerUnit) pair whenever [putRate] is called. */
|
||||
val fiatFlow: StateFlow<Pair<String?, Double?>> = _fiatFlow.asStateFlow()
|
||||
|
||||
|
||||
// ── Rate ──────────────────────────────────────────────────────────────────
|
||||
|
||||
private var cachedRate : Double? = null
|
||||
@@ -51,6 +60,7 @@ class FiatRateCache(private val fiatDataStore: FiatDataStore) {
|
||||
cachedRate = rate
|
||||
rateLastFetchedAt = now
|
||||
cachedRateCurrency = currency
|
||||
_fiatFlow.value = Pair(currency, rate)
|
||||
fiatDataStore.persistRate(currency, rate)
|
||||
Timber.d("FIAT [RATE FRESH ] 1 $currency = $rate sats — cached + persisted")
|
||||
}
|
||||
@@ -60,6 +70,7 @@ class FiatRateCache(private val fiatDataStore: FiatDataStore) {
|
||||
cachedRate = null
|
||||
rateLastFetchedAt = 0L
|
||||
cachedRateCurrency = ""
|
||||
_fiatFlow.value = Pair(null, null)
|
||||
}
|
||||
|
||||
// ── Currency list ─────────────────────────────────────────────────────────
|
||||
@@ -91,6 +102,5 @@ class FiatRateCache(private val fiatDataStore: FiatDataStore) {
|
||||
Timber.d("FIAT [CURRENCIES] cached ${currencies.size} currencies")
|
||||
}
|
||||
|
||||
val currentRate: Double?
|
||||
get() = cachedRate.takeIf { cachedRateCurrency.isNotBlank() }
|
||||
val currentRate: Double? get() = cachedRate.takeIf { cachedRateCurrency.isNotBlank() }
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.bitcointxoko.gudariwallet.data
|
||||
|
||||
import com.bitcointxoko.gudariwallet.data.db.ContactDao
|
||||
import com.bitcointxoko.gudariwallet.data.db.PaymentContactSummary
|
||||
import com.bitcointxoko.gudariwallet.data.db.PaymentDao
|
||||
import com.bitcointxoko.gudariwallet.data.db.toDomain
|
||||
import com.bitcointxoko.gudariwallet.data.model.ContactSummary
|
||||
import com.bitcointxoko.gudariwallet.data.model.PaymentFeedItem
|
||||
import com.bitcointxoko.gudariwallet.data.model.toPaymentFeedItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
/**
|
||||
* Assembles the unified [PaymentFeedItem] stream by combining all four
|
||||
* independent data sources:
|
||||
* 1. payment_records — via [PaymentDao.observeAll]
|
||||
* 2. tx_contact_links — via [ContactDao.observePaymentContactSummaries]
|
||||
* 3. node_alias_cache — via [NodeAliasRepository.aliases]
|
||||
* 4. fiat rate — via [FiatRateCache.fiatFlow]
|
||||
*
|
||||
* This repository does not own any of these sources — it only combines them.
|
||||
* Filtering, searching, and sorting are the responsibility of the ViewModel.
|
||||
*/
|
||||
class PaymentFeedRepository(
|
||||
private val paymentDao: PaymentDao,
|
||||
private val contactDao: ContactDao,
|
||||
private val nodeAliasRepository: NodeAliasRepository,
|
||||
private val fiatRateCache: FiatRateCache
|
||||
) {
|
||||
|
||||
/**
|
||||
* Emits a new list of [PaymentFeedItem] whenever any upstream source changes.
|
||||
* Items are ordered newest-first (delegated to [PaymentDao.observeAll]).
|
||||
*/
|
||||
fun observeFeedItems(): Flow<List<PaymentFeedItem>> = combine(
|
||||
paymentDao.observeAll(),
|
||||
contactDao.observePaymentContactSummaries(),
|
||||
nodeAliasRepository.aliases,
|
||||
fiatRateCache.fiatFlow
|
||||
) { payments, contactSummaries, aliasMap, (fiatCurrency, fiatSatsPerUnit) ->
|
||||
|
||||
// Group contact summaries by paymentHash for O(1) lookup per payment
|
||||
val contactsByPayment: Map<String, List<PaymentContactSummary>> =
|
||||
contactSummaries.groupBy { it.paymentHash }
|
||||
|
||||
payments.map { entity ->
|
||||
val linked: List<ContactSummary> = contactsByPayment[entity.paymentHash]
|
||||
?.map { ContactSummary(id = it.contactId, displayName = it.displayName) }
|
||||
?: emptyList()
|
||||
|
||||
val resolvedAlias: String? = entity.pubkey?.let { aliasMap[it] }
|
||||
|
||||
entity.toDomain().toPaymentFeedItem(
|
||||
linkedContacts = linked,
|
||||
resolvedAlias = resolvedAlias, // mapper falls back to entity.alias if null
|
||||
fiatCurrency = fiatCurrency,
|
||||
fiatSatsPerUnit = fiatSatsPerUnit
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,13 @@ data class ContactWithAddresses(
|
||||
val addresses: List<PaymentAddressEntity>
|
||||
)
|
||||
|
||||
/** Flat projection used by PaymentFeedRepository to build ContactSummary lists. */
|
||||
data class PaymentContactSummary(
|
||||
val paymentHash: String,
|
||||
val contactId: String,
|
||||
val displayName: String
|
||||
)
|
||||
|
||||
@Dao
|
||||
interface ContactDao {
|
||||
|
||||
@@ -125,6 +132,17 @@ interface ContactDao {
|
||||
""")
|
||||
fun observePaymentContactNames(): Flow<List<PaymentContactName>>
|
||||
|
||||
// Returns all (paymentHash, contactId, displayName) triples — used by
|
||||
// PaymentFeedRepository to build full ContactSummary lists per payment.
|
||||
@Query("""
|
||||
SELECT tcl.paymentHash AS paymentHash,
|
||||
c.id AS contactId,
|
||||
COALESCE(c.localAlias, c.displayName, c.name) AS displayName
|
||||
FROM tx_contact_links tcl
|
||||
INNER JOIN contacts c ON c.id = tcl.contactId
|
||||
""")
|
||||
fun observePaymentContactSummaries(): Flow<List<PaymentContactSummary>>
|
||||
|
||||
// For text search matching contact names — returns paymentHashes of matched payments
|
||||
@Query("""
|
||||
SELECT tcl.paymentHash FROM tx_contact_links tcl
|
||||
|
||||
@@ -4,6 +4,8 @@ import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface PaymentDao {
|
||||
|
||||
@@ -56,6 +58,10 @@ interface PaymentDao {
|
||||
@Query("SELECT * FROM payment_records ORDER BY time DESC")
|
||||
suspend fun getAll(): List<PaymentRecordEntity>
|
||||
|
||||
/** Reactive version of [getAll] — emits a new list whenever any row changes. */
|
||||
@Query("SELECT * FROM payment_records ORDER BY COALESCE(createdAt, CAST(time AS INTEGER)) DESC")
|
||||
fun observeAll(): Flow<List<PaymentRecordEntity>>
|
||||
|
||||
/** A single page, ordered newest-first. */
|
||||
@Query("SELECT * FROM payment_records ORDER BY time DESC LIMIT :limit OFFSET :offset")
|
||||
suspend fun getPage(limit: Int, offset: Int): List<PaymentRecordEntity>
|
||||
|
||||
Reference in New Issue
Block a user