Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-08 16:03:08 +05:00
parent 24d7953389
commit 8683516c59
24 changed files with 817 additions and 148 deletions

View file

@ -11,6 +11,7 @@ import com.tangem.domain.models.wallet.UserWalletId
internal fun String.toAccountId(userWalletId: UserWalletId): AccountId {
return when {
startsWith(AccountId.PaymentAccountIdPrefix) -> AccountId.forPaymentAccount(userWalletId).right()
startsWith(AccountId.VirtualAccountIdPrefix) -> AccountId.forVirtualAccount(userWalletId).right()
else -> AccountId.forCryptoPortfolio(value = this, userWalletId = userWalletId)
}.getOrElse {
error("Unable to create AccountId from value: $this. Cause: $it")

View file

@ -11,6 +11,7 @@ import com.tangem.domain.common.wallets.getSyncStrict
import com.tangem.domain.core.flow.FlowProducerTools
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.features.virtualaccount.VirtualAccountFeatureToggles
import com.tangem.hot.sdk.model.HotWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
@ -37,6 +38,7 @@ internal class DefaultSingleAccountListProducer @AssistedInject constructor(
override val flowProducerTools: FlowProducerTools,
private val walletAccountListFlowFactory: WalletAccountListFlowFactory,
private val userWalletsListRepository: UserWalletsListRepository,
private val virtualAccountsFeatureToggles: VirtualAccountFeatureToggles,
private val dispatchers: CoroutineDispatcherProvider,
) : SingleAccountListProducer {
@ -51,18 +53,9 @@ internal class DefaultSingleAccountListProducer @AssistedInject constructor(
return walletAccountListFlowFactory.create(walletId)
.map { accountList ->
val userWallet = userWalletsListRepository.getSyncStrict(id = walletId)
val isPaymentSupported = userWallet.isPaymentAccountSupported()
logger.i(
"produce()[$walletId]: userWallet resolved (type=${userWallet::class.simpleName}), " +
"isPaymentAccountSupported=$isPaymentSupported",
)
if (isPaymentSupported) {
accountList.plus(Account.Payment(walletId)).getOrElse { throwable ->
error("Can not combine account list and payment account status: $throwable")
}
} else {
accountList
}
accountList
.addAccountIf(userWallet.isPaymentAccountSupported()) { Account.Payment(walletId) }
.addAccountIf(userWallet.isVirtualAccountSupported()) { Account.Virtual(walletId) }
}
.flowOn(dispatchers.default)
}
@ -72,6 +65,25 @@ internal class DefaultSingleAccountListProducer @AssistedInject constructor(
is UserWallet.Hot -> hotWalletId.authType != HotWalletId.AuthType.NoPassword
}
private fun UserWallet.isVirtualAccountSupported(): Boolean {
if (!virtualAccountsFeatureToggles.isVirtualAccountsEnabled) return false
return when (this) {
is UserWallet.Cold -> scanResponse.card.firmwareVersion >= FirmwareVersion.HDWalletAvailable
is UserWallet.Hot -> hotWalletId.authType != HotWalletId.AuthType.NoPassword
}
}
private inline fun AccountList.addAccountIf(condition: Boolean, account: () -> Account): AccountList {
return if (condition) {
plus(account()).getOrElse { throwable ->
error("Can not combine account list and special account: $throwable")
}
} else {
this
}
}
@AssistedFactory
interface Factory : SingleAccountListProducer.Factory {
override fun create(params: SingleAccountListProducer.Params): DefaultSingleAccountListProducer

View file

@ -1,13 +1,16 @@
package com.tangem.data.account.producer
import arrow.core.getOrElse
import com.google.common.truth.Truth
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.core.flow.FlowProducerTools
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.virtualaccount.VirtualAccountFeatureToggles
import com.tangem.hot.sdk.model.HotWalletId
import com.tangem.test.core.getEmittedValues
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
@ -40,12 +43,16 @@ class DefaultSingleAccountListProducerTest {
private val userWalletsListRepository = mockk<UserWalletsListRepository> {
every { userWallets } returns MutableStateFlow<List<UserWallet>?>(value = listOf(userWallet))
}
private val virtualAccountsFeatureToggles = mockk<VirtualAccountFeatureToggles> {
every { isVirtualAccountsEnabled } returns false
}
private val producer = DefaultSingleAccountListProducer(
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
walletAccountListFlowFactory = walletAccountListFlowFactory,
flowProducerTools = flowProducerTools,
userWalletsListRepository = userWalletsListRepository,
virtualAccountsFeatureToggles = virtualAccountsFeatureToggles,
dispatchers = TestingCoroutineDispatcherProvider(),
)
@ -72,6 +79,45 @@ class DefaultSingleAccountListProducerTest {
}
}
@Test
fun `GIVEN virtual accounts enabled WHEN produce THEN account list contains virtual account`() = runTest {
// Arrange
val supportedWallet = mockk<UserWallet.Hot> {
every { walletId } returns userWalletId
every { hotWalletId } returns mockk {
every { authType } returns HotWalletId.AuthType.Password
}
}
val userWalletsListRepository = mockk<UserWalletsListRepository> {
every { userWallets } returns MutableStateFlow<List<UserWallet>?>(value = listOf(supportedWallet))
}
val virtualAccountsFeatureToggles = mockk<VirtualAccountFeatureToggles> {
every { isVirtualAccountsEnabled } returns true
}
val producer = DefaultSingleAccountListProducer(
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
walletAccountListFlowFactory = walletAccountListFlowFactory,
flowProducerTools = flowProducerTools,
userWalletsListRepository = userWalletsListRepository,
virtualAccountsFeatureToggles = virtualAccountsFeatureToggles,
dispatchers = TestingCoroutineDispatcherProvider(),
)
val accountList = AccountList.empty(userWalletId)
every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList)
// Act
val actual = producer.produce().let(::getEmittedValues)
// Assert
val expected = accountList
.plus(Account.Payment(userWalletId))
.getOrElse { error("Unable to add payment account: $it") }
.plus(Account.Virtual(userWalletId))
.getOrElse { error("Unable to add virtual account: $it") }
Truth.assertThat(actual).containsExactly(expected)
}
@Test
fun `flow will updated if factoryFlow is updated`() = runTest {
// Arrange