Updated on 2026-08-14
This commit is contained in:
parent
91200c4973
commit
0693835f07
9 changed files with 99 additions and 13 deletions
|
|
@ -2,7 +2,7 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "c0f8e7d958a8852440a5bdec5a989f0e",
|
||||
"identityHash": "1ddc01e929ea21940e7f1e9e35810691",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "express_provider",
|
||||
|
|
@ -271,15 +271,16 @@
|
|||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_from_address_from_network_from_contract_address_created_at` ON `${TABLE_NAME}` (`from_address`, `from_network`, `from_contract_address`, `created_at`)"
|
||||
},
|
||||
{
|
||||
"name": "index_express_exchange_to_network_to_contract_address_created_at",
|
||||
"name": "index_express_exchange_payout_address_to_network_to_contract_address_created_at",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"payout_address",
|
||||
"to_network",
|
||||
"to_contract_address",
|
||||
"created_at"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_to_network_to_contract_address_created_at` ON `${TABLE_NAME}` (`to_network`, `to_contract_address`, `created_at`)"
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_payout_address_to_network_to_contract_address_created_at` ON `${TABLE_NAME}` (`payout_address`, `to_network`, `to_contract_address`, `created_at`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -650,7 +651,7 @@
|
|||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c0f8e7d958a8852440a5bdec5a989f0e')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1ddc01e929ea21940e7f1e9e35810691')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -62,20 +62,21 @@ interface ExpressHistoryDao {
|
|||
): Flow<List<ExpressExchangeEntity>>
|
||||
|
||||
/**
|
||||
* Incoming swaps: the viewed currency is the swap's `to` side. Such a deal was initiated from a
|
||||
* different coin, so the row is stored under that coin's `from_address` — hence this query is
|
||||
* cross-address, matched by the `to` asset. Join to on-chain by `payout_hash`.
|
||||
* Incoming swaps: the viewed currency is the swap's `to` side, so the row is looked up by its `payout_address`
|
||||
* (where the target assets landed = this currency's address). Join to on-chain by `payout_hash`.
|
||||
*/
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM express_exchange
|
||||
WHERE to_network = :network
|
||||
WHERE payout_address = :payoutAddress
|
||||
AND to_network = :network
|
||||
AND to_contract_address = :contract
|
||||
AND (created_at >= :fromCreatedAtIso OR status IN (:activeStatuses))
|
||||
ORDER BY created_at DESC
|
||||
""",
|
||||
)
|
||||
fun observeIncomingSwaps(
|
||||
payoutAddress: String,
|
||||
network: String,
|
||||
contract: String,
|
||||
fromCreatedAtIso: String,
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ import androidx.room.*
|
|||
indices = [
|
||||
// Outgoing swaps lookup (observeOutgoingSwaps): from-address + from-asset equality, created_at range/sort.
|
||||
Index(value = ["from_address", "from_network", "from_contract_address", "created_at"]),
|
||||
// Incoming (cross-owner) swaps lookup (observeIncomingSwaps): to-asset equality, created_at range/sort.
|
||||
// No owner filter here, so to_contract_address in the index is what keeps a popular to-network selective.
|
||||
Index(value = ["to_network", "to_contract_address", "created_at"]),
|
||||
// Incoming swaps lookup (observeIncomingSwaps): payout-address + to-asset equality, created_at range/sort.
|
||||
Index(value = ["payout_address", "to_network", "to_contract_address", "created_at"]),
|
||||
],
|
||||
)
|
||||
data class ExpressExchangeEntity(
|
||||
|
|
|
|||
|
|
@ -95,6 +95,26 @@ internal class DefaultExpressServiceFetcher @Inject constructor(
|
|||
return flow { getInitializationStatusInternal(userWalletId).collect { emit(it) } }
|
||||
}
|
||||
|
||||
override suspend fun getOrFetch(
|
||||
userWalletId: UserWalletId,
|
||||
assetId: ExpressAsset.ID,
|
||||
): Either<Throwable, ExpressAsset> = either {
|
||||
// Return the already-loaded asset (in-memory status or persisted cache) without hitting the network.
|
||||
findLoadedAsset(userWalletId, assetId)?.let { return@either it }
|
||||
|
||||
// Not loaded yet — fetch it from the API, then take it from the refreshed status.
|
||||
fetch(userWalletId = userWalletId, assetIds = setOf(assetId)).bind()
|
||||
|
||||
findLoadedAsset(userWalletId, assetId)
|
||||
?: raise(IllegalStateException("Express asset $assetId is not available after fetch"))
|
||||
}
|
||||
|
||||
private suspend fun findLoadedAsset(userWalletId: UserWalletId, assetId: ExpressAsset.ID): ExpressAsset? {
|
||||
return getInitializationStatusInternal(userWalletId).value
|
||||
.getOrNull()
|
||||
?.firstOrNull { it.id == assetId }
|
||||
}
|
||||
|
||||
@Suppress("SuspendFunWithFlowReturnType")
|
||||
private suspend fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow {
|
||||
val initializationStatus = initializationStatuses.value[userWalletId]
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package com.tangem.data.txhistory.repository
|
|||
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.common.converter.ExpressProviderConverter
|
||||
import com.tangem.data.txhistory.repository.converter.ExpressStatusMapper
|
||||
import com.tangem.data.txhistory.repository.converter.ExpressOnrampConverter
|
||||
import com.tangem.data.txhistory.repository.converter.ExpressStatusMapper
|
||||
import com.tangem.data.txhistory.repository.converter.ExpressSwapConverter
|
||||
import com.tangem.data.txhistory.repository.converter.OnrampCountryConverter
|
||||
import com.tangem.data.txhistory.repository.factory.ExpressTransactionAssetFactory
|
||||
|
|
@ -11,6 +11,7 @@ import com.tangem.data.txhistory.repository.factory.toAssetId
|
|||
import com.tangem.data.txhistory.repository.paging.TxHistoryPageBatchFetcher
|
||||
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
|
||||
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||
import com.tangem.datasource.local.txhistory.db.dao.HistoryIndexDao
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressExchangeEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressOnrampEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressProviderEntity
|
||||
|
|
@ -25,6 +26,7 @@ import com.tangem.domain.txhistory.model.TxHistoryListBatchingContext
|
|||
import com.tangem.domain.txhistory.model.TxHistoryListConfig
|
||||
import com.tangem.domain.txhistory.models.Page
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.repository.ExpressHistoryPage
|
||||
import com.tangem.domain.txhistory.repository.TxHistoryRepositoryV2
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.walletmanager.utils.SdkPageConverter
|
||||
|
|
@ -33,16 +35,19 @@ import com.tangem.pagination.BatchListSource
|
|||
import com.tangem.pagination.toBatchFlow
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeZone
|
||||
import org.joda.time.format.ISODateTimeFormat
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class RefactoredTxHistoryRepository @Inject constructor(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val txHistoryItemsStore: TxHistoryItemsStore,
|
||||
private val expressHistoryDao: ExpressHistoryDao,
|
||||
private val historyIndexDao: HistoryIndexDao,
|
||||
private val expressTransactionAssetFactory: ExpressTransactionAssetFactory,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -80,6 +85,7 @@ internal class RefactoredTxHistoryRepository @Inject constructor(
|
|||
activeStatuses = ExpressStatusMapper.activeExchangeStatuses,
|
||||
).distinctUntilChanged(),
|
||||
flow2 = expressHistoryDao.observeIncomingSwaps(
|
||||
payoutAddress = address,
|
||||
network = rawNetwork,
|
||||
contract = contract,
|
||||
fromCreatedAtIso = fromCreatedAtIso,
|
||||
|
|
@ -110,6 +116,31 @@ internal class RefactoredTxHistoryRepository @Inject constructor(
|
|||
emitAll(flow)
|
||||
}.flowOn(dispatchers.io)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun getIndexedExpressHistory(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
limit: Int,
|
||||
): Flow<ExpressHistoryPage> = flow {
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, currency.network).orEmpty()
|
||||
val pages = historyIndexDao
|
||||
.observePage(addresses = listOf(address), cursor = null, limit = limit)
|
||||
.map { page ->
|
||||
IndexWindow(
|
||||
fromCreatedAtMillis = page.lastOrNull()?.sortTimeMillis ?: 0L,
|
||||
hasMore = page.size >= limit,
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { window ->
|
||||
getExpressHistory(userWalletId, currency, window.fromCreatedAtMillis)
|
||||
.map { express -> ExpressHistoryPage(items = express, hasMore = window.hasMore) }
|
||||
}
|
||||
emitAll(pages)
|
||||
}.flowOn(dispatchers.io)
|
||||
|
||||
private data class IndexWindow(val fromCreatedAtMillis: Long, val hasMore: Boolean)
|
||||
|
||||
/** The reactive express-history inputs gathered from the DB in a single [combine] tick. */
|
||||
private data class ExpressHistorySources(
|
||||
val outgoingSwaps: List<ExpressExchangeEntity>,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.express.models
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
|
|
@ -39,6 +40,13 @@ data class ExpressAsset(
|
|||
operator fun invoke(networkId: String, contractAddress: String?): ID {
|
||||
return ID(networkId = networkId, contractAddress = contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE)
|
||||
}
|
||||
|
||||
operator fun invoke(cryptoCurrency: CryptoCurrency): ID {
|
||||
return ID(
|
||||
networkId = cryptoCurrency.network.rawId,
|
||||
contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,4 +35,6 @@ interface ExpressServiceFetcher {
|
|||
* @return A flow emitting Lce states containing either a list of Express assets or an error.
|
||||
*/
|
||||
fun getInitializationStatus(userWalletId: UserWalletId): Flow<Lce<Throwable, List<ExpressAsset>>>
|
||||
|
||||
suspend fun getOrFetch(userWalletId: UserWalletId, assetId: ExpressAsset.ID): Either<Throwable, ExpressAsset>
|
||||
}
|
||||
|
|
@ -23,4 +23,21 @@ interface TxHistoryRepositoryV2 {
|
|||
currency: CryptoCurrency,
|
||||
fromCreatedAtMillis: Long,
|
||||
): Flow<List<ExpressTx>>
|
||||
|
||||
/**
|
||||
* Reactive express history for [currency] paginated via the unified history index: the [limit] most recent index
|
||||
* rows define the window (their oldest sort time), which bounds [getExpressHistory]. Grow [limit] to load more.
|
||||
*
|
||||
* Used as the standalone backbone when there is no on-chain history source for the currency.
|
||||
*/
|
||||
fun getIndexedExpressHistory(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
limit: Int,
|
||||
): Flow<ExpressHistoryPage>
|
||||
}
|
||||
|
||||
data class ExpressHistoryPage(
|
||||
val items: List<ExpressTx>,
|
||||
val hasMore: Boolean,
|
||||
)
|
||||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.txhistory.model.TxHistoryListBatchingContext
|
|||
import com.tangem.domain.txhistory.model.TxHistoryListConfig
|
||||
import com.tangem.domain.txhistory.models.Page
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.repository.ExpressHistoryPage
|
||||
import com.tangem.domain.txhistory.repository.TxHistoryRepositoryV2
|
||||
import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionStateConverter
|
||||
import com.tangem.pagination.BatchFetchResult
|
||||
|
|
@ -231,6 +232,12 @@ internal class TxHistoryListManagerTest {
|
|||
fromCreatedAtMillis: Long,
|
||||
) = emptyFlow<List<ExpressTx>>()
|
||||
|
||||
override fun getIndexedExpressHistory(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
limit: Int,
|
||||
) = emptyFlow<ExpressHistoryPage>()
|
||||
|
||||
fun loadedItemsCount(): Int = batchFlow.state.value.data.sumOf { batch -> batch.data.items.size }
|
||||
|
||||
fun status(): PaginationStatus<*> = batchFlow.state.value.status
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue