Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-27 16:14:33 +04:00
parent d3320b9e8b
commit cde13c66d3
7 changed files with 42 additions and 35 deletions

View file

@ -19,7 +19,7 @@ import kotlinx.serialization.Serializable
@Serializable
data class AccountStatusList(
val userWalletId: UserWalletId,
val accountStatuses: Set<AccountStatus>,
val accountStatuses: List<AccountStatus>,
val totalAccounts: Int,
val totalFiatBalance: TotalFiatBalance,
) {

View file

@ -4,9 +4,9 @@ import arrow.core.Option
import arrow.core.none
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusesFlowFactory
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.core.utils.lceContent
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TokensGroupType
@ -31,6 +31,7 @@ import java.math.BigDecimal
* Produces a flow of [AccountStatusList] for a single user wallet.
*
* @property params Parameters containing the user wallet ID.
* @property accountsCRUDRepository Repository for accessing account data.
* @property singleAccountListSupplier Supplier to get the list of accounts for the user wallet.
* @property cryptoCurrencyStatusesFlowFactory Factory to create flows of cryptocurrency statuses.
* @property dispatchers Coroutine dispatcher provider for managing threading.
@ -40,7 +41,7 @@ import java.math.BigDecimal
@OptIn(ExperimentalCoroutinesApi::class)
internal class DefaultSingleAccountStatusListProducer @AssistedInject constructor(
@Assisted private val params: SingleAccountStatusListProducer.Params,
private val userWalletsListRepository: UserWalletsListRepository,
private val accountsCRUDRepository: AccountsCRUDRepository,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val cryptoCurrencyStatusesFlowFactory: CryptoCurrencyStatusesFlowFactory,
private val dispatchers: CoroutineDispatcherProvider,
@ -61,9 +62,11 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
if (account.cryptoCurrencies.isEmpty()) {
createEmptyAccountStatusFlow(account)
} else {
val userWallet = userWalletsListRepository.userWalletsSync().first {
it.walletId == params.userWalletId
}
val userWallet = accountsCRUDRepository.getUserWallets()
.mapNotNull { wallets ->
wallets.find { it.walletId == params.userWalletId }
}
.first()
getAccountStatusFlow(
userWallet = userWallet,
@ -80,7 +83,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
AccountStatusList(
userWalletId = accountList.userWalletId,
accountStatuses = accountStatuses.toSet(),
accountStatuses = accountStatuses.toList(),
totalAccounts = accountList.totalAccounts,
totalFiatBalance = TotalFiatBalanceCalculator.calculate(balances),
)

View file

@ -84,17 +84,24 @@ class ManageCryptoCurrenciesUseCase(
account = account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet()),
)
derivePublicKeys(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
val isDerivingFailed = derivePublicKeys(
userWalletId = userWalletId,
currencies = modifiedCurrencyList.added,
).isLeft()
parallelUpdatingScope.launch {
/*
* If only removal of currencies happened, we need to sync tokens. Otherwise, tokens will be synced
* when balances are refreshed for added currencies.
*/
if (modifiedCurrencyList.added.isEmpty() && modifiedCurrencyList.removed.isNotEmpty()) {
val isOnlyRemoval = modifiedCurrencyList.added.isEmpty() && modifiedCurrencyList.removed.isNotEmpty()
if (isDerivingFailed || isOnlyRemoval) {
launch { accountsCRUDRepository.syncTokens(userWalletId) }
}
if (isDerivingFailed) return@launch
cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = modifiedCurrencyList.added)
refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total)
clearMetadata(userWalletId = userWalletId, currencies = modifiedCurrencyList.removed)
@ -204,14 +211,11 @@ class ManageCryptoCurrenciesUseCase(
)
}
private suspend fun Raise<Throwable>.derivePublicKeys(
private suspend fun derivePublicKeys(
userWalletId: UserWalletId,
currencies: List<CryptoCurrency>,
) {
catch(
block = { derivationsRepository.derivePublicKeys(userWalletId = userWalletId, currencies = currencies) },
catch = ::raise,
)
): Either<Throwable, Unit> = Either.catch {
derivationsRepository.derivePublicKeys(userWalletId = userWalletId, currencies = currencies)
}
private fun List<CryptoCurrency>.groupByNetwork(

View file

@ -58,14 +58,14 @@ internal object AccountCryptoCurrencyStatusFinder {
* @param network the network to filter accounts by, can be null.
* @return a set of [AccountStatus] that match the expected criteria.
*/
private fun AccountStatusList.getExpectedAccountStatuses(network: Network?): Set<AccountStatus> {
private fun AccountStatusList.getExpectedAccountStatuses(network: Network?): List<AccountStatus> {
val possibleAccountIndex = network?.getAccountIndexOrNull()
return when (possibleAccountIndex) {
// currency can be in any account
null -> accountStatuses
// currency only in the main account
DerivationIndex.Main.value -> setOf(mainAccount)
DerivationIndex.Main.value -> listOf(mainAccount)
// currency only in the account with specific derivation index or in the main account
else -> {
val accountStatus = accountStatuses.firstOrNull {
@ -74,7 +74,7 @@ internal object AccountCryptoCurrencyStatusFinder {
cryptoPortfolio.derivationIndex.value == possibleAccountIndex
}
setOfNotNull(accountStatus, mainAccount)
listOfNotNull(accountStatus, mainAccount)
}
}
}

View file

@ -7,9 +7,9 @@ import com.tangem.common.test.utils.getEmittedValues
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusesFlowFactory
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.core.utils.lceContent
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.models.StatusSource
@ -38,7 +38,7 @@ import java.math.BigDecimal
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DefaultSingleAccountStatusListProducerTest {
private val userWalletsListRepository: UserWalletsListRepository = mockk()
private val accountsCRUDRepository: AccountsCRUDRepository = mockk()
private val singleAccountListSupplier: SingleAccountListSupplier = mockk()
private val cryptoCurrencyStatusesFlowFactory: CryptoCurrencyStatusesFlowFactory = mockk()
@ -49,7 +49,7 @@ class DefaultSingleAccountStatusListProducerTest {
private val producer = DefaultSingleAccountStatusListProducer(
params = SingleAccountStatusListProducer.Params(userWalletId),
userWalletsListRepository = userWalletsListRepository,
accountsCRUDRepository = accountsCRUDRepository,
singleAccountListSupplier = singleAccountListSupplier,
cryptoCurrencyStatusesFlowFactory = cryptoCurrencyStatusesFlowFactory,
dispatchers = TestingCoroutineDispatcherProvider(),
@ -57,7 +57,7 @@ class DefaultSingleAccountStatusListProducerTest {
@AfterEach
fun tearDown() {
clearMocks(singleAccountListSupplier, cryptoCurrencyStatusesFlowFactory)
clearMocks(accountsCRUDRepository, singleAccountListSupplier, cryptoCurrencyStatusesFlowFactory)
}
@Test
@ -75,7 +75,7 @@ class DefaultSingleAccountStatusListProducerTest {
// Assert
val expected = AccountStatusList(
userWalletId = userWalletId,
accountStatuses = setOf(
accountStatuses = listOf(
AccountStatus.CryptoPortfolio(
account = accountList.mainAccount,
tokenList = TokenList.Empty,
@ -110,7 +110,7 @@ class DefaultSingleAccountStatusListProducerTest {
// Assert (first emission)
val expected = AccountStatusList(
userWalletId = userWalletId,
accountStatuses = setOf(
accountStatuses = listOf(
AccountStatus.CryptoPortfolio(
account = accountList.mainAccount,
tokenList = TokenList.Empty,
@ -129,7 +129,7 @@ class DefaultSingleAccountStatusListProducerTest {
// Assert (second emission)
val expected2 = AccountStatusList(
userWalletId = userWalletId,
accountStatuses = setOf(
accountStatuses = listOf(
AccountStatus.CryptoPortfolio(
account = updatedAccountList.mainAccount,
tokenList = TokenList.Empty,
@ -159,7 +159,7 @@ class DefaultSingleAccountStatusListProducerTest {
val expected = AccountStatusList(
userWalletId = userWalletId,
accountStatuses = setOf(
accountStatuses = listOf(
AccountStatus.CryptoPortfolio(
account = accountList.mainAccount,
tokenList = TokenList.Empty,
@ -198,7 +198,7 @@ class DefaultSingleAccountStatusListProducerTest {
cryptoCurrencies = cryptoCurrencyFactory.ethereumAndStellar.toSet(),
)
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
coEvery { accountsCRUDRepository.getUserWallets() } returns flowOf(listOf(userWallet))
every {
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))
@ -226,7 +226,7 @@ class DefaultSingleAccountStatusListProducerTest {
// Assert
val expected = AccountStatusList(
userWalletId = userWalletId,
accountStatuses = setOf(
accountStatuses = listOf(
AccountStatus.CryptoPortfolio(
account = accountList.mainAccount,
tokenList = TokenList.Ungrouped(

View file

@ -79,7 +79,7 @@ class GetAccountCurrencyStatusUseCaseTest {
)
val accountStatusList = mockk<AccountStatusList>(relaxed = true) {
every { this@mockk.accountStatuses } returns setOf(accountStatus)
every { this@mockk.accountStatuses } returns listOf(accountStatus)
}
coEvery { supplier.getSyncOrNull(supplierParams) } returns accountStatusList
@ -117,7 +117,7 @@ class GetAccountCurrencyStatusUseCaseTest {
)
val accountStatusList = mockk<AccountStatusList>(relaxed = true) {
every { this@mockk.accountStatuses } returns setOf(mainAccountStatus, accountStatus, mockk())
every { this@mockk.accountStatuses } returns listOf(mainAccountStatus, accountStatus, mockk())
}
coEvery { supplier.getSyncOrNull(supplierParams) } returns accountStatusList
@ -154,7 +154,7 @@ class GetAccountCurrencyStatusUseCaseTest {
)
val accountStatusList = mockk<AccountStatusList>(relaxed = true) {
every { this@mockk.accountStatuses } returns setOf(accountStatus)
every { this@mockk.accountStatuses } returns listOf(accountStatus)
}
coEvery { supplier.getSyncOrNull(supplierParams) } returns accountStatusList
@ -198,7 +198,7 @@ class GetAccountCurrencyStatusUseCaseTest {
)
val accountStatusList = mockk<AccountStatusList>(relaxed = true) {
every { this@mockk.accountStatuses } returns setOf(accountStatus)
every { this@mockk.accountStatuses } returns listOf(accountStatus)
}
coEvery { supplier(supplierParams) } returns flowOf(accountStatusList)
@ -237,7 +237,7 @@ class GetAccountCurrencyStatusUseCaseTest {
)
val accountStatusList = mockk<AccountStatusList>(relaxed = true) {
every { this@mockk.accountStatuses } returns setOf(mainAccountStatus, accountStatus, mockk())
every { this@mockk.accountStatuses } returns listOf(mainAccountStatus, accountStatus, mockk())
}
coEvery { supplier(supplierParams) } returns flowOf(accountStatusList)
@ -271,7 +271,7 @@ class GetAccountCurrencyStatusUseCaseTest {
)
val accountStatusList = mockk<AccountStatusList>(relaxed = true) {
every { this@mockk.accountStatuses } returns setOf(accountStatus)
every { this@mockk.accountStatuses } returns listOf(accountStatus)
}
coEvery { supplier(supplierParams) } returns flowOf(accountStatusList)

View file

@ -20,7 +20,7 @@ internal data class AvailableToAddData(
internal data class AvailableToAddWallet(
val userWallet: UserWallet,
val accounts: Set<AccountStatus>,
val accounts: List<AccountStatus>,
val availableNetworks: Set<TokenMarketInfo.Network>,
val availableToAddAccounts: Map<AccountId, AvailableToAddAccount>,
)