Updated on 2026-08-14
This commit is contained in:
parent
e2721b21e1
commit
a67ca6279f
14 changed files with 24 additions and 614 deletions
|
|
@ -23,7 +23,6 @@ import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
|||
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
|
|
@ -138,8 +137,6 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getApiConfigsManager(): ApiConfigsManager
|
||||
|
||||
fun getUserTokensResponseStore(): UserTokensResponseStore
|
||||
|
||||
fun getUserWalletsListRepository(): UserWalletsListRepository
|
||||
|
||||
fun getTangemHotSdk(): TangemHotSdk
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
|||
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.utils.NetworkLogsSaveInterceptor
|
||||
import com.tangem.datasource.utils.WireMockRedirectInterceptor
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
|
|
@ -218,9 +217,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
private val apiConfigsManager: ApiConfigsManager
|
||||
get() = entryPoint.getApiConfigsManager()
|
||||
|
||||
private val userTokensResponseStore: UserTokensResponseStore
|
||||
get() = entryPoint.getUserTokensResponseStore()
|
||||
|
||||
private val userWalletsListRepository
|
||||
get() = entryPoint.getUserWalletsListRepository()
|
||||
|
||||
|
|
@ -381,7 +377,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
settingsManager = settingsManager,
|
||||
uiMessageSender = uiMessageSender,
|
||||
coldUserWalletBuilderFactory = coldUserWalletBuilderFactory,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
tangemHotSdk = tangemHotSdk,
|
||||
trackingContextProxy = trackingContextProxy,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.tangem.domain.nft.utils.NFTCleaner
|
|||
import com.tangem.domain.quotes.single.SingleQuoteStatusFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -63,9 +62,7 @@ internal object NFTDomainModule {
|
|||
fun providesGetNFTAvailableNetworksUseCase(
|
||||
nftRepository: NFTRepository,
|
||||
singleAccountListSupplier: SingleAccountListSupplier,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
): GetNFTNetworksUseCase = GetNFTNetworksUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
singleAccountListSupplier = singleAccountListSupplier,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import com.tangem.data.card.TransactionSignerFactory
|
|||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.datasource.local.config.issuers.IssuersConfigStorage
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||
|
|
@ -71,7 +70,6 @@ data class DaggerGraphState(
|
|||
val uiMessageSender: UiMessageSender? = null,
|
||||
val cardArworksProvider: CardArtworksProvider? = null,
|
||||
val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory? = null,
|
||||
val userTokensResponseStore: UserTokensResponseStore? = null,
|
||||
val userWalletsListRepository: UserWalletsListRepository? = null,
|
||||
val tangemHotSdk: TangemHotSdk? = null,
|
||||
val trackingContextProxy: TrackingContextProxy? = null,
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
package com.tangem.data.account.producer
|
||||
|
||||
import arrow.core.Option
|
||||
import arrow.core.some
|
||||
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.getSyncStrict
|
||||
import com.tangem.domain.core.flow.FlowProducerTools
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Default implementation of [MultiWalletCryptoCurrenciesProducer]
|
||||
*
|
||||
* @property params params
|
||||
* @property flowProducerTools tools for producing flows
|
||||
* @property userWalletsListRepository repository for getting user wallets
|
||||
* @property userTokensResponseStore store of `UserTokensResponse`
|
||||
* @property responseCryptoCurrenciesFactory factory for creating [CryptoCurrency] from `UserTokensResponse`
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultMultiWalletCryptoCurrenciesProducer @AssistedInject constructor(
|
||||
@Assisted val params: MultiWalletCryptoCurrenciesProducer.Params,
|
||||
override val flowProducerTools: FlowProducerTools,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val userTokensResponseStore: UserTokensResponseStore,
|
||||
private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : MultiWalletCryptoCurrenciesProducer {
|
||||
|
||||
override val fallback: Option<Set<CryptoCurrency>> = emptySet<CryptoCurrency>().some()
|
||||
|
||||
override fun produce(): Flow<Set<CryptoCurrency>> {
|
||||
val userWallet = userWalletsListRepository.getSyncStrict(id = params.userWalletId)
|
||||
|
||||
if (!userWallet.isMultiCurrency) {
|
||||
error("${this::class.simpleName ?: this::class.toString()} supports only multi-currency wallet")
|
||||
}
|
||||
|
||||
return userTokensResponseStore.get(userWalletId = params.userWalletId)
|
||||
.distinctUntilChanged()
|
||||
.map { response ->
|
||||
if (response == null) return@map emptySet()
|
||||
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = response,
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
).toSet()
|
||||
}
|
||||
.onEmpty { emit(emptySet()) }
|
||||
.flowOn(dispatchers.default)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MultiWalletCryptoCurrenciesProducer.Factory {
|
||||
override fun create(
|
||||
params: MultiWalletCryptoCurrenciesProducer.Params,
|
||||
): DefaultMultiWalletCryptoCurrenciesProducer
|
||||
}
|
||||
}
|
||||
|
|
@ -1,378 +0,0 @@
|
|||
package com.tangem.data.account.producer
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.test.domain.card.MockScanResponseFactory
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
|
||||
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.card.configs.GenericCardConfig
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.core.flow.FlowProducerTools
|
||||
import com.tangem.domain.models.account.DerivationIndex
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
||||
import com.tangem.test.core.getEmittedValues
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
|
||||
|
||||
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
|
||||
private val params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWallet.walletId)
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxUnitFun = true)
|
||||
private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxUnitFun = true)
|
||||
private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory = mockk()
|
||||
private val flowProducerTools: FlowProducerTools = mockk()
|
||||
|
||||
private val producer = DefaultMultiWalletCryptoCurrenciesProducer(
|
||||
params = params,
|
||||
flowProducerTools = flowProducerTools,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(userWalletsListRepository, userTokensResponseStore, responseCryptoCurrenciesFactory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is mapped for user wallet id from params`() = runTest {
|
||||
// Arrange
|
||||
val userTokensResponseFlow = flowOf<UserTokensResponse?>(null)
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
every { userTokensResponseStore.get(params.userWalletId) } returns userTokensResponseFlow
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = emptySet<CryptoCurrency>()
|
||||
|
||||
Truth.assertThat(actual.size).isEqualTo(1)
|
||||
Truth.assertThat(actual.first()).isEqualTo(expected)
|
||||
|
||||
verifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
userTokensResponseStore.get(params.userWalletId)
|
||||
}
|
||||
|
||||
verify(inverse = true) {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any(), accountIndex = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow will updated if UserTokensResponse is updated`() = runTest {
|
||||
// Arrange
|
||||
val userTokensResponseFlow = MutableSharedFlow<UserTokensResponse>(replay = 2)
|
||||
|
||||
val userTokensResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.TOKEN,
|
||||
sort = UserTokensResponse.SortType.MARKETCAP,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
val cryptoCurrencies = emptySet<CryptoCurrency>()
|
||||
|
||||
val updatedUserTokensResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.TOKEN,
|
||||
sort = UserTokensResponse.SortType.MARKETCAP,
|
||||
tokens = listOf(
|
||||
UserTokensResponse.Token(
|
||||
id = null,
|
||||
networkId = "bitcoin",
|
||||
derivationPath = null,
|
||||
name = "Bitcoin",
|
||||
symbol = "BTC",
|
||||
decimals = 8,
|
||||
contractAddress = null,
|
||||
addresses = listOf(),
|
||||
),
|
||||
),
|
||||
)
|
||||
val updatedCryptoCurrencies = setOf(
|
||||
cryptoCurrencyFactory.createCoin(Blockchain.Bitcoin),
|
||||
)
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
every { userTokensResponseStore.get(params.userWalletId) } returns userTokensResponseFlow
|
||||
|
||||
every {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
)
|
||||
} returns cryptoCurrencies.toList()
|
||||
|
||||
every {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = updatedUserTokensResponse,
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
)
|
||||
} returns updatedCryptoCurrencies.toList()
|
||||
|
||||
val producerFlow = producer.produce()
|
||||
|
||||
// Act 1 (first emit)
|
||||
userTokensResponseFlow.emit(userTokensResponse)
|
||||
|
||||
val actual1 = getEmittedValues(flow = producerFlow)
|
||||
|
||||
// Assert
|
||||
val expected1 = cryptoCurrencies
|
||||
|
||||
Truth.assertThat(actual1.size).isEqualTo(1)
|
||||
Truth.assertThat(actual1.first()).isEqualTo(expected1)
|
||||
|
||||
verifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
userTokensResponseStore.get(params.userWalletId)
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
)
|
||||
}
|
||||
|
||||
// Act 2 (second emit)
|
||||
userTokensResponseFlow.emit(updatedUserTokensResponse)
|
||||
|
||||
val actual2 = getEmittedValues(flow = producerFlow)
|
||||
|
||||
// Assert
|
||||
val expected2 = listOf(cryptoCurrencies, updatedCryptoCurrencies)
|
||||
|
||||
Truth.assertThat(actual2.size).isEqualTo(2)
|
||||
Truth.assertThat(actual2).isEqualTo(expected2)
|
||||
|
||||
verifyOrder {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = updatedUserTokensResponse,
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is filtered the same status`() = runTest {
|
||||
// Arrange
|
||||
val userTokensResponseFlow = MutableSharedFlow<UserTokensResponse>(replay = 2)
|
||||
|
||||
val userTokensResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.TOKEN,
|
||||
sort = UserTokensResponse.SortType.MARKETCAP,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
|
||||
val cryptoCurrencies = emptySet<CryptoCurrency>()
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
every { userTokensResponseStore.get(params.userWalletId) } returns userTokensResponseFlow
|
||||
|
||||
every {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
)
|
||||
} returns cryptoCurrencies.toList()
|
||||
|
||||
val producerFlow = producer.produce()
|
||||
|
||||
// Act 1 (first emit)
|
||||
userTokensResponseFlow.emit(userTokensResponse)
|
||||
|
||||
val actual1 = getEmittedValues(flow = producerFlow)
|
||||
|
||||
// Assert
|
||||
val expected1 = cryptoCurrencies
|
||||
|
||||
Truth.assertThat(actual1.size).isEqualTo(1)
|
||||
Truth.assertThat(actual1.first()).isEqualTo(expected1)
|
||||
|
||||
verifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
userTokensResponseStore.get(params.userWalletId)
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
)
|
||||
}
|
||||
|
||||
// Act 2 (second emit)
|
||||
userTokensResponseFlow.emit(userTokensResponse)
|
||||
|
||||
val actual2 = getEmittedValues(flow = producerFlow)
|
||||
|
||||
// Assert
|
||||
val expected2 = expected1
|
||||
Truth.assertThat(actual2.size).isEqualTo(1)
|
||||
Truth.assertThat(actual2.first()).isEqualTo(expected2)
|
||||
}
|
||||
|
||||
@Disabled
|
||||
@Test
|
||||
fun `flow throws exception`() = runTest {
|
||||
// Arrange
|
||||
val exception = IllegalStateException()
|
||||
|
||||
val userTokensResponse = UserTokensResponse(
|
||||
group = UserTokensResponse.GroupType.TOKEN,
|
||||
sort = UserTokensResponse.SortType.MARKETCAP,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
|
||||
val cryptoCurrencies = emptySet<CryptoCurrency>()
|
||||
|
||||
val innerFlow = MutableStateFlow(value = false)
|
||||
val userTokensResponseFlow = flow {
|
||||
if (innerFlow.value) {
|
||||
emit(userTokensResponse)
|
||||
} else {
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
.buffer(capacity = 5)
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
every { userTokensResponseStore.get(params.userWalletId) } returns userTokensResponseFlow
|
||||
|
||||
every {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
)
|
||||
} returns cryptoCurrencies.toList()
|
||||
|
||||
val producerFlow = producer.produceWithFallback()
|
||||
|
||||
// Act 1 (fallback)
|
||||
val actual1 = getEmittedValues(flow = producerFlow)
|
||||
|
||||
// Assert
|
||||
val expected1 = producer.fallback.getOrNull()
|
||||
Truth.assertThat(actual1.size).isEqualTo(1)
|
||||
Truth.assertThat(actual1.first()).isEqualTo(expected1)
|
||||
|
||||
verifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
userTokensResponseStore.get(params.userWalletId)
|
||||
}
|
||||
|
||||
// Act 2 (emit)
|
||||
innerFlow.emit(value = true)
|
||||
val actual2 = getEmittedValues(flow = producerFlow)
|
||||
|
||||
// Assert
|
||||
val expected2 = cryptoCurrencies
|
||||
Truth.assertThat(actual2.size).isEqualTo(1)
|
||||
Truth.assertThat(actual2.first()).isEqualTo(expected2)
|
||||
|
||||
verifyOrder {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
userWallet = userWallet,
|
||||
accountIndex = DerivationIndex.Main,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flow is empty if store returns empty flow`() = runTest {
|
||||
// Arrange
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
every { userTokensResponseStore.get(params.userWalletId) } returns emptyFlow()
|
||||
|
||||
// Act
|
||||
val actual = producer.produce().let(::getEmittedValues)
|
||||
|
||||
// Assert
|
||||
val expected = producer.fallback.getOrNull()
|
||||
Truth.assertThat(actual.size).isEqualTo(1)
|
||||
Truth.assertThat(actual.first()).isEqualTo(expected)
|
||||
|
||||
verifyOrder {
|
||||
userWalletsListRepository.userWallets
|
||||
userTokensResponseStore.get(params.userWalletId)
|
||||
}
|
||||
|
||||
verify(inverse = true) {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any(), accountIndex = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `produce throws exception if UserWallet isn't multi-currency wallet`() = runTest {
|
||||
// Arrange
|
||||
val mockUserWallet = mockk<UserWallet> {
|
||||
every { walletId } returns userWallet.walletId
|
||||
every { isMultiCurrency } returns false
|
||||
}
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(mockUserWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
|
||||
// Act
|
||||
val actual = runCatching { producer.produce() }.exceptionOrNull()
|
||||
|
||||
// Assert
|
||||
val expected = IllegalStateException(
|
||||
"${DefaultMultiWalletCryptoCurrenciesProducer::class.simpleName} supports only multi-currency wallet",
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isInstanceOf(expected::class.java)
|
||||
Truth.assertThat(actual).hasMessageThat().isEqualTo(expected.message)
|
||||
|
||||
verifyOrder { userWalletsListRepository.userWallets }
|
||||
|
||||
verify(inverse = true) {
|
||||
userTokensResponseStore.get(any())
|
||||
responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any(), accountIndex = any())
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(2),
|
||||
derivedKeys = emptyMap(),
|
||||
)
|
||||
|
||||
val userWallet = MockUserWalletFactory.create(scanResponse = scanResponse)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.tokens.UserTokensBackwardCompatibility
|
||||
import com.tangem.data.common.wallet.WalletServerBinder
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.isNetworkError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.getSyncOrNull
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -23,31 +21,11 @@ import timber.log.Timber
|
|||
class UserTokensSaver(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val userTokensResponseStore: UserTokensResponseStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val addressesEnricher: UserTokensResponseAddressesEnricher,
|
||||
private val walletServerBinder: WalletServerBinder,
|
||||
private val pushTokensRetryerPool: RetryerPool,
|
||||
) {
|
||||
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
|
||||
|
||||
suspend fun storeAndPush(userWalletId: UserWalletId, response: UserTokensResponse) {
|
||||
withContext(dispatchers.default) {
|
||||
val enrichedResponse = response.enrichIf(userWalletId = userWalletId, condition = true)
|
||||
|
||||
store(userWalletId = userWalletId, response = enrichedResponse, useEnricher = false)
|
||||
push(userWalletId = userWalletId, response = enrichedResponse, useEnricher = false)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun store(userWalletId: UserWalletId, response: UserTokensResponse, useEnricher: Boolean = true) =
|
||||
withContext(dispatchers.default) {
|
||||
val updatedResponse = response
|
||||
.applyCompatibility()
|
||||
.enrichIf(userWalletId = userWalletId, condition = useEnricher)
|
||||
|
||||
userTokensResponseStore.store(userWalletId = userWalletId, response = updatedResponse)
|
||||
}
|
||||
|
||||
suspend fun push(
|
||||
userWalletId: UserWalletId,
|
||||
|
|
@ -111,10 +89,6 @@ class UserTokensSaver(
|
|||
)
|
||||
}
|
||||
|
||||
private fun UserTokensResponse.applyCompatibility(): UserTokensResponse {
|
||||
return userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(userTokensResponse = this)
|
||||
}
|
||||
|
||||
private suspend fun UserTokensResponse.enrichIf(
|
||||
userWalletId: UserWalletId,
|
||||
condition: Boolean,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import com.tangem.data.common.wallet.WalletServerBinder
|
|||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -67,7 +66,6 @@ internal object DataCommonModule {
|
|||
fun provideUserTokensSaver(
|
||||
tangemTechApi: TangemTechApi,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
userTokensResponseStore: UserTokensResponseStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
addressesEnricher: UserTokensResponseAddressesEnricher,
|
||||
walletServerBinder: WalletServerBinder,
|
||||
|
|
@ -75,7 +73,6 @@ internal object DataCommonModule {
|
|||
return UserTokensSaver(
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
dispatchers = dispatchers,
|
||||
addressesEnricher = addressesEnricher,
|
||||
pushTokensRetryerPool = RetryerPool(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import com.tangem.datasource.api.common.response.ApiResponseError
|
|||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletType
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -23,14 +22,12 @@ class UserTokensSaverTest {
|
|||
|
||||
private val tangemTechApi: TangemTechApi = mockk()
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxUnitFun = true)
|
||||
private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxed = true)
|
||||
private val enricher: UserTokensResponseAddressesEnricher = mockk()
|
||||
private val walletServerBinder: WalletServerBinder = mockk()
|
||||
|
||||
private val userTokensSaver: UserTokensSaver = UserTokensSaver(
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
addressesEnricher = enricher,
|
||||
walletServerBinder = walletServerBinder,
|
||||
|
|
@ -42,46 +39,11 @@ class UserTokensSaverTest {
|
|||
clearMocks(
|
||||
tangemTechApi,
|
||||
userWalletsListRepository,
|
||||
userTokensResponseStore,
|
||||
enricher,
|
||||
walletServerBinder,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallet id and response WHEN store THEN should store enriched response`() = runTest {
|
||||
// GIVEN
|
||||
val userWalletId = UserWalletId("1234567890abcdef")
|
||||
val response = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
|
||||
val enrichedResponse = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.MANUAL,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
|
||||
coEvery { enricher(userWalletId, response) } returns enrichedResponse
|
||||
|
||||
// WHEN
|
||||
userTokensSaver.store(userWalletId, response)
|
||||
|
||||
// THEN
|
||||
coVerifyOrder {
|
||||
enricher(userWalletId, response)
|
||||
userTokensResponseStore.store(userWalletId, enrichedResponse)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
tangemTechApi.saveTokens(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallet id and response WHEN push AND api call fails THEN should log error and call onFailSend`() =
|
||||
runTest {
|
||||
|
|
@ -133,49 +95,4 @@ class UserTokensSaverTest {
|
|||
|
||||
assert(onFailSendCalled) { "onFailSend callback should be called when API call fails" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallet id and response WHEN storeAndPush THEN should store and push enriched response`() = runTest {
|
||||
// GIVEN
|
||||
val userWalletId = UserWalletId("1234567890abcdef")
|
||||
val userWallet = mockk<UserWallet.Cold> {
|
||||
every { this@mockk.walletId } returns userWalletId
|
||||
every { this@mockk.name } returns "Wallet"
|
||||
}
|
||||
|
||||
val response = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
walletName = userWallet.name,
|
||||
walletType = WalletType.COLD,
|
||||
)
|
||||
val enrichedResponse = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
walletName = userWallet.name,
|
||||
walletType = WalletType.COLD,
|
||||
)
|
||||
|
||||
val userWalletsFlow = MutableStateFlow(listOf(userWallet))
|
||||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
coEvery { enricher(userWalletId, response) } returns enrichedResponse
|
||||
coEvery {
|
||||
tangemTechApi.saveTokens(userWalletId.stringValue, enrichedResponse)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
|
||||
// WHEN
|
||||
userTokensSaver.storeAndPush(userWalletId, response)
|
||||
|
||||
// THEN
|
||||
coVerifyOrder {
|
||||
enricher(userWalletId, response)
|
||||
userWalletsListRepository.userWallets
|
||||
tangemTechApi.saveTokens(userWalletId.stringValue, enrichedResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +1,30 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
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.wallet.UserWalletId
|
||||
import com.tangem.domain.nft.models.NFTNetworks
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
|
||||
class GetNFTNetworksUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val nftRepository: NFTRepository,
|
||||
) {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(portfolioId: PortfolioId): Flow<NFTNetworks> = when (portfolioId) {
|
||||
is PortfolioId.Account -> {
|
||||
singleAccountListSupplier(portfolioId.userWalletId)
|
||||
.mapNotNull { accountList ->
|
||||
val account = accountList.accounts.find { it.accountId == portfolioId.accountId }
|
||||
operator fun invoke(accountId: AccountId): Flow<NFTNetworks> {
|
||||
return singleAccountListSupplier(accountId.userWalletId)
|
||||
.mapNotNull { accountList ->
|
||||
val account = accountList.accounts.find { it.accountId == accountId }
|
||||
|
||||
(account as? Account.CryptoPortfolio)?.cryptoCurrencies?.toList()
|
||||
}
|
||||
.mapLatest { it.toNFTNetworks(portfolioId.userWalletId) }
|
||||
}
|
||||
is PortfolioId.Wallet -> {
|
||||
currenciesRepository
|
||||
.getWalletCurrenciesUpdates(portfolioId.userWalletId)
|
||||
.map { cryptoCurrencies -> cryptoCurrencies.toNFTNetworks(portfolioId.userWalletId) }
|
||||
}
|
||||
(account as? Account.CryptoPortfolio)?.cryptoCurrencies?.toList()
|
||||
}
|
||||
.mapLatest { it.toNFTNetworks(accountId.userWalletId) }
|
||||
}
|
||||
|
||||
private suspend fun List<CryptoCurrency>.toNFTNetworks(userWalletId: UserWalletId): NFTNetworks {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import com.tangem.core.decompose.context.childByContext
|
|||
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.features.account.PortfolioFetcher
|
||||
import com.tangem.features.account.PortfolioSelectorComponent
|
||||
import com.tangem.features.account.PortfolioSelectorController
|
||||
|
|
@ -160,13 +159,13 @@ internal class DefaultNFTComponent @AssistedInject constructor(
|
|||
if (portfolioData.isSingleChoice) {
|
||||
val mainAccountId = portfolioData.balances.values.first()
|
||||
.accountsBalance.mainAccount.account.accountId
|
||||
innerRouter.push(NFTRoute.Receive(portfolioId = PortfolioId(mainAccountId)))
|
||||
innerRouter.push(NFTRoute.Receive(accountId = mainAccountId))
|
||||
} else {
|
||||
bottomSheetNavigation.activate(Unit)
|
||||
val selectedAccountId = portfolioSelectorController.selectedAccount
|
||||
.filterNotNull().first()
|
||||
bottomSheetNavigation.dismiss()
|
||||
innerRouter.push(NFTRoute.Receive(portfolioId = PortfolioId(selectedAccountId)))
|
||||
innerRouter.push(NFTRoute.Receive(accountId = selectedAccountId))
|
||||
}
|
||||
}.saveIn(onReceiveClickJob)
|
||||
|
||||
|
|
@ -176,7 +175,7 @@ internal class DefaultNFTComponent @AssistedInject constructor(
|
|||
): ComposableContentComponent = NFTReceiveComponent(
|
||||
context = factoryContext,
|
||||
params = NFTReceiveComponent.Params(
|
||||
portfolioId = route.portfolioId,
|
||||
accountId = route.accountId,
|
||||
onBackClick = ::onChildBack,
|
||||
),
|
||||
tokenReceiveComponentFactory = tokenReceiveComponentFactory,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.features.nft.common
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.nft.models.NFTCollection
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal sealed class NFTRoute : Route {
|
||||
|
|
@ -15,9 +15,7 @@ internal sealed class NFTRoute : Route {
|
|||
) : NFTRoute()
|
||||
|
||||
@Serializable
|
||||
data class Receive(
|
||||
val portfolioId: PortfolioId,
|
||||
) : NFTRoute()
|
||||
data class Receive(val accountId: AccountId) : NFTRoute()
|
||||
|
||||
@Serializable
|
||||
data class Details(
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import com.tangem.core.decompose.context.childByContext
|
|||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.features.nft.receive.model.NFTReceiveModel
|
||||
import com.tangem.features.nft.receive.ui.NFTReceive
|
||||
import com.tangem.features.tokenreceive.TokenReceiveComponent
|
||||
|
|
@ -57,7 +57,7 @@ internal class NFTReceiveComponent @AssistedInject constructor(
|
|||
)
|
||||
|
||||
data class Params(
|
||||
val portfolioId: PortfolioId,
|
||||
val accountId: AccountId,
|
||||
val onBackClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ import com.tangem.core.ui.message.DialogMessage
|
|||
import com.tangem.domain.account.producer.SingleAccountProducer
|
||||
import com.tangem.domain.account.supplier.SingleAccountSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
|
@ -88,14 +87,12 @@ internal class NFTReceiveModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun loadPortfolioName() = modelScope.launch(dispatchers.default) {
|
||||
val appBarSubtitle = when (val portfolioId = params.portfolioId) {
|
||||
is PortfolioId.Wallet -> loadWalletName(portfolioId.userWalletId)
|
||||
is PortfolioId.Account -> if (isAccountsModeEnabledUseCase.invokeSync()) {
|
||||
loadAccountName(portfolioId.accountId)
|
||||
} else {
|
||||
loadWalletName(portfolioId.userWalletId)
|
||||
}
|
||||
val appBarSubtitle = if (isAccountsModeEnabledUseCase.invokeSync()) {
|
||||
loadAccountName(params.accountId)
|
||||
} else {
|
||||
loadWalletName(params.accountId.userWalletId)
|
||||
}
|
||||
|
||||
_state.update { it.copy(appBarSubtitle = appBarSubtitle) }
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +118,7 @@ internal class NFTReceiveModel @Inject constructor(
|
|||
|
||||
private fun subscribeToNFTAvailableNetworks() {
|
||||
combine(
|
||||
flow = getNFTNetworksUseCase(params.portfolioId),
|
||||
flow = getNFTNetworksUseCase(params.accountId),
|
||||
flow2 = searchManager.query.distinctUntilChanged(),
|
||||
) { networks, query ->
|
||||
filterNFTAvailableNetworksUseCase(networks, query)
|
||||
|
|
@ -173,7 +170,7 @@ internal class NFTReceiveModel @Inject constructor(
|
|||
analyticsEventHandler.send(NFTAnalyticsEvent.Receive.BlockchainChosen(network.name))
|
||||
|
||||
val networkStatus = getNFTNetworkStatusUseCase.invoke(
|
||||
userWalletId = params.portfolioId.userWalletId,
|
||||
userWalletId = params.accountId.userWalletId,
|
||||
network = network,
|
||||
) ?: return@launch
|
||||
|
||||
|
|
@ -198,7 +195,7 @@ internal class NFTReceiveModel @Inject constructor(
|
|||
private suspend fun configureReceiveAddresses(addresses: NetworkAddress, network: Network): TokenReceiveConfig {
|
||||
val cryptoCurrency = getNFTCurrencyUseCase.invoke(network)
|
||||
return receiveAddressesFactory.createForNft(
|
||||
userWalletId = params.portfolioId.userWalletId,
|
||||
userWalletId = params.accountId.userWalletId,
|
||||
addresses = addresses,
|
||||
network = network,
|
||||
nft = cryptoCurrency,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue