diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/data/PaymentCacheRepository.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/data/PaymentCacheRepository.kt index ac1843e..5a364e9 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/PaymentCacheRepository.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/PaymentCacheRepository.kt @@ -160,9 +160,33 @@ class PaymentCacheRepository(context: Context) { Timber.d("PAYMENTS [CLEARED ] cache wiped") } - // Fetches specific payments by checkingId (used after a contact lookup) - suspend fun queryByCheckingIds(checkingIds: List): List { + /** + * Fetch payments by checkingId with the full PaymentFilter applied. + * Used by HistoryViewModel for contact name search supplement and + * contact ID filter. Amount/date/status are applied in SQL; + * direction and type are handled in-memory afterwards (same as queryAll). + */ + suspend fun queryByCheckingIds( + checkingIds : List, + filter : PaymentFilter + ): List { if (checkingIds.isEmpty()) return emptyList() - return dao.getByCheckingIds(checkingIds).map { it.toDomain() } + val statusPattern: String? = if (filter.statuses.isEmpty()) null else { + filter.statuses.flatMap { s -> + when (s) { + StatusFilter.COMPLETED -> listOf("success", "complete", "paid") + StatusFilter.PENDING -> listOf("pending", "in_flight", "inflight") + StatusFilter.FAILED -> listOf("failed", "error", "expired") + } + }.joinToString(",") + } + return dao.getByCheckingIdsFiltered( + checkingIds = checkingIds, + minAmountMsat = filter.minAmountSat?.let { it * 1000L }, + maxAmountMsat = filter.maxAmountSat?.let { it * 1000L }, + minCreatedAt = filter.minCreatedAt, + maxCreatedAt = filter.maxCreatedAt, + statusPattern = statusPattern + ).map { it.toDomain() } } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/data/db/PaymentDao.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/data/db/PaymentDao.kt index 231d516..6434467 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/data/db/PaymentDao.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/data/db/PaymentDao.kt @@ -35,6 +35,27 @@ interface PaymentDao { @Query("SELECT * FROM payment_records WHERE checkingId IN (:checkingIds) ORDER BY createdAt DESC") suspend fun getByCheckingIds(checkingIds: List): List + + @Query(""" + SELECT * FROM payment_records + WHERE checkingId IN (:checkingIds) + AND (:minAmountMsat IS NULL OR ABS(amountMsat) >= :minAmountMsat) + AND (:maxAmountMsat IS NULL OR ABS(amountMsat) <= :maxAmountMsat) + AND (:minCreatedAt IS NULL OR createdAt >= :minCreatedAt) + AND (:maxCreatedAt IS NULL OR createdAt <= :maxCreatedAt) + AND (:statusPattern IS NULL OR + ',' || :statusPattern || ',' LIKE '%,' || lower(status) || ',%') + ORDER BY createdAt DESC, time DESC +""") + suspend fun getByCheckingIdsFiltered( + checkingIds : List, + minAmountMsat : Long? = null, + maxAmountMsat : Long? = null, + minCreatedAt : Long? = null, + maxCreatedAt : Long? = null, + statusPattern : String? = null + ): List + @Query("DELETE FROM payment_records") suspend fun deleteAll() diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt index c79ac1c..3686359 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/HistoryViewModel.kt @@ -119,22 +119,6 @@ class HistoryViewModel( ?.payments?.firstOrNull { it.checkingId == checkingId } } - // Maps contactId → display name (for filter chips) - val contactDisplayNames: StateFlow> = - contactRepo.observeAllContacts() - .map { contacts -> - contacts.associate { c -> - c.contact.id to (c.contact.localAlias - ?: c.contact.displayName - ?: c.contact.name - ?: "Unknown") - } - } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = emptyMap() - ) // ── Room-backed query results ────────────────────────────────────────────── private val _roomResults = MutableStateFlow?>(null) @@ -183,6 +167,22 @@ class HistoryViewModel( started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyMap() ) + // Maps contactId → display name (for filter chips) + val contactDisplayNames: StateFlow> = + contactRepo.observeAllContacts() + .map { contacts -> + contacts.associate { c -> + c.contact.id to (c.contact.localAlias + ?: c.contact.displayName + ?: c.contact.name + ?: "Unknown") + } + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = emptyMap() + ) // ── Contact assignment ─────────────────────────────────────────────────── @@ -269,7 +269,7 @@ class HistoryViewModel( .findCheckingIdsByContactName(f.searchQuery) if (contactCheckingIds.isNotEmpty()) { val contactPayments = paymentCache - .queryByCheckingIds(contactCheckingIds) + .queryByCheckingIds(contactCheckingIds, f) // Merge without duplicates val existingIds = baseResults.map { it.checkingId }.toSet() baseResults += contactPayments @@ -282,17 +282,12 @@ class HistoryViewModel( val contactCheckingIds = contactRepo .findCheckingIdsByContactIds(f.contactIds.toList()) if (contactCheckingIds.isNotEmpty()) { - // Intersect: only keep base results that belong - // to the selected contacts, OR fetch them directly - val existingIds = baseResults.map { it.checkingId }.toSet() - val missing = contactCheckingIds.filter { it !in existingIds } - if (missing.isNotEmpty()) { - baseResults += paymentCache.queryByCheckingIds(missing) - } - // Now narrow: remove payments NOT linked to selected contacts - val contactCheckingIdSet = contactCheckingIds.toSet() - _roomResults.value = baseResults - .filter { it.checkingId in contactCheckingIdSet } + // Fetch ALL payments for these contacts, WITH the full filter applied + val contactFiltered = paymentCache.queryByCheckingIds( + checkingIds = contactCheckingIds, + filter = f + ) + _roomResults.value = contactFiltered .also { Timber.d("FILTER [ROOM+CONTACT] contacts=${f.contactIds} → ${it.size} results") } @@ -305,7 +300,7 @@ class HistoryViewModel( } _roomResults.value = baseResults.also { - Timber.d("FILTER [ROOM] q=\"${f.searchQuery}\" → ${it.size} results (${it.size - (paymentCache.queryAll(f.searchQuery, f).size)} from contacts)") + Timber.d("FILTER [ROOM] q=\"${f.searchQuery}\" → ${it.size} results") } } } diff --git a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentFilter.kt b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentFilter.kt index 1cc0c6e..1a6367e 100644 --- a/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentFilter.kt +++ b/app/src/main/java/com/bitcointxoko/gudariwallet/ui/history/PaymentFilter.kt @@ -30,6 +30,7 @@ val PaymentFilter.isActive: Boolean || maxAmountSat != null || minCreatedAt != null || maxCreatedAt != null + || contactIds.isNotEmpty() fun PaymentFilter.needsRoomQuery(): Boolean = searchQuery.isNotBlank() ||