Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-05 13:16:45 +05:00
parent 1437c14019
commit 3f671e3e0f
46 changed files with 1454 additions and 43 deletions

View file

@ -14,6 +14,7 @@ dependencies {
implementation(projects.core.utils)
implementation(projects.core.datasource)
implementation(projects.core.pagination)
implementation(projects.domain.legacy)
implementation(projects.libs.blockchainSdk)
implementation(projects.domain.tokens.models)

View file

@ -2,9 +2,11 @@ package com.tangem.data.txhistory.di
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.txhistory.repository.DefaultTxHistoryRepository
import com.tangem.data.txhistory.repository.RefactoredTxHistoryRepository
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.txhistory.repository.TxHistoryRepository
import com.tangem.domain.txhistory.repository.TxHistoryRepositoryV2
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -32,4 +34,18 @@ internal object TxHistoryDataModule {
txHistoryItemsStore,
dispatchers,
)
@Provides
@Singleton
fun provideTxHistoryRepositoryV2(
walletManagersFacade: WalletManagersFacade,
dispatchers: CoroutineDispatcherProvider,
txHistoryItemsStore: TxHistoryItemsStore,
cacheRegistry: CacheRegistry,
): TxHistoryRepositoryV2 = RefactoredTxHistoryRepository(
walletManagersFacade = walletManagersFacade,
dispatchers = dispatchers,
txHistoryItemsStore = txHistoryItemsStore,
cacheRegistry = cacheRegistry,
)
}

View file

@ -0,0 +1,126 @@
package com.tangem.data.txhistory.repository
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.txhistory.repository.paging.TxHistoryPageBatchFetcher
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
import com.tangem.domain.txhistory.model.TxHistoryListBatchFlow
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.models.TxHistoryItem
import com.tangem.domain.txhistory.repository.TxHistoryRepositoryV2
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.walletmanager.utils.SdkPageConverter
import com.tangem.pagination.BatchFetchResult
import com.tangem.pagination.BatchListSource
import com.tangem.pagination.toBatchFlow
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import timber.log.Timber
internal class RefactoredTxHistoryRepository(
private val walletManagersFacade: WalletManagersFacade,
private val txHistoryItemsStore: TxHistoryItemsStore,
private val cacheRegistry: CacheRegistry,
private val dispatchers: CoroutineDispatcherProvider,
) : TxHistoryRepositoryV2 {
private val sdkPageConverter = SdkPageConverter()
private val TxHistoryListConfig.storeKey get() = TxHistoryItemsStore.Key(userWalletId, currency)
override fun getTxHistoryBatchFlow(batchSize: Int, context: TxHistoryListBatchingContext): TxHistoryListBatchFlow {
return BatchListSource(
fetchDispatcher = dispatchers.io,
context = context,
generateNewKey = { keys -> keys.lastOrNull()?.inc() ?: 0 },
batchFetcher = createFetcher(batchSize),
).toBatchFlow()
}
private fun createFetcher(
batchSize: Int,
): TxHistoryPageBatchFetcher<TxHistoryListConfig, PaginationWrapper<TxHistoryItem>> =
TxHistoryPageBatchFetcher { request, _ ->
val wrappedItems = loadItems(request, batchSize)
BatchFetchResult.Success(
data = wrappedItems,
empty = wrappedItems.items.isEmpty(),
last = wrappedItems.nextPage is Page.LastPage,
)
}
private suspend fun loadItems(
request: TxHistoryPageBatchFetcher.Request<TxHistoryListConfig>,
batchSize: Int,
): PaginationWrapper<TxHistoryItem> {
cacheRegistry.invokeOnExpire(
key = getTxHistoryPageKey(request.page, request.params),
skipCache = request.params.refresh,
block = { fetch(request, batchSize) },
)
return txHistoryItemsStore.getSync(request.page, request.params)
}
private suspend fun fetch(request: TxHistoryPageBatchFetcher.Request<TxHistoryListConfig>, batchSize: Int) {
val wrappedItems = walletManagersFacade.getTxHistoryItems(
userWalletId = request.params.userWalletId,
currency = request.params.currency,
page = sdkPageConverter.convertBack(request.page),
pageSize = batchSize,
)
txHistoryItemsStore.store(key = request.params.storeKey, value = wrappedItems)
}
private suspend fun TxHistoryItemsStore.getSync(
pageToLoad: Page,
config: TxHistoryListConfig,
): PaginationWrapper<TxHistoryItem> {
val storedItems = requireNotNull(getSyncOrNull(config.storeKey, pageToLoad)) {
"The transaction history page #$pageToLoad could not be retrieved"
}
return if (pageToLoad is Page.Initial) storedItems.addRecentTransactions(config) else storedItems
}
private suspend fun PaginationWrapper<TxHistoryItem>.addRecentTransactions(
config: TxHistoryListConfig,
): PaginationWrapper<TxHistoryItem> {
val recentItems = walletManagersFacade.getRecentTransactions(
userWalletId = config.userWalletId,
currency = config.currency,
)
.filterUnconfirmedTransaction()
.sortedByDescending { it.timestampInMillis }
.filterIfTxAlreadyAdded(apiItems = items)
return if (recentItems.isEmpty()) {
Timber.d("Nothing to add to TxHistory")
this
} else {
Timber.d(
"Recent transactions were added to TxHistory: %s",
recentItems.joinToString(
prefix = "[",
postfix = "]",
transform = TxHistoryItem::txHash,
),
)
return copy(items = recentItems + items)
}
}
private fun List<TxHistoryItem>.filterUnconfirmedTransaction(): List<TxHistoryItem> {
return filter { it.status == TxHistoryItem.TransactionStatus.Unconfirmed }
}
private fun List<TxHistoryItem>.filterIfTxAlreadyAdded(apiItems: List<TxHistoryItem>): List<TxHistoryItem> {
return filter { item -> apiItems.none { it.txHash == item.txHash } }
}
private fun getTxHistoryPageKey(page: Page, config: TxHistoryListConfig): String {
return "tx_history_page_${config.currency}_${config.userWalletId}_$page"
}
}

