Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-13 18:12:18 +04:00
parent febb2bda4c
commit 82ae40d9f7
3 changed files with 66 additions and 6 deletions

View file

@ -28,6 +28,14 @@ internal enum class SwapMarketCategory(
title = resourceReference(R.string.markets_sort_by_top_losers_title),
order = TokenMarketListConfig.Order.TopLosers,
),
ExperiencedBuyers(
title = resourceReference(R.string.markets_sort_by_experienced_buyers_title),
order = TokenMarketListConfig.Order.Buyers,
),
Trending(
title = resourceReference(R.string.markets_sort_by_trending_title),
order = TokenMarketListConfig.Order.Trending,
),
}
/**

View file

@ -34,6 +34,8 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
private const val MARKET_PULSE_ITEM_LIMIT = 5
@Suppress("LongParameterList")
internal class MarketBlockDelegate @AssistedInject constructor(
private val marketsListBatchFlowManagerFactory: MarketsListBatchFlowManager.Factory,
@ -84,7 +86,7 @@ internal class MarketBlockDelegate @AssistedInject constructor(
* When single-currency wallets aren't selectable here (e.g. swap), the wallet is always
* multi-currency, so we skip the per-wallet logic entirely and return [baseMarketsStateFlow].
*/
val marketsStateFlow: Flow<SwapMarketState?> = if (!shouldShowSingleCurrencyWallets) {
private val walletAwareMarketsStateFlow: Flow<SwapMarketState?> = if (!shouldShowSingleCurrencyWallets) {
baseMarketsStateFlow
} else {
selectedWalletFlow
@ -92,6 +94,10 @@ internal class MarketBlockDelegate @AssistedInject constructor(
.distinctUntilChanged()
}
val marketsStateFlow: Flow<SwapMarketState?> = walletAwareMarketsStateFlow
.map { it.limitMarketPulseItems() }
.distinctUntilChanged()
private val defaultMarketsListManager by lazy {
marketsListBatchFlowManagerFactory.create(
batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Main,
@ -156,9 +162,8 @@ internal class MarketBlockDelegate @AssistedInject constructor(
return combine(
flow = defaultMarketsListManager.uiItems,
flow2 = defaultMarketsListManager.isInInitialLoadingErrorState,
flow3 = defaultMarketsListManager.totalCount,
flow4 = selectedCategoryFlow,
) { uiItems, isError, total, selectedCategory ->
flow3 = selectedCategoryFlow,
) { uiItems, isError, selectedCategory ->
val categories = buildCategoriesUM(selectedCategory)
when {
isError -> SwapMarketState.LoadingError(
@ -174,10 +179,10 @@ internal class MarketBlockDelegate @AssistedInject constructor(
)
else -> SwapMarketState.Content(
items = uiItems,
loadMore = { defaultMarketsListManager.loadMore() },
loadMore = {},
onItemClick = { item -> addToPortfolioItem(item) },
visibleIdsChanged = { visibleDefaultMarketItemIds.value = it },
total = total ?: uiItems.size,
total = uiItems.size,
marketsTitle = marketsTitle,
shouldAssetsCount = false,
categories = categories,
@ -265,6 +270,13 @@ internal class MarketBlockDelegate @AssistedInject constructor(
}
}
private fun SwapMarketState?.limitMarketPulseItems(): SwapMarketState? {
if (this !is SwapMarketState.Content || shouldAssetsCount) return this
if (items.size <= MARKET_PULSE_ITEM_LIMIT) return this
val limitedItems = items.take(MARKET_PULSE_ITEM_LIMIT).toImmutableList()
return copy(items = limitedItems, total = limitedItems.size)
}
private fun addToPortfolioItem(item: MarketsListItemUM) {
val tokenMarket = defaultMarketsListManager.getTokenMarketById(item.id)
?: searchMarketsListManager.getTokenMarketById(item.id) ?: return

View file

@ -27,6 +27,7 @@ import io.mockk.every
import io.mockk.mockk
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@ -116,6 +117,23 @@ internal class MarketBlockDelegateTest {
assertThat((result as SwapMarketState.Content).items).containsExactly(item1, item2).inOrder()
}
@Test
fun `GIVEN more than 5 market items WHEN default flow emitted THEN only first 5 shown`() = runTest {
// Arrange
val items = (1..7).map { marketItem("token-$it") }
defaultUiItems.value = items.toPersistentList()
val delegate = createDelegate(wallet = MockUserWalletFactory.create())
// Act
val result = lastMarketState(delegate)
// Assert
assertThat(result).isInstanceOf(SwapMarketState.Content::class.java)
val content = result as SwapMarketState.Content
assertThat(content.items).containsExactlyElementsIn(items.take(5)).inOrder()
assertThat(content.total).isEqualTo(5)
}
@Test
fun `GIVEN single-currency wallet WHEN trending emitted THEN market block is hidden`() = runTest {
// Arrange
@ -188,6 +206,28 @@ internal class MarketBlockDelegateTest {
assertThat(result).isNull()
}
@Test
fun `GIVEN NODL wallet WHEN network token is beyond first 5 THEN block still shows it`() = runTest {
// Arrange — 5 tokens on another network first, the wallet-network token only at position 6.
val nodlWallet = MockUserWalletFactory.createSingleWalletWithToken()
val otherNetworkItems = (1..5).map { marketItem("token-eth-$it") }
val walletNetworkItem = marketItem("token-stellar")
(1..5).forEach { tokenMarketsByRawId["token-eth-$it"] = tokenMarket(ETHEREUM_NETWORK_ID) }
tokenMarketsByRawId["token-stellar"] = tokenMarket(STELLAR_NETWORK_ID)
defaultUiItems.value = (otherNetworkItems + walletNetworkItem).toPersistentList()
every {
singleAccountStatusListSupplier(nodlWallet.walletId)
} returns flowOf(accountStatusList(STELLAR_NETWORK_ID))
// Act
val result = lastMarketState(createDelegate(wallet = nodlWallet))
// Assert — network filtering runs on the full list before the 5-item cap, so it isn't dropped.
assertThat(result).isInstanceOf(SwapMarketState.Content::class.java)
assertThat((result as SwapMarketState.Content).items).containsExactly(walletNetworkItem)
}
// region Helpers
private fun TestScope.lastMarketState(delegate: MarketBlockDelegate): SwapMarketState? {