Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-04 11:57:51 +04:00
parent 642190f7c6
commit 3a5144403b
19 changed files with 1033 additions and 49 deletions

View file

@ -9,12 +9,17 @@ android {
namespace = "com.tangem.data.txhistory"
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
dependencies {
implementation(projects.data.common)
implementation(projects.core.utils)
implementation(projects.core.datasource)
implementation(projects.core.pagination)
implementation(projects.core.analytics)
implementation(projects.domain.legacy)
implementation(projects.domain.common)
@ -24,6 +29,9 @@ dependencies {
implementation(projects.domain.txhistory)
implementation(projects.domain.txhistory.models)
implementation(projects.domain.wallets.models)
implementation(projects.domain.wallets)
implementation(projects.domain.account)
implementation(projects.domain.account.status)
implementation(projects.libs.blockchainSdk)
@ -34,4 +42,11 @@ dependencies {
implementation(deps.hilt.core)
kapt(deps.hilt.kapt)
// region Test
testImplementation(projects.common.test)
testImplementation(projects.test.core)
testImplementation(projects.test.mock)
testRuntimeOnly(deps.test.junit5.engine)
// endregion
}

View file

@ -1,51 +1,35 @@
package com.tangem.data.txhistory.di
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.txhistory.fetcher.DefaultAppTxHistoryFetcher
import com.tangem.data.txhistory.fetcher.DefaultTxHistoryFetcherUtils
import com.tangem.data.txhistory.fetcher.TxHistoryFetcherUtils
import com.tangem.data.txhistory.repository.DefaultTxHistoryRepository
import com.tangem.data.txhistory.repository.RefactoredTxHistoryRepository
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.txhistory.fetcher.AppTxHistoryFetcher
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.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object TxHistoryDataModule {
internal interface TxHistoryDataModule {
@Provides
@Binds
@Singleton
fun provideTxHistoryRepository(
cacheRegistry: CacheRegistry,
walletManagersFacade: WalletManagersFacade,
userWalletsListRepository: UserWalletsListRepository,
txHistoryItemsStore: TxHistoryItemsStore,
dispatchers: CoroutineDispatcherProvider,
): TxHistoryRepository = DefaultTxHistoryRepository(
cacheRegistry = cacheRegistry,
walletManagersFacade = walletManagersFacade,
userWalletsListRepository = userWalletsListRepository,
txHistoryItemsStore = txHistoryItemsStore,
dispatchers = dispatchers,
)
fun provideTxHistoryRepository(default: DefaultTxHistoryRepository): TxHistoryRepository
@Provides
@Binds
@Singleton
fun provideTxHistoryRepositoryV2(
walletManagersFacade: WalletManagersFacade,
dispatchers: CoroutineDispatcherProvider,
txHistoryItemsStore: TxHistoryItemsStore,
cacheRegistry: CacheRegistry,
): TxHistoryRepositoryV2 = RefactoredTxHistoryRepository(
walletManagersFacade = walletManagersFacade,
dispatchers = dispatchers,
txHistoryItemsStore = txHistoryItemsStore,
cacheRegistry = cacheRegistry,
)
fun provideTxHistoryRepositoryV2(default: RefactoredTxHistoryRepository): TxHistoryRepositoryV2
@Binds
@Singleton
fun provideAppTxHistoryFetcher(default: DefaultAppTxHistoryFetcher): AppTxHistoryFetcher
@Binds
fun provideTxHistoryFetcherUtils(default: DefaultTxHistoryFetcherUtils): TxHistoryFetcherUtils
}

View file

@ -0,0 +1,125 @@
package com.tangem.data.txhistory.fetcher
import androidx.annotation.VisibleForTesting
import com.tangem.data.txhistory.fetcher.TxHistoryFetcherUtils.Companion.cancelScope
import com.tangem.data.txhistory.fetcher.TxHistoryFetcherUtils.Companion.defaultLaunchIn
import com.tangem.data.txhistory.fetcher.TxHistoryFetcherUtils.Companion.receiveTrigger
import com.tangem.domain.account.supplier.SingleAccountSupplier
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
import com.tangem.domain.txhistory.fetcher.AccountTxHistoryFetcher
import com.tangem.domain.txhistory.fetcher.ExpressTxHistoryFetcher
import com.tangem.domain.txhistory.fetcher.TxHistoryFetchTrigger
import com.tangem.domain.walletmanager.WalletManagersFacade
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.*
import java.util.concurrent.ConcurrentHashMap
internal class DefaultAccountTxHistoryFetcher @AssistedInject constructor(
@Assisted override val accountId: AccountId,
private val utils: TxHistoryFetcherUtils,
private val singleAccountSupplier: SingleAccountSupplier,
private val paymentAccountCurrency: GetPaymentAccountCryptoCurrencyStatusUseCase,
private val expressFetcherFactory: DefaultExpressTxHistoryFetcher.Factory,
private val walletManagersFacade: WalletManagersFacade,
) : AccountTxHistoryFetcher, TxHistoryFetcherUtils by utils {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val expressFetchers = ConcurrentHashMap<String, ExpressTxHistoryFetcher>()
init {
defaultLaunchIn(buildFlow())
}
override suspend fun invoke(params: TxHistoryFetchTrigger) {
sendTrigger(params)
}
override fun close() {
cancelScope()
expressFetchers.forEach { (_, fetcher) -> fetcher.close() }
expressFetchers.clear()
}
private fun buildFlow(): Flow<Unit> = channelFlow {
val accountFlow = singleAccountSupplier(accountId).stateIn(this)
val controlFetchersFlow = when (accountFlow.value) {
is Account.CryptoPortfolio -> accountFlow
.filterIsInstance<Account.CryptoPortfolio>()
.controlFetchersForCryptoAccount()
is Account.Payment -> controlFetchersForPaymentAccount()
}
controlFetchersFlow.launchIn(this)
receiveTrigger().onEach { trigger ->
when (trigger) {
is TxHistoryFetchTrigger.TokenDetailsOpen -> {
val addressKey = getAddress(trigger.walletId, trigger.currency) ?: return@onEach
expressFetchers[addressKey]?.invoke(trigger)
}
is TxHistoryFetchTrigger.TokenDetailsPTR -> {
val addressKey = getAddress(trigger.walletId, trigger.currency) ?: return@onEach
expressFetchers[addressKey]?.invoke(trigger)
}
}
}.collect {}
}
private fun controlFetchersForPaymentAccount(): Flow<Unit> {
return paymentAccountCurrency(walletId)
.map { (_, paymentCurrency) ->
val paymentNetwork = paymentCurrency.currency.network
val address = getAddress(walletId, paymentCurrency.currency)
if (paymentNetwork.isSupportExpressTxHistory() && !address.isNullOrBlank()) {
getOrPutExpressFetcher(address, accountId)
} else {
// single currency for payment account, so we can close all(one)
expressFetchers.forEach { (_, fetcher) -> fetcher.close() }
expressFetchers.clear()
}
}
}
private fun Flow<Account.CryptoPortfolio>.controlFetchersForCryptoAccount(): Flow<Unit> {
return map { account -> account.cryptoCurrencies }
.map { currencies ->
val onlyCoins = currencies.filterIsInstance<CryptoCurrency.Coin>()
val networks = onlyCoins.map { coin -> coin.network }
val newExpressKeys = networks
.filter { net -> net.isSupportExpressTxHistory() }
.mapNotNull { net -> getAddress(walletId, net) }
.toSet()
val previousExpressKeys = expressFetchers.keys
val removed = previousExpressKeys - newExpressKeys
newExpressKeys.forEach { address -> getOrPutExpressFetcher(address, accountId) }
removed.forEach { address -> expressFetchers.remove(address)?.close() }
}
}
@Suppress("FunctionOnlyReturningConstant") // todo txhistory check
private fun Network.isSupportExpressTxHistory(): Boolean {
return true
}
private suspend fun getAddress(userWalletId: UserWalletId, currencies: CryptoCurrency): String? =
getAddress(userWalletId, currencies.network)
private suspend fun getAddress(userWalletId: UserWalletId, network: Network): String? =
walletManagersFacade.getDefaultAddress(userWalletId, network)
private fun getOrPutExpressFetcher(address: String, id: AccountId): ExpressTxHistoryFetcher {
return expressFetchers.computeIfAbsent(address) { expressFetcherFactory.create(address, id) }
}
@AssistedFactory
internal interface Factory {
fun create(accountId: AccountId): DefaultAccountTxHistoryFetcher
}
}

View file

@ -0,0 +1,88 @@
package com.tangem.data.txhistory.fetcher
import androidx.annotation.VisibleForTesting
import com.tangem.data.txhistory.fetcher.TxHistoryFetcherUtils.Companion.cancelScope
import com.tangem.data.txhistory.fetcher.TxHistoryFetcherUtils.Companion.defaultLaunchIn
import com.tangem.data.txhistory.fetcher.TxHistoryFetcherUtils.Companion.receiveTrigger
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.txhistory.fetcher.AppTxHistoryFetcher
import com.tangem.domain.txhistory.fetcher.TxHistoryFetchTrigger
import com.tangem.domain.txhistory.fetcher.WalletTxHistoryFetcher
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import kotlinx.coroutines.flow.*
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
internal class DefaultAppTxHistoryFetcher @Inject constructor(
private val utils: TxHistoryFetcherUtils,
private val getWalletsUseCase: GetWalletsUseCase,
private val selectedWalletUseCase: GetSelectedWalletUseCase,
private val walletTxHistoryFetcherFactory: DefaultWalletTxHistoryFetcher.Factory,
) : AppTxHistoryFetcher, TxHistoryFetcherUtils by utils {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val fetchers = ConcurrentHashMap<UserWalletId, WalletTxHistoryFetcher>()
init {
defaultLaunchIn(buildFlow())
}
override suspend fun invoke(params: TxHistoryFetchTrigger) {
sendTrigger(params)
}
override fun close() {
cancelScope()
fetchers.forEach { (_, fetcher) -> fetcher.close() }
fetchers.clear()
}
private fun buildFlow(): Flow<Unit> = channelFlow {
val walletsFlow: StateFlow<Map<UserWalletId, UserWallet>> = getWalletsUseCase
.invokeAsMap(isOnlyMultiCurrency = true, filterLocked = true)
.stateIn(this)
selectedWalletUseCase.selectedFlow()
.filter { wallet -> wallet.isMultiCurrency }
// todo txhistory some init trigger?
.onEach { }
.launchIn(this)
walletsFlow
.map { map -> map.keys }
.distinctUntilChanged()
// todo txhistory create for all or lazy?
.createForNewWallets()
.closeForRemovedWallets()
.launchIn(this)
receiveTrigger()
.onEach { trigger ->
when (trigger) {
is TxHistoryFetchTrigger.TokenDetailsOpen -> fetchers[trigger.walletId]?.invoke(trigger)
is TxHistoryFetchTrigger.TokenDetailsPTR -> fetchers[trigger.walletId]?.invoke(trigger)
}
}
.collect {}
}
private fun Flow<Set<UserWalletId>>.createForNewWallets() =
onEach { ids -> ids.forEach { walletId -> getOrPutFetcher(walletId) } }
private fun Flow<Set<UserWalletId>>.closeForRemovedWallets() = runningReduce { previousIds, newIds ->
val removedWallets = previousIds.subtract(newIds)
removedWallets.forEach { walletId -> fetchers.remove(walletId)?.close() }
newIds
}
private fun getOrPutFetcher(id: UserWalletId): WalletTxHistoryFetcher {
return fetchers.computeIfAbsent(id) { createFetcher(id) }
}
private fun createFetcher(id: UserWalletId): WalletTxHistoryFetcher {
return walletTxHistoryFetcherFactory.create(id)
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.data.txhistory.fetcher
import com.tangem.data.txhistory.fetcher.TxHistoryFetcherUtils.Companion.cancelScope
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.txhistory.fetcher.ExpressTxHistoryFetcher
import com.tangem.domain.txhistory.fetcher.TxHistoryExpressTrigger
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultExpressTxHistoryFetcher @AssistedInject constructor(
@Assisted override val address: String,
@Assisted private val accountId: AccountId,
private val utils: TxHistoryFetcherUtils,
) : ExpressTxHistoryFetcher, TxHistoryFetcherUtils by utils {
override suspend fun invoke(params: TxHistoryExpressTrigger) {
utils.sendTrigger(params)
accountId
}
override fun close() {
cancelScope()
}
@AssistedFactory
internal interface Factory {
fun create(address: String, accountId: AccountId): DefaultExpressTxHistoryFetcher
}
}

View file

@ -0,0 +1,98 @@
package com.tangem.data.txhistory.fetcher
import androidx.annotation.VisibleForTesting
import com.tangem.data.txhistory.fetcher.TxHistoryFetcherUtils.Companion.cancelScope
import com.tangem.data.txhistory.fetcher.TxHistoryFetcherUtils.Companion.defaultLaunchIn
import com.tangem.data.txhistory.fetcher.TxHistoryFetcherUtils.Companion.receiveTrigger
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.status.utils.AccountCryptoCurrencyOperations.getAccountCryptoCurrency
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.txhistory.fetcher.AccountTxHistoryFetcher
import com.tangem.domain.txhistory.fetcher.TxHistoryFetchTrigger
import com.tangem.domain.txhistory.fetcher.WalletTxHistoryFetcher
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.*
import java.util.concurrent.ConcurrentHashMap
internal class DefaultWalletTxHistoryFetcher @AssistedInject constructor(
@Assisted override val walletId: UserWalletId,
private val utils: TxHistoryFetcherUtils,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val accountTxHistoryFetcher: DefaultAccountTxHistoryFetcher.Factory,
) : WalletTxHistoryFetcher, TxHistoryFetcherUtils by utils {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val fetchers = ConcurrentHashMap<AccountId, AccountTxHistoryFetcher>()
init {
defaultLaunchIn(buildFlow())
}
override suspend fun invoke(params: TxHistoryFetchTrigger) {
sendTrigger(params)
}
override fun close() {
cancelScope()
fetchers.forEach { (_, fetcher) -> fetcher.close() }
fetchers.clear()
}
private fun buildFlow(): Flow<Unit> = channelFlow {
val accountListFlow = singleAccountListSupplier(walletId)
.stateIn(this)
fun accountList(): AccountList = accountListFlow.value
accountListFlow
.map { accountList -> accountList.accounts.mapTo(mutableSetOf()) { account -> account.accountId } }
.distinctUntilChanged()
.createForNewAccounts()
.closeForRemovedAccounts()
.launchIn(this)
receiveTrigger()
.onEach { trigger ->
when (trigger) {
is TxHistoryFetchTrigger.TokenDetailsOpen -> accountList()
.findFetcher(trigger.currency)?.invoke(trigger)
is TxHistoryFetchTrigger.TokenDetailsPTR -> accountList()
.findFetcher(trigger.currency)?.invoke(trigger)
}
}
.collect {}
}
private fun Flow<Set<AccountId>>.createForNewAccounts() =
onEach { ids -> ids.forEach { id -> getOrPutFetcher(id) } }
private fun Flow<Set<AccountId>>.closeForRemovedAccounts() = runningReduce { previousIds, newIds ->
val removedWallets = previousIds.subtract(newIds)
removedWallets.forEach { walletId -> fetchers.remove(walletId)?.close() }
newIds
}
private fun AccountList.findFetcher(currency: CryptoCurrency): AccountTxHistoryFetcher? = this
.getAccountCryptoCurrency(currency)
.getOrNull()
?.account
?.let { account -> fetchers[account.accountId] }
private fun getOrPutFetcher(id: AccountId): AccountTxHistoryFetcher {
return fetchers.computeIfAbsent(id) { createFetcher(id) }
}
private fun createFetcher(id: AccountId): AccountTxHistoryFetcher {
return accountTxHistoryFetcher.create(id)
}
@AssistedFactory
internal interface Factory {
fun create(walletId: UserWalletId): DefaultWalletTxHistoryFetcher
}
}

View file

@ -0,0 +1,67 @@
package com.tangem.data.txhistory.fetcher
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.domain.txhistory.fetcher.TxHistoryFetchTrigger
import com.tangem.utils.coroutines.AppCoroutineScope
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.plus
import javax.inject.Inject
const val TX_HISTORY_TAG = "TxHistory"
internal interface TxHistoryFetcherUtils {
val triggersBuffer: Channel<TxHistoryFetchTrigger>
val fetcherScope: CoroutineScope
val analyticsEventHandler: AnalyticsEventHandler
val analyticsExceptionHandler: AnalyticsExceptionHandler
suspend fun sendTrigger(trigger: TxHistoryFetchTrigger)
companion object {
fun TxHistoryFetcherUtils.cancelScope() = fetcherScope.cancel()
fun <T> TxHistoryFetcherUtils.defaultLaunchIn(flow: Flow<T>) = flow
.retry { error ->
logError(error)
true
}
.launchIn(fetcherScope)
fun TxHistoryFetcherUtils.receiveTrigger(): Flow<TxHistoryFetchTrigger> {
return triggersBuffer.receiveAsFlow()
}
inline fun <reified R> TxHistoryFetcherUtils.receiveTriggerInstance(): Flow<R> {
return receiveTrigger().filterIsInstance<R>()
}
fun logError(error: Throwable, message: String = error.message.orEmpty()) {
TangemLogger.withTag(TX_HISTORY_TAG).e(message, error)
}
}
}
internal class DefaultTxHistoryFetcherUtils @Inject constructor(
appScope: AppCoroutineScope,
override val analyticsEventHandler: AnalyticsEventHandler,
override val analyticsExceptionHandler: AnalyticsExceptionHandler,
) : TxHistoryFetcherUtils {
override val triggersBuffer: Channel<TxHistoryFetchTrigger> = Channel(Channel.BUFFERED)
// todo txhistory use lifecycle scope?
override val fetcherScope: CoroutineScope = appScope + SupervisorJob()
override suspend fun sendTrigger(trigger: TxHistoryFetchTrigger) {
triggersBuffer.trySend(trigger)
}
}

View file

@ -23,8 +23,9 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
import javax.inject.Inject
class DefaultTxHistoryRepository(
class DefaultTxHistoryRepository @Inject constructor(
private val cacheRegistry: CacheRegistry,
private val walletManagersFacade: WalletManagersFacade,
private val userWalletsListRepository: UserWalletsListRepository,

View file

@ -17,8 +17,9 @@ import com.tangem.pagination.BatchListSource
import com.tangem.pagination.toBatchFlow
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
import javax.inject.Inject
internal class RefactoredTxHistoryRepository(
internal class RefactoredTxHistoryRepository @Inject constructor(
private val walletManagersFacade: WalletManagersFacade,
private val txHistoryItemsStore: TxHistoryItemsStore,
private val cacheRegistry: CacheRegistry,

View file

@ -0,0 +1,176 @@
package com.tangem.data.txhistory.fetcher
import com.google.common.truth.Truth.assertThat
import com.tangem.common.test.TestAppCoroutineScope
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
import com.tangem.domain.txhistory.fetcher.TxHistoryFetchTrigger
import com.tangem.domain.account.supplier.SingleAccountSupplier
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.test.mock.MockAccounts
import io.mockk.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.job
import kotlinx.coroutines.test.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@OptIn(ExperimentalCoroutinesApi::class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class DefaultAccountTxHistoryFetcherTest {
private val singleAccountSupplier: SingleAccountSupplier = mockk()
private val paymentAccountCurrency: GetPaymentAccountCryptoCurrencyStatusUseCase = mockk()
private val expressFetcherFactory: DefaultExpressTxHistoryFetcher.Factory = mockk()
private val walletManagersFacade: WalletManagersFacade = mockk()
private val coin: CryptoCurrency = MockCryptoCurrencyFactory().ethereum
private val cryptoAccount = MockAccounts.createAccount(
derivationIndex = 1,
userWalletId = WALLET_ID,
cryptoCurrencies = listOf(coin),
)
@BeforeEach
fun setup() {
clearMocks(singleAccountSupplier, paymentAccountCurrency, expressFetcherFactory, walletManagersFacade)
}
@Test
fun `creates express fetcher per coin address of a crypto portfolio account`() = runTest {
val utils = createUtils()
val accountFlow = MutableStateFlow<Account>(cryptoAccount)
every { singleAccountSupplier.invoke(cryptoAccount.accountId) } returns accountFlow
coEvery { walletManagersFacade.getDefaultAddress(WALLET_ID, coin.network) } returns ADDRESS
val expressFetcher = relaxedExpressFetcher()
every { expressFetcherFactory.create(ADDRESS, cryptoAccount.accountId) } returns expressFetcher
// Act
val fetcher = createFetcher(cryptoAccount.accountId, utils)
advanceUntilIdle()
// Assert
assertThat(fetcher.expressFetchers.keys).containsExactly(ADDRESS)
verify(exactly = 1) { expressFetcherFactory.create(ADDRESS, cryptoAccount.accountId) }
}
@Test
fun `closes express fetcher when its coin is removed from the account`() = runTest {
val utils = createUtils()
val accountFlow = MutableStateFlow<Account>(cryptoAccount)
every { singleAccountSupplier.invoke(cryptoAccount.accountId) } returns accountFlow
coEvery { walletManagersFacade.getDefaultAddress(WALLET_ID, coin.network) } returns ADDRESS
val expressFetcher = relaxedExpressFetcher()
every { expressFetcherFactory.create(ADDRESS, cryptoAccount.accountId) } returns expressFetcher
val fetcher = createFetcher(cryptoAccount.accountId, utils)
advanceUntilIdle()
assertThat(fetcher.expressFetchers.keys).containsExactly(ADDRESS)
// Act
accountFlow.value = cryptoAccount.copy(cryptoCurrencies = emptyList())
advanceUntilIdle()
// Assert
assertThat(fetcher.expressFetchers).isEmpty()
verify(exactly = 1) { expressFetcher.close() }
}
@Test
fun `routes trigger to the express fetcher of the currency address`() = runTest {
val utils = createUtils()
val accountFlow = MutableStateFlow<Account>(cryptoAccount)
every { singleAccountSupplier.invoke(cryptoAccount.accountId) } returns accountFlow
coEvery { walletManagersFacade.getDefaultAddress(WALLET_ID, coin.network) } returns ADDRESS
val expressFetcher = relaxedExpressFetcher()
every { expressFetcherFactory.create(ADDRESS, cryptoAccount.accountId) } returns expressFetcher
val fetcher = createFetcher(cryptoAccount.accountId, utils)
advanceUntilIdle()
// Act
val trigger = TxHistoryFetchTrigger.TokenDetailsOpen(walletId = WALLET_ID, currency = coin)
fetcher.invoke(trigger)
advanceUntilIdle()
// Assert
coVerify(exactly = 1) { expressFetcher.invoke(trigger) }
}
@Test
fun `creates express fetcher for a payment account currency`() = runTest {
val utils = createUtils()
val paymentAccountId = AccountId.forPaymentAccount(WALLET_ID)
val accountFlow = MutableStateFlow<Account>(Account.Payment(WALLET_ID))
every { singleAccountSupplier.invoke(paymentAccountId) } returns accountFlow
val paymentStatus = mockk<AccountStatus.Payment>(relaxed = true)
val currencyStatus = mockk<CryptoCurrencyStatus> { every { currency } returns coin }
every { paymentAccountCurrency.invoke(WALLET_ID) } returns flowOf(paymentStatus to currencyStatus)
coEvery { walletManagersFacade.getDefaultAddress(WALLET_ID, coin.network) } returns ADDRESS
val expressFetcher = relaxedExpressFetcher()
every { expressFetcherFactory.create(ADDRESS, paymentAccountId) } returns expressFetcher
// Act
val fetcher = createFetcher(paymentAccountId, utils)
advanceUntilIdle()
// Assert
assertThat(fetcher.expressFetchers.keys).containsExactly(ADDRESS)
verify(exactly = 1) { expressFetcherFactory.create(ADDRESS, paymentAccountId) }
}
@Test
fun `close cancels scope and closes all express fetchers`() = runTest {
val utils = createUtils()
val accountFlow = MutableStateFlow<Account>(cryptoAccount)
every { singleAccountSupplier.invoke(cryptoAccount.accountId) } returns accountFlow
coEvery { walletManagersFacade.getDefaultAddress(WALLET_ID, coin.network) } returns ADDRESS
val expressFetcher = relaxedExpressFetcher()
every { expressFetcherFactory.create(ADDRESS, cryptoAccount.accountId) } returns expressFetcher
val fetcher = createFetcher(cryptoAccount.accountId, utils)
advanceUntilIdle()
assertThat(fetcher.expressFetchers.keys).containsExactly(ADDRESS)
// Act
fetcher.close()
// Assert
assertThat(fetcher.expressFetchers).isEmpty()
verify(exactly = 1) { expressFetcher.close() }
assertThat(utils.fetcherScope.coroutineContext.job.isActive).isFalse()
}
private fun TestScope.createUtils(): DefaultTxHistoryFetcherUtils = DefaultTxHistoryFetcherUtils(
appScope = TestAppCoroutineScope(testScope = this),
analyticsEventHandler = mockk(relaxed = true),
analyticsExceptionHandler = mockk(relaxed = true),
)
private fun createFetcher(accountId: AccountId, utils: DefaultTxHistoryFetcherUtils) =
DefaultAccountTxHistoryFetcher(
accountId = accountId,
utils = utils,
singleAccountSupplier = singleAccountSupplier,
paymentAccountCurrency = paymentAccountCurrency,
expressFetcherFactory = expressFetcherFactory,
walletManagersFacade = walletManagersFacade,
)
private fun relaxedExpressFetcher() = mockk<DefaultExpressTxHistoryFetcher>(relaxed = true)
private companion object {
val WALLET_ID = MockAccounts.userWalletId
const val ADDRESS = "0xEthAddress"
}
}

View file

@ -0,0 +1,164 @@
package com.tangem.data.txhistory.fetcher
import com.google.common.truth.Truth.assertThat
import com.tangem.common.test.TestAppCoroutineScope
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.txhistory.fetcher.TxHistoryFetchTrigger
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import io.mockk.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.job
import kotlinx.coroutines.test.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@OptIn(ExperimentalCoroutinesApi::class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class DefaultAppTxHistoryFetcherTest {
private val getWalletsUseCase: GetWalletsUseCase = mockk()
private val selectedWalletUseCase: GetSelectedWalletUseCase = mockk()
private val walletFetcherFactory: DefaultWalletTxHistoryFetcher.Factory = mockk()
private val currency: CryptoCurrency = MockCryptoCurrencyFactory().ethereum
@BeforeEach
fun setup() {
clearMocks(getWalletsUseCase, selectedWalletUseCase, walletFetcherFactory)
every { selectedWalletUseCase.selectedFlow() } returns emptyFlow()
}
@Test
fun `creates wallet fetcher for each new wallet`() = runTest {
val utils = createUtils()
val walletsFlow = MutableStateFlow(linkedMapOf<UserWalletId, UserWallet>())
every { getWalletsUseCase.invokeAsMap(any(), any()) } returns walletsFlow
val walletFetcher1 = relaxedWalletFetcher()
every { walletFetcherFactory.create(WALLET_ID_1) } returns walletFetcher1
val fetcher = createFetcher(utils)
advanceUntilIdle()
assertThat(fetcher.fetchers).isEmpty()
// Act
walletsFlow.value = linkedMapOf(WALLET_ID_1 to mockk())
advanceUntilIdle()
// Assert
assertThat(fetcher.fetchers.keys).containsExactly(WALLET_ID_1)
verify(exactly = 1) { walletFetcherFactory.create(WALLET_ID_1) }
}
@Test
fun `closes and removes fetcher when wallet is removed`() = runTest {
val utils = createUtils()
val walletsFlow = MutableStateFlow(
linkedMapOf<UserWalletId, UserWallet>(WALLET_ID_1 to mockk(), WALLET_ID_2 to mockk()),
)
every { getWalletsUseCase.invokeAsMap(any(), any()) } returns walletsFlow
val walletFetcher1 = relaxedWalletFetcher()
val walletFetcher2 = relaxedWalletFetcher()
every { walletFetcherFactory.create(WALLET_ID_1) } returns walletFetcher1
every { walletFetcherFactory.create(WALLET_ID_2) } returns walletFetcher2
val fetcher = createFetcher(utils)
advanceUntilIdle()
assertThat(fetcher.fetchers.keys).containsExactly(WALLET_ID_1, WALLET_ID_2)
// Act
walletsFlow.value = linkedMapOf(WALLET_ID_1 to mockk())
advanceUntilIdle()
// Assert
assertThat(fetcher.fetchers.keys).containsExactly(WALLET_ID_1)
verify(exactly = 1) { walletFetcher2.close() }
verify(inverse = true) { walletFetcher1.close() }
}
@Test
fun `routes trigger to the fetcher of the target wallet`() = runTest {
val utils = createUtils()
val walletsFlow = MutableStateFlow(linkedMapOf<UserWalletId, UserWallet>(WALLET_ID_1 to mockk()))
every { getWalletsUseCase.invokeAsMap(any(), any()) } returns walletsFlow
val walletFetcher1 = relaxedWalletFetcher()
every { walletFetcherFactory.create(WALLET_ID_1) } returns walletFetcher1
val fetcher = createFetcher(utils)
advanceUntilIdle()
// Act
val trigger = TxHistoryFetchTrigger.TokenDetailsOpen(walletId = WALLET_ID_1, currency = currency)
fetcher.invoke(trigger)
advanceUntilIdle()
// Assert
coVerify(exactly = 1) { walletFetcher1.invoke(trigger) }
}
@Test
fun `does nothing when trigger targets unknown wallet`() = runTest {
val utils = createUtils()
val walletsFlow = MutableStateFlow(linkedMapOf<UserWalletId, UserWallet>())
every { getWalletsUseCase.invokeAsMap(any(), any()) } returns walletsFlow
val fetcher = createFetcher(utils)
advanceUntilIdle()
// Act
val trigger = TxHistoryFetchTrigger.TokenDetailsOpen(walletId = WALLET_ID_1, currency = currency)
val result = fetcher.invoke(trigger)
advanceUntilIdle()
// Assert
assertThat(fetcher.fetchers).isEmpty()
verify(inverse = true) { walletFetcherFactory.create(any()) }
}
@Test
fun `close cancels scope and closes all child fetchers`() = runTest {
val utils = createUtils()
val walletsFlow = MutableStateFlow(linkedMapOf<UserWalletId, UserWallet>(WALLET_ID_1 to mockk()))
every { getWalletsUseCase.invokeAsMap(any(), any()) } returns walletsFlow
val walletFetcher1 = relaxedWalletFetcher()
every { walletFetcherFactory.create(WALLET_ID_1) } returns walletFetcher1
val fetcher = createFetcher(utils)
advanceUntilIdle()
assertThat(fetcher.fetchers.keys).containsExactly(WALLET_ID_1)
// Act
fetcher.close()
// Assert
assertThat(fetcher.fetchers).isEmpty()
verify(exactly = 1) { walletFetcher1.close() }
assertThat(utils.fetcherScope.coroutineContext.job.isActive).isFalse()
}
private fun TestScope.createUtils(): DefaultTxHistoryFetcherUtils = DefaultTxHistoryFetcherUtils(
appScope = TestAppCoroutineScope(testScope = this),
analyticsEventHandler = mockk(relaxed = true),
analyticsExceptionHandler = mockk(relaxed = true),
)
private fun createFetcher(utils: DefaultTxHistoryFetcherUtils) = DefaultAppTxHistoryFetcher(
utils = utils,
getWalletsUseCase = getWalletsUseCase,
selectedWalletUseCase = selectedWalletUseCase,
walletTxHistoryFetcherFactory = walletFetcherFactory,
)
private fun relaxedWalletFetcher() = mockk<DefaultWalletTxHistoryFetcher>(relaxed = true)
private companion object {
val WALLET_ID_1 = UserWalletId("001")
val WALLET_ID_2 = UserWalletId("002")
}
}

View file

@ -0,0 +1,176 @@
package com.tangem.data.txhistory.fetcher
import com.google.common.truth.Truth.assertThat
import com.tangem.common.test.TestAppCoroutineScope
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.txhistory.fetcher.TxHistoryFetchTrigger
import com.tangem.test.mock.MockAccounts
import io.mockk.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.job
import kotlinx.coroutines.test.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@OptIn(ExperimentalCoroutinesApi::class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class DefaultWalletTxHistoryFetcherTest {
private val singleAccountListSupplier: SingleAccountListSupplier = mockk()
private val accountFetcherFactory: DefaultAccountTxHistoryFetcher.Factory = mockk()
private val currency: CryptoCurrency = MockCryptoCurrencyFactory().ethereum
private val mainAccount = Account.CryptoPortfolio.createMainAccount(
userWalletId = WALLET_ID,
cryptoCurrencies = listOf(currency),
)
private val secondAccount = MockAccounts.createAccount(derivationIndex = 1, userWalletId = WALLET_ID)
@BeforeEach
fun setup() {
clearMocks(singleAccountListSupplier, accountFetcherFactory)
}
@Test
fun `creates account fetcher for each account in the wallet`() = runTest {
val utils = createUtils()
val accountListFlow = MutableStateFlow(accountListOf(mainAccount, secondAccount))
every { singleAccountListSupplier.invoke(WALLET_ID) } returns accountListFlow
val mainFetcher = relaxedAccountFetcher()
val secondFetcher = relaxedAccountFetcher()
every { accountFetcherFactory.create(mainAccount.accountId) } returns mainFetcher
every { accountFetcherFactory.create(secondAccount.accountId) } returns secondFetcher
// Act
val fetcher = createFetcher(utils)
advanceUntilIdle()
// Assert
assertThat(fetcher.fetchers.keys).containsExactly(mainAccount.accountId, secondAccount.accountId)
verify(exactly = 1) { accountFetcherFactory.create(mainAccount.accountId) }
verify(exactly = 1) { accountFetcherFactory.create(secondAccount.accountId) }
}
@Test
fun `closes and removes fetcher when account is removed`() = runTest {
val utils = createUtils()
val accountListFlow = MutableStateFlow(accountListOf(mainAccount, secondAccount))
every { singleAccountListSupplier.invoke(WALLET_ID) } returns accountListFlow
val mainFetcher = relaxedAccountFetcher()
val secondFetcher = relaxedAccountFetcher()
every { accountFetcherFactory.create(mainAccount.accountId) } returns mainFetcher
every { accountFetcherFactory.create(secondAccount.accountId) } returns secondFetcher
val fetcher = createFetcher(utils)
advanceUntilIdle()
assertThat(fetcher.fetchers.keys).containsExactly(mainAccount.accountId, secondAccount.accountId)
// Act
accountListFlow.value = accountListOf(mainAccount)
advanceUntilIdle()
// Assert
assertThat(fetcher.fetchers.keys).containsExactly(mainAccount.accountId)
verify(exactly = 1) { secondFetcher.close() }
verify(inverse = true) { mainFetcher.close() }
}
@Test
fun `routes trigger to the fetcher of the account that holds the currency`() = runTest {
val utils = createUtils()
val accountListFlow = MutableStateFlow(accountListOf(mainAccount))
every { singleAccountListSupplier.invoke(WALLET_ID) } returns accountListFlow
val mainFetcher = relaxedAccountFetcher()
every { accountFetcherFactory.create(mainAccount.accountId) } returns mainFetcher
val fetcher = createFetcher(utils)
advanceUntilIdle()
// Act
val trigger = TxHistoryFetchTrigger.TokenDetailsPTR(walletId = WALLET_ID, currency = currency)
fetcher.invoke(trigger)
advanceUntilIdle()
// Assert
coVerify(exactly = 1) { mainFetcher.invoke(trigger) }
}
@Test
fun `does nothing when trigger currency is not present in any account`() = runTest {
val utils = createUtils()
// main account without the triggered currency
val accountListFlow = MutableStateFlow(accountListOf(Account.CryptoPortfolio.createMainAccount(WALLET_ID)))
every { singleAccountListSupplier.invoke(WALLET_ID) } returns accountListFlow
val mainFetcher = relaxedAccountFetcher()
every { accountFetcherFactory.create(any()) } returns mainFetcher
val fetcher = createFetcher(utils)
advanceUntilIdle()
// Act
val trigger = TxHistoryFetchTrigger.TokenDetailsOpen(walletId = WALLET_ID, currency = currency)
val result = fetcher.invoke(trigger)
advanceUntilIdle()
// Assert
coVerify(inverse = true) { mainFetcher.invoke(any()) }
}
@Test
fun `close cancels scope and closes all child fetchers`() = runTest {
val utils = createUtils()
val accountListFlow = MutableStateFlow(accountListOf(mainAccount, secondAccount))
every { singleAccountListSupplier.invoke(WALLET_ID) } returns accountListFlow
val mainFetcher = relaxedAccountFetcher()
val secondFetcher = relaxedAccountFetcher()
every { accountFetcherFactory.create(mainAccount.accountId) } returns mainFetcher
every { accountFetcherFactory.create(secondAccount.accountId) } returns secondFetcher
val fetcher = createFetcher(utils)
advanceUntilIdle()
assertThat(fetcher.fetchers.keys).containsExactly(mainAccount.accountId, secondAccount.accountId)
// Act
fetcher.close()
// Assert
assertThat(fetcher.fetchers).isEmpty()
verify(exactly = 1) { mainFetcher.close() }
verify(exactly = 1) { secondFetcher.close() }
assertThat(utils.fetcherScope.coroutineContext.job.isActive).isFalse()
}
private fun accountListOf(vararg accounts: Account): AccountList = AccountList(
userWalletId = WALLET_ID,
accounts = accounts.toList(),
totalAccounts = accounts.size,
totalArchivedAccounts = 0,
).getOrNull()!!
private fun TestScope.createUtils(): DefaultTxHistoryFetcherUtils = DefaultTxHistoryFetcherUtils(
appScope = TestAppCoroutineScope(testScope = this),
analyticsEventHandler = mockk(relaxed = true),
analyticsExceptionHandler = mockk(relaxed = true),
)
private fun createFetcher(utils: DefaultTxHistoryFetcherUtils) = DefaultWalletTxHistoryFetcher(
walletId = WALLET_ID,
utils = utils,
singleAccountListSupplier = singleAccountListSupplier,
accountTxHistoryFetcher = accountFetcherFactory,
)
private fun relaxedAccountFetcher() = mockk<DefaultAccountTxHistoryFetcher>(relaxed = true)
private companion object {
val WALLET_ID: UserWalletId = MockAccounts.userWalletId
}
}

View file

@ -19,6 +19,10 @@ abstract class SingleAccountSupplier(
override val keyCreator: (SingleAccountProducer.Params) -> String,
) : FlowCachingSupplier<SingleAccountProducer, SingleAccountProducer.Params, Account>() {
operator fun invoke(accountId: AccountId): Flow<Account> {
return invoke(params = SingleAccountProducer.Params(accountId))
}
fun filterPaymentAccount(accountId: AccountId): Flow<Account.Payment> {
return invoke(params = SingleAccountProducer.Params(accountId)).filterIsInstance()
}

View file

@ -0,0 +1,20 @@
package com.tangem.domain.txhistory.fetcher
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
sealed interface TxHistoryFetchTrigger {
data class TokenDetailsOpen(
val walletId: UserWalletId,
val currency: CryptoCurrency,
) : TxHistoryFetchTrigger, TxHistoryExpressTrigger, TxHistoryGatewayTrigger
data class TokenDetailsPTR(
val walletId: UserWalletId,
val currency: CryptoCurrency,
) : TxHistoryFetchTrigger, TxHistoryExpressTrigger, TxHistoryGatewayTrigger
}
sealed interface TxHistoryExpressTrigger : TxHistoryFetchTrigger
sealed interface TxHistoryGatewayTrigger : TxHistoryFetchTrigger

View file

@ -0,0 +1,24 @@
package com.tangem.domain.txhistory.fetcher
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWalletId
interface TxHistoryFetcher<T : TxHistoryFetchTrigger> {
suspend fun invoke(params: T)
fun close()
}
interface AppTxHistoryFetcher : TxHistoryFetcher<TxHistoryFetchTrigger>
interface WalletTxHistoryFetcher : TxHistoryFetcher<TxHistoryFetchTrigger> {
val walletId: UserWalletId
}
interface AccountTxHistoryFetcher : TxHistoryFetcher<TxHistoryFetchTrigger> {
val accountId: AccountId
val walletId: UserWalletId get() = accountId.userWalletId
}
interface ExpressTxHistoryFetcher : TxHistoryFetcher<TxHistoryExpressTrigger> {
val address: String
}

View file

@ -4,6 +4,7 @@ import arrow.core.Option
import arrow.core.none
import arrow.core.some
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@ -21,11 +22,7 @@ class GetPaymentAccountCryptoCurrencyStatusUseCase(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): Flow<Pair<Account.Payment, CryptoCurrencyStatus>> {
return paymentAccountStatusSupplier(userWalletId).mapNotNull { accountStatus ->
val cryptoCurrencyStatus = when (val statusValue = accountStatus.value) {
is PaymentAccountStatusValue.Loaded -> statusValue.cryptoCurrencyStatus
else -> return@mapNotNull null
}
return invoke(userWalletId).mapNotNull { (accountStatus, cryptoCurrencyStatus) ->
if (cryptoCurrencyStatus.currency == cryptoCurrency) {
accountStatus.account to cryptoCurrencyStatus
} else {
@ -34,6 +31,16 @@ class GetPaymentAccountCryptoCurrencyStatusUseCase(
}
}
operator fun invoke(userWalletId: UserWalletId): Flow<Pair<AccountStatus.Payment, CryptoCurrencyStatus>> {
return paymentAccountStatusSupplier(userWalletId).mapNotNull { accountStatus ->
val cryptoCurrencyStatus = when (val statusValue = accountStatus.value) {
is PaymentAccountStatusValue.Loaded -> statusValue.cryptoCurrencyStatus
else -> return@mapNotNull null
}
accountStatus to cryptoCurrencyStatus
}
}
suspend fun invokeSync(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,

View file

@ -27,6 +27,10 @@ class GetSelectedWalletUseCase(
}
}
fun selectedFlow(): Flow<UserWallet> {
return userWalletsListRepository.selectedUserWallet.filterNotNull()
}
@Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features")
fun sync(): Either<GetUserWalletError, UserWallet?> {
return either {

View file

@ -3,6 +3,7 @@ package com.tangem.domain.wallets.usecase
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.isMultiCurrency
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@ -22,13 +23,14 @@ class GetWalletsUseCase(
operator fun invoke(): Flow<List<UserWallet>> = userWalletsListRepository.userWallets.map { requireNotNull(it) }
@Throws(IllegalArgumentException::class)
fun invokeAsMap(isOnlyMultiCurrency: Boolean = true): Flow<LinkedHashMap<UserWalletId, UserWallet>> = invoke()
fun invokeAsMap(
isOnlyMultiCurrency: Boolean = true,
filterLocked: Boolean = false,
): Flow<LinkedHashMap<UserWalletId, UserWallet>> = invoke()
.map { list ->
val wallets = if (isOnlyMultiCurrency) {
list.filter { wallet -> wallet.isMultiCurrency }
} else {
list
}
val wallets = list
.filter { wallet -> if (isOnlyMultiCurrency) wallet.isMultiCurrency else true }
.filter { wallet -> if (filterLocked) !wallet.isLocked else true }
wallets.associateByTo(
destination = linkedMapOf(),
keySelector = { wallet -> wallet.walletId },

View file

@ -4,7 +4,6 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
@ -61,8 +60,7 @@ internal class PortfolioFullBlockDelegate @AssistedInject constructor(
}
private fun buildFlow() = flow {
val walletsFlow = getWalletsUseCase.invokeAsMap()
.map { wallets -> wallets.filterNot { (_, wallet) -> wallet.isLocked } }
val walletsFlow = getWalletsUseCase.invokeAsMap(filterLocked = true)
val fullPortfolioBlockFlow = combine(
flow = walletsFlow,
flow2 = portfolioListBlockDelegate.portfolioList,