View file

@ -0,0 +1,73 @@
package com.tangem.data.txhistory.repository.paging
import com.tangem.domain.txhistory.models.Page
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.pagination.BatchFetchResult
import com.tangem.pagination.exception.EndOfPaginationException
import com.tangem.pagination.fetcher.BatchFetcher
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow
internal class TxHistoryPageBatchFetcher<TRequestParams : Any, TData : PaginationWrapper<TxHistoryItem>>(
private val subFetcher: SubFetcher<TRequestParams, TData>,
) : BatchFetcher<TRequestParams, TData> {
data class Request<TRequestParams>(val page: Page, val params: TRequestParams)
fun interface SubFetcher<TRequestParams : Any, TData> {
suspend fun fetch(
request: Request<TRequestParams>,
lastResult: BatchFetchResult<TData>?,
): BatchFetchResult<TData>
}
private val lastRequest = MutableStateFlow<Request<TRequestParams>?>(null)
override suspend fun fetchFirst(requestParams: TRequestParams): BatchFetchResult<TData> {
val req = Request(
page = Page.Initial,
params = requestParams,
)
val res = runCatching {
subFetcher.fetch(request = req, lastResult = null)
}.getOrElse {
currentCoroutineContext().ensureActive()
BatchFetchResult.Error(it)
}
lastRequest.value = req
return res
}
override suspend fun fetchNext(
overrideRequestParams: TRequestParams?,
lastResult: BatchFetchResult<TData>,
): BatchFetchResult<TData> {
val last = lastRequest.value
requireNotNull(last)
val req = if (lastResult is BatchFetchResult.Success) {
if (lastResult.last && overrideRequestParams == null) {
return BatchFetchResult.Error(EndOfPaginationException())
}
Request(
page = lastResult.data.nextPage,
params = overrideRequestParams ?: last.params,
)
} else {
last
}
val res = runCatching {
subFetcher.fetch(request = req, lastResult = lastResult)
}.getOrElse {
currentCoroutineContext().ensureActive()
BatchFetchResult.Error(it)
}
lastRequest.value = req
return res
}
}