Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-01 13:23:50 +04:00
parent 3aef6bf69c
commit b340fc7e33
18 changed files with 357 additions and 107 deletions

View file

@ -6,8 +6,8 @@ import com.tangem.data.common.network.NetworkFactory
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.DerivationIndex
@ -18,7 +18,7 @@ import javax.inject.Inject
/**
* Factory to create default [GetWalletAccountsResponse].
*
* @property userWalletsListRepository repository to get user wallet information
* @property userWalletsStore store to get user wallet information
* @property cryptoPortfolioCF converter factory to convert crypto portfolio accounts
* @property userTokensResponseFactory factory to create [UserTokensResponse]
* @property networkFactory factory to create network derivation path
@ -26,14 +26,14 @@ import javax.inject.Inject
[REDACTED_AUTHOR]
*/
internal class DefaultWalletAccountsResponseFactory @Inject constructor(
private val userWalletsListRepository: UserWalletsListRepository,
private val userWalletsStore: UserWalletsStore,
private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory,
private val userTokensResponseFactory: UserTokensResponseFactory,
private val networkFactory: NetworkFactory,
) {
suspend fun create(userWalletId: UserWalletId, userTokensResponse: UserTokensResponse?): GetWalletAccountsResponse {
val userWallet = userWalletsListRepository.userWalletsSync().firstOrNull { it.walletId == userWalletId }
fun create(userWalletId: UserWalletId, userTokensResponse: UserTokensResponse?): GetWalletAccountsResponse {
val userWallet = userWalletsStore.getSyncOrNull(userWalletId)
val accountDTOs = userWallet?.let(::createDefaultAccountDTOs).orEmpty()
val response = userTokensResponse.orDefault(userWallet = userWallet)

View file

@ -7,8 +7,8 @@ import com.tangem.data.common.currency.UserTokensResponseFactory
import com.tangem.data.common.network.NetworkFactory
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
@ -23,14 +23,14 @@ import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DefaultWalletAccountsResponseFactoryTest {
private val userWalletsListRepository = mockk<UserWalletsListRepository>()
private val userWalletsStore = mockk<UserWalletsStore>()
private val cryptoPortfolioCF = mockk<CryptoPortfolioConverter.Factory>()
private val cryptoPortfolioConverter = mockk<CryptoPortfolioConverter>()
private val userTokensResponseFactory = mockk<UserTokensResponseFactory>()
private val networkFactory = mockk<NetworkFactory>()
private val factory = DefaultWalletAccountsResponseFactory(
userWalletsListRepository = userWalletsListRepository,
userWalletsStore = userWalletsStore,
cryptoPortfolioCF = cryptoPortfolioCF,
userTokensResponseFactory = userTokensResponseFactory,
networkFactory = networkFactory,
@ -46,7 +46,7 @@ class DefaultWalletAccountsResponseFactoryTest {
@AfterEach
fun tearDownEach() {
clearMocks(
userWalletsListRepository,
userWalletsStore,
cryptoPortfolioCF,
cryptoPortfolioConverter,
userTokensResponseFactory,
@ -63,7 +63,7 @@ class DefaultWalletAccountsResponseFactoryTest {
tokens = emptyList(),
)
coEvery { userWalletsListRepository.userWalletsSync() } returns emptyList()
coEvery { userWalletsStore.getSyncOrNull(userWalletId) } returns null
every {
userTokensResponseFactory.createDefaultResponse(
userWallet = null,
@ -88,7 +88,7 @@ class DefaultWalletAccountsResponseFactoryTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
userWalletsListRepository.userWalletsSync()
userWalletsStore.getSyncOrNull(userWalletId)
userTokensResponseFactory.createDefaultResponse(
userWallet = null,
networkFactory = networkFactory,
@ -107,7 +107,7 @@ class DefaultWalletAccountsResponseFactoryTest {
val accounts = AccountList.empty(userWallet.walletId).accounts
.filterIsInstance<Account.CryptoPortfolio>()
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
coEvery { userWalletsStore.getSyncOrNull(userWalletId) } returns userWallet
val defaultResponse = UserTokensResponse(
group = UserTokensResponse.GroupType.NETWORK,
@ -142,7 +142,7 @@ class DefaultWalletAccountsResponseFactoryTest {
Truth.assertThat(actual).isEqualTo(expected)
coVerifyOrder {
userWalletsListRepository.userWalletsSync()
userWalletsStore.getSyncOrNull(userWalletId)
cryptoPortfolioConverter.convertListBack(accounts)
userTokensResponseFactory.createDefaultResponse(
userWallet = userWallet,
@ -162,7 +162,7 @@ class DefaultWalletAccountsResponseFactoryTest {
val accounts = AccountList.empty(userWallet.walletId).accounts
.filterIsInstance<Account.CryptoPortfolio>()
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
coEvery { userWalletsStore.getSyncOrNull(userWalletId) } returns userWallet
val defaultResponse = UserTokensResponse(
group = UserTokensResponse.GroupType.NETWORK,
@ -203,7 +203,7 @@ class DefaultWalletAccountsResponseFactoryTest {
every { walletId } returns userWalletId
}
val assignedTokens = listOf(mockk<CryptoCurrency.Token>(), mockk<CryptoCurrency.Token>())
coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet)
coEvery { userWalletsStore.getSyncOrNull(userWalletId) } returns userWallet
val userTokensResponse = UserTokensResponse(
group = UserTokensResponse.GroupType.NETWORK,
sort = UserTokensResponse.SortType.BALANCE,

View file

@ -6,7 +6,6 @@ import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.status.usecase.*
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.express.ExpressServiceFetcher
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
@ -34,12 +33,12 @@ internal object AccountStatusUseCaseModule {
@Provides
@Singleton
fun provideGetAccountCurrencyByAddressUseCase(
userWalletsListRepository: UserWalletsListRepository,
accountsCRUDRepository: AccountsCRUDRepository,
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
singleAccountListSupplier: SingleAccountListSupplier,
): GetAccountCurrencyByAddressUseCase {
return GetAccountCurrencyByAddressUseCase(
userWalletsListRepository = userWalletsListRepository,
accountsCRUDRepository = accountsCRUDRepository,
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
singleAccountListSupplier = singleAccountListSupplier,
)

View file

@ -3,20 +3,23 @@ package com.tangem.domain.account.status.producer
import arrow.core.Option
import arrow.core.some
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
/**
* Produces a flow of [AccountStatusList] for multiple user wallets.
*
* @property params Parameters for the producer (currently unused).
* @property userWalletsListRepository Repository to get the list of user wallets.
* @property accountsCRUDRepository Repository to get the list of user wallets.
* @property singleAccountStatusListSupplier Supplier to get the account status list for a single user wallet.
* @property dispatchers Coroutine dispatcher provider for managing threading.
*
@ -24,7 +27,7 @@ import kotlinx.coroutines.flow.*
*/
internal class DefaultMultiAccountStatusListProducer @AssistedInject constructor(
@Assisted val params: Unit,
private val userWalletsListRepository: UserWalletsListRepository,
private val accountsCRUDRepository: AccountsCRUDRepository,
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val dispatchers: CoroutineDispatcherProvider,
) : MultiAccountStatusListProducer {
@ -33,8 +36,7 @@ internal class DefaultMultiAccountStatusListProducer @AssistedInject constructor
@OptIn(ExperimentalCoroutinesApi::class)
override fun produce(): Flow<List<AccountStatusList>> {
return userWalletsListRepository.userWallets
.filterNotNull()
return accountsCRUDRepository.getUserWallets()
.flatMapLatest { userWallets ->
val flows = userWallets.map {
singleAccountStatusListSupplier(

View file

@ -54,7 +54,6 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
params = SingleAccountListProducer.Params(params.userWalletId),
)
@Suppress("UnusedFlow")
return accountListFlow.flatMapLatest { accountList ->
val accountStatusFlows = accountList.accounts.mapNotNull { account ->
if (account !is Account.CryptoPortfolio) return@mapNotNull null
@ -62,11 +61,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo
if (account.cryptoCurrencies.isEmpty()) {
createEmptyAccountStatusFlow(account)
} else {
val userWallet = accountsCRUDRepository.getUserWallets()
.mapNotNull { wallets ->
wallets.find { it.walletId == params.userWalletId }
}
.first()
val userWallet = accountsCRUDRepository.getUserWallet(userWalletId = params.userWalletId)
getAccountStatusFlow(
userWallet = userWallet,

View file

@ -9,9 +9,9 @@ import arrow.core.raise.option
import arrow.core.toNonEmptyListOrNull
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.status.model.AccountCryptoCurrency
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.getAddress
@ -29,14 +29,14 @@ private typealias WalletIdWithNetworkId = Pair<UserWalletId, Network.ID>
/**
* Use case to retrieve an [AccountCryptoCurrency] based on a provided address.
*
* @property userWalletsListRepository Repository to access user wallets.
* @property accountsCRUDRepository Repository to access user wallets.
* @property multiNetworkStatusSupplier Supplier to get network status for multiple networks.
* @property singleAccountListSupplier Supplier to get account lists for a single wallet.
*
[REDACTED_AUTHOR]
*/
class GetAccountCurrencyByAddressUseCase(
private val userWalletsListRepository: UserWalletsListRepository,
private val accountsCRUDRepository: AccountsCRUDRepository,
private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
private val singleAccountListSupplier: SingleAccountListSupplier,
) {
@ -65,7 +65,7 @@ class GetAccountCurrencyByAddressUseCase(
}
private fun OptionRaise.getUserWalletIds(): NonEmptyList<UserWalletId> {
val userWalletIds = userWalletsListRepository.userWallets.value.orEmpty()
val userWalletIds = accountsCRUDRepository.getUserWalletsSync()
.filter(UserWallet::isMultiCurrency)
.map(UserWallet::walletId)
.toNonEmptyListOrNull()

View file

@ -3,8 +3,8 @@ package com.tangem.domain.account.status.producer
import com.google.common.truth.Truth
import com.tangem.common.test.utils.getEmittedValues
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
@ -20,7 +20,7 @@ import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DefaultMultiAccountStatusListProducerTest {
private val userWalletsListRepository: UserWalletsListRepository = mockk()
private val accountsCRUDRepository: AccountsCRUDRepository = mockk()
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier = mockk()
private val dispatchers = TestingCoroutineDispatcherProvider()
@ -36,14 +36,14 @@ class DefaultMultiAccountStatusListProducerTest {
private val producer = DefaultMultiAccountStatusListProducer(
params = Unit,
userWalletsListRepository = userWalletsListRepository,
accountsCRUDRepository = accountsCRUDRepository,
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
dispatchers = dispatchers,
)
@AfterEach
fun tearDown() {
clearMocks(userWalletsListRepository, singleAccountStatusListSupplier)
clearMocks(accountsCRUDRepository, singleAccountStatusListSupplier)
}
@Test
@ -52,7 +52,7 @@ class DefaultMultiAccountStatusListProducerTest {
val wallets = listOf(userWallet1, userWallet2)
val walletsFlow = MutableStateFlow(wallets)
every { userWalletsListRepository.userWallets } returns walletsFlow
every { accountsCRUDRepository.getUserWallets() } returns walletsFlow
val accountStatusList1 = mockk<AccountStatusList>()
val accountStatusList2 = mockk<AccountStatusList>()
@ -76,7 +76,7 @@ class DefaultMultiAccountStatusListProducerTest {
Truth.assertThat(actual).containsExactly(expected)
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWallets()
singleAccountStatusListSupplier(
params = SingleAccountStatusListProducer.Params(userWalletId1),
)
@ -90,7 +90,7 @@ class DefaultMultiAccountStatusListProducerTest {
fun `produce returns empty flow if userWallets is empty list`() = runTest {
// Arrange
val walletsFlow = MutableStateFlow<List<UserWallet>>(emptyList())
every { userWalletsListRepository.userWallets } returns walletsFlow
every { accountsCRUDRepository.getUserWallets() } returns walletsFlow
// Act
val actual = producer.produce().let(::getEmittedValues)
@ -99,26 +99,27 @@ class DefaultMultiAccountStatusListProducerTest {
Truth.assertThat(actual).isEmpty()
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWallets()
}
}
@Test
fun `produce returns empty flow if userWallets is null`() = runTest {
// Arrange
val walletsFlow = MutableStateFlow<List<UserWallet>?>(null)
every { userWalletsListRepository.userWallets } returns walletsFlow
// Act
val actual = producer.produce().let(::getEmittedValues)
// Assert
Truth.assertThat(actual).isEmpty()
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsListRepository.userWallets
}
}
// TODO: uncomment after migration on UserWalletsListRepository
// @Test
// fun `produce returns empty flow if userWallets is null`() = runTest {
// // Arrange
// val walletsFlow = MutableStateFlow<List<UserWallet>?>(null)
// every { accountsCRUDRepository.getUserWallets() } returns walletsFlow
//
// // Act
// val actual = producer.produce().let(::getEmittedValues)
//
// // Assert
// Truth.assertThat(actual).isEmpty()
//
// coVerify(ordering = Ordering.SEQUENCE) {
// accountsCRUDRepository.getUserWallets()
// }
// }
@Test
fun `flow will updated if userWallets are updated`() = runTest {
@ -127,7 +128,7 @@ class DefaultMultiAccountStatusListProducerTest {
val userWallet3 = mockk<UserWallet> { every { walletId } returns userWalletId3 }
val walletsFlow = MutableStateFlow(listOf(userWallet1, userWallet2))
every { userWalletsListRepository.userWallets } returns walletsFlow
every { accountsCRUDRepository.getUserWallets() } returns walletsFlow
val accountStatusList1 = mockk<AccountStatusList>()
val accountStatusList2 = mockk<AccountStatusList>()
@ -167,7 +168,7 @@ class DefaultMultiAccountStatusListProducerTest {
Truth.assertThat(actual2).containsExactly(expected2)
coVerify(ordering = Ordering.SEQUENCE) {
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWallets()
singleAccountStatusListSupplier(
params = SingleAccountStatusListProducer.Params(userWalletId1),
)
@ -183,7 +184,7 @@ class DefaultMultiAccountStatusListProducerTest {
singleAccountStatusListSupplier(
params = SingleAccountStatusListProducer.Params(userWalletId3),
)
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWallets()
singleAccountStatusListSupplier(
params = SingleAccountStatusListProducer.Params(userWalletId1),
)

View file

@ -206,7 +206,7 @@ class DefaultSingleAccountStatusListProducerTest {
cryptoCurrencies = cryptoCurrencyFactory.ethereumAndStellar.toSet(),
)
coEvery { accountsCRUDRepository.getUserWallets() } returns flowOf(listOf(userWallet))
coEvery { accountsCRUDRepository.getUserWallet(userWalletId = userWalletId) } returns userWallet
every {
singleAccountListSupplier(params = SingleAccountListProducer.Params(userWalletId))

View file

@ -5,9 +5,9 @@ import com.tangem.common.test.utils.assertNone
import com.tangem.common.test.utils.assertSome
import com.tangem.domain.account.models.AccountList
import com.tangem.domain.account.producer.SingleAccountListProducer
import com.tangem.domain.account.repository.AccountsCRUDRepository
import com.tangem.domain.account.status.model.AccountCryptoCurrency
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.network.NetworkStatus
@ -17,7 +17,6 @@ import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
import io.mockk.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
@ -29,19 +28,19 @@ import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class GetAccountCurrencyByAddressUseCaseTest {
private val userWalletsListRepository: UserWalletsListRepository = mockk()
private val accountsCRUDRepository: AccountsCRUDRepository = mockk()
private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier = mockk()
private val singleAccountListSupplier: SingleAccountListSupplier = mockk()
private val useCase = GetAccountCurrencyByAddressUseCase(
userWalletsListRepository = userWalletsListRepository,
accountsCRUDRepository = accountsCRUDRepository,
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
singleAccountListSupplier = singleAccountListSupplier,
)
@AfterEach
fun tearDown() {
clearMocks(userWalletsListRepository, multiNetworkStatusSupplier, singleAccountListSupplier)
clearMocks(accountsCRUDRepository, multiNetworkStatusSupplier, singleAccountListSupplier)
}
@Test
@ -56,26 +55,27 @@ class GetAccountCurrencyByAddressUseCaseTest {
assertNone(actual)
}
@Test
fun `returns None if userWalletIds is null`() = runTest {
// Arrange
every { userWalletsListRepository.userWallets } returns MutableStateFlow(null)
// Act
val actual = useCase(validAddress)
// Assert
assertNone(actual)
coVerifySequence {
userWalletsListRepository.userWallets
}
}
// TODO: uncomment after migration on UserWalletsListRepository
// @Test
// fun `returns None if userWalletIds is null`() = runTest {
// // Arrange
// every { accountsCRUDRepository.getUserWalletsSync() } returns MutableStateFlow(null)
//
// // Act
// val actual = useCase(validAddress)
//
// // Assert
// assertNone(actual)
//
// coVerifySequence {
// accountsCRUDRepository.getUserWalletsSync()
// }
// }
@Test
fun `returns None if userWalletIds is empty list`() = runTest {
// Arrange
every { userWalletsListRepository.userWallets } returns MutableStateFlow(emptyList())
every { accountsCRUDRepository.getUserWalletsSync() } returns emptyList()
// Act
val actual = useCase(validAddress)
@ -84,7 +84,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
assertNone(actual)
coVerifySequence {
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWalletsSync()
}
}
@ -95,7 +95,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
every { isMultiCurrency } returns false
}
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(singleWallet))
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(singleWallet)
// Act
val actual = useCase(validAddress)
@ -104,14 +104,14 @@ class GetAccountCurrencyByAddressUseCaseTest {
assertNone(actual)
coVerifySequence {
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWalletsSync()
}
}
@Test
fun `returns None if multiNetworkStatusSupplier returns null`() = runTest {
// Arrange
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(multiUserWallet))
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
coEvery {
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
} returns null
@ -123,7 +123,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
assertNone(actual)
coVerifySequence {
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWalletsSync()
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
}
}
@ -131,7 +131,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
@Test
fun `returns None if multiNetworkStatusSupplier returns empty list`() = runTest {
// Arrange
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(multiUserWallet))
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
coEvery {
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
} returns emptySet()
@ -143,7 +143,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
assertNone(actual)
coVerifySequence {
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWalletsSync()
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
}
}
@ -156,7 +156,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
value = NetworkStatus.Unreachable(address = null),
)
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(multiUserWallet))
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
coEvery {
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
} returns setOf(networkStatus)
@ -168,7 +168,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
assertNone(actual)
coVerifySequence {
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWalletsSync()
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
}
}
@ -183,7 +183,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
value = NetworkStatus.Unreachable(address = validNetworkAddress),
)
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(multiUserWallet))
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
coEvery {
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
} returns setOf(networkStatus)
@ -200,7 +200,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
assertNone(actual)
coVerifySequence {
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWalletsSync()
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
singleAccountListSupplier.getSyncOrNull(
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
@ -220,7 +220,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
)
val accountList = AccountList.empty(userWalletId)
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(multiUserWallet))
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
coEvery {
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
} returns setOf(networkStatus)
@ -237,7 +237,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
assertNone(actual)
coVerifySequence {
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWalletsSync()
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
singleAccountListSupplier.getSyncOrNull(
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
@ -255,7 +255,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
)
val accountList = AccountList.empty(userWalletId = userWalletId, cryptoCurrencies = setOf(currency))
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(multiUserWallet))
every { accountsCRUDRepository.getUserWalletsSync() } returns listOf(multiUserWallet)
coEvery {
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
} returns setOf(networkStatus)
@ -273,7 +273,7 @@ class GetAccountCurrencyByAddressUseCaseTest {
assertSome(actual, expected)
coVerifySequence {
userWalletsListRepository.userWallets
accountsCRUDRepository.getUserWalletsSync()
multiNetworkStatusSupplier.getSyncOrNull(params = MultiNetworkStatusProducer.Params(userWalletId))
singleAccountListSupplier.getSyncOrNull(
params = SingleAccountListProducer.Params(userWalletId = userWalletId),

View file

@ -17,6 +17,10 @@ sealed interface AccountStatus {
/** The account associated with this status */
val account: Account
/** Unique identifier of the account */
val accountId: AccountId
get() = account.accountId
/**
* Represents the status of a crypto portfolio account
*

View file

@ -9,10 +9,12 @@ import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.*
import javax.inject.Inject
@Suppress("LongParameterList")
@ModelScoped
internal class WalletContentLoaderFactory @Inject constructor(
private val multiWalletContentLoaderFactory: MultiWalletContentLoaderFactory,
private val singleWalletWithTokenContentLoaderFactory: SingleWalletWithTokenContentLoaderFactory,
private val singleWalletWithTokenContentLoaderV2Factory: SingleWalletWithTokenContentLoaderV2.Factory,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val singleWalletContentLoaderFactory: SingleWalletContentLoaderFactory,
private val singleWalletContentLoaderV2Factory: SingleWalletContentLoaderV2.Factory,
@ -29,7 +31,11 @@ internal class WalletContentLoaderFactory @Inject constructor(
multiWalletContentLoaderFactory.create(userWallet, clickIntents)
}
userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() -> {
singleWalletWithTokenContentLoaderFactory.create(userWallet, clickIntents)
if (accountsFeatureToggles.isFeatureEnabled) {
singleWalletWithTokenContentLoaderV2Factory.create(userWallet)
} else {
singleWalletWithTokenContentLoaderFactory.create(userWallet, clickIntents)
}
}
userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isVisaWallet() -> {
visaWalletContentLoaderFactory.create(userWallet, clickIntents, isRefresh)

View file

@ -1,13 +1,14 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender
@ -16,8 +17,8 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.feature.wallet.presentation.account.AccountDependencies
@Deprecated("Use SingleWalletWithTokenContentLoaderV2 instead")
@Suppress("LongParameterList")
internal class SingleWalletWithTokenContentLoader(
private val userWallet: UserWallet.Cold,

View file

@ -2,13 +2,14 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender
@ -16,11 +17,11 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import javax.inject.Inject
// TODO: Refactor
@Suppress("LongParameterList")
@Deprecated("Use SingleWalletWithTokenContentLoaderV2.Factory instead")
@ModelScoped
internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
private val stateHolder: WalletStateController,

View file

@ -0,0 +1,50 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("LongParameterList")
internal class SingleWalletWithTokenContentLoaderV2 @AssistedInject constructor(
@Assisted private val userWallet: UserWallet.Cold,
private val singleWalletWithTokenSubscriberFactory: SingleWalletWithTokenSubscriber.Factory,
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
private val clickIntents: WalletClickIntents,
private val stateController: WalletStateController,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> = listOf(
singleWalletWithTokenSubscriberFactory.create(userWallet),
MultiWalletWarningsSubscriber(
userWallet = userWallet,
stateHolder = stateController,
clickIntents = clickIntents,
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
),
WalletDropDownItemsSubscriber(
stateHolder = stateController,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
clickIntents = clickIntents,
),
checkWalletWithFundsSubscriberFactory.create(userWallet),
)
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet.Cold): SingleWalletWithTokenContentLoaderV2
}
}

View file

@ -0,0 +1,146 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListErrorTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import timber.log.Timber
/**
* Basic implementation of [WalletSubscriber] for wallet with accounts.
*
[REDACTED_AUTHOR]
*/
internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
abstract val accountDependencies: AccountDependencies
abstract val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase
abstract val stateController: WalletStateController
abstract val clickIntents: WalletClickIntents
override val singleAccountStatusListSupplier: SingleAccountStatusListSupplier
get() = accountDependencies.singleAccountStatusListSupplier
protected fun getAppCurrencyFlow(): Flow<AppCurrency> {
return getSelectedAppCurrencyUseCase.invokeOrDefault()
.distinctUntilChanged()
}
protected fun updateState(
accountList: AccountStatusList,
appCurrency: AppCurrency,
expandedAccounts: Set<AccountId>,
isAccountMode: Boolean,
yieldSupplyApyMap: Map<String, String> = emptyMap(),
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
) {
val accountFlattenCurrencies = accountList.flattenCurrencies()
val mainAccount = accountList.mainAccount
when {
!isAccountMode -> {
val isMainAccountEmpty = mainAccount.tokenList.flattenCurrencies().isEmpty()
val maybeTokenList = if (isMainAccountEmpty) {
Lce.Error(TokenListError.EmptyTokens)
} else {
Lce.Content(mainAccount.tokenList)
}
singleAccountTransform(
maybeTokenList = maybeTokenList,
appCurrency = appCurrency,
portfolioId = PortfolioId(mainAccount.accountId),
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
)
}
isAccountMode -> {
val isAllAccountsEmpty = accountFlattenCurrencies.isEmpty()
if (isAllAccountsEmpty) {
stateController.update(
SetTokenListErrorTransformer(
selectedWallet = userWallet,
error = TokenListError.EmptyTokens,
appCurrency = appCurrency,
),
)
} else {
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)
}
}
}
}
private fun singleAccountTransform(
maybeTokenList: Lce<TokenListError, TokenList>,
appCurrency: AppCurrency,
portfolioId: PortfolioId,
yieldSupplyApyMap: Map<String, String> = emptyMap(),
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
) {
val tokenList = maybeTokenList.getOrElse(
ifLoading = { maybeContent ->
val isRefreshing = stateController.getWalletState(userWallet.walletId)
?.pullToRefreshConfig
?.isRefreshing == true
maybeContent
?.takeIf { !isRefreshing }
?: return
},
ifError = { e ->
Timber.e("Failed to load token list: $e")
stateController.update(
SetTokenListErrorTransformer(
selectedWallet = userWallet,
error = e,
appCurrency = appCurrency,
),
)
return
},
)
updateContent(
params = TokenConverterParams.Wallet(portfolioId, tokenList),
appCurrency = appCurrency,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
)
}
private fun updateContent(
params: TokenConverterParams,
appCurrency: AppCurrency,
yieldSupplyApyMap: Map<String, String> = emptyMap(),
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
) {
stateController.update(
SetTokenListTransformer(
params = params,
userWallet = userWallet,
appCurrency = appCurrency,
clickIntents = clickIntents,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingApyMap = stakingApyMap,
),
)
}
}

View file

@ -4,6 +4,9 @@ import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
@ -13,11 +16,12 @@ import kotlinx.coroutines.flow.*
* @property userWallet The user wallet.
* @property singleAccountStatusListSupplier Supplier for account status list.
* @property walletWithFundsChecker The checker to notify when funds are detected.
* @property dispatchers Coroutine dispatcher provider.
*
[REDACTED_AUTHOR]
*/
internal class CheckWalletWithFundsSubscriber(
override val userWallet: UserWallet,
internal class CheckWalletWithFundsSubscriber @AssistedInject constructor(
@Assisted override val userWallet: UserWallet,
override val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val dispatchers: CoroutineDispatcherProvider,
@ -30,4 +34,9 @@ internal class CheckWalletWithFundsSubscriber(
.onEach { walletWithFundsChecker.check(userWalletId = userWallet.walletId, currencies = it) }
.flowOn(dispatchers.default)
}
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet): CheckWalletWithFundsSubscriber
}
}

View file

@ -20,6 +20,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@Deprecated("Use SingleWalletWithTokenSubscriber instead")
@Suppress("LongParameterList")
internal class SingleWalletWithTokenListSubscriber(
override val userWallet: UserWallet.Cold,

View file

@ -0,0 +1,35 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
internal class SingleWalletWithTokenSubscriber @AssistedInject constructor(
@Assisted override val userWallet: UserWallet.Cold,
override val accountDependencies: AccountDependencies,
override val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
override val stateController: WalletStateController,
override val clickIntents: WalletClickIntents,
) : BasicAccountListSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<Unit> = combine(
flow = getAccountStatusListFlow(),
flow2 = getAppCurrencyFlow(),
flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet),
flow4 = accountDependencies.isAccountsModeEnabledUseCase(),
transform = ::updateState,
)
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet.Cold): SingleWalletWithTokenSubscriber
}
}