Updated on 2026-08-14
This commit is contained in:
parent
febb2bda4c
commit
82ae40d9f7
3 changed files with 66 additions and 6 deletions
|
|
@ -28,6 +28,14 @@ internal enum class SwapMarketCategory(
|
||||||
title = resourceReference(R.string.markets_sort_by_top_losers_title),
|
title = resourceReference(R.string.markets_sort_by_top_losers_title),
|
||||||
order = TokenMarketListConfig.Order.TopLosers,
|
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,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
private const val MARKET_PULSE_ITEM_LIMIT = 5
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
internal class MarketBlockDelegate @AssistedInject constructor(
|
internal class MarketBlockDelegate @AssistedInject constructor(
|
||||||
private val marketsListBatchFlowManagerFactory: MarketsListBatchFlowManager.Factory,
|
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
|
* 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].
|
* 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
|
baseMarketsStateFlow
|
||||||
} else {
|
} else {
|
||||||
selectedWalletFlow
|
selectedWalletFlow
|
||||||
|
|
@ -92,6 +94,10 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val marketsStateFlow: Flow<SwapMarketState?> = walletAwareMarketsStateFlow
|
||||||
|
.map { it.limitMarketPulseItems() }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
|
||||||
private val defaultMarketsListManager by lazy {
|
private val defaultMarketsListManager by lazy {
|
||||||
marketsListBatchFlowManagerFactory.create(
|
marketsListBatchFlowManagerFactory.create(
|
||||||
batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Main,
|
batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Main,
|
||||||
|
|
@ -156,9 +162,8 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
||||||
return combine(
|
return combine(
|
||||||
flow = defaultMarketsListManager.uiItems,
|
flow = defaultMarketsListManager.uiItems,
|
||||||
flow2 = defaultMarketsListManager.isInInitialLoadingErrorState,
|
flow2 = defaultMarketsListManager.isInInitialLoadingErrorState,
|
||||||
flow3 = defaultMarketsListManager.totalCount,
|
flow3 = selectedCategoryFlow,
|
||||||
flow4 = selectedCategoryFlow,
|
) { uiItems, isError, selectedCategory ->
|
||||||
) { uiItems, isError, total, selectedCategory ->
|
|
||||||
val categories = buildCategoriesUM(selectedCategory)
|
val categories = buildCategoriesUM(selectedCategory)
|
||||||
when {
|
when {
|
||||||
isError -> SwapMarketState.LoadingError(
|
isError -> SwapMarketState.LoadingError(
|
||||||
|
|
@ -174,10 +179,10 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
else -> SwapMarketState.Content(
|
else -> SwapMarketState.Content(
|
||||||
items = uiItems,
|
items = uiItems,
|
||||||
loadMore = { defaultMarketsListManager.loadMore() },
|
loadMore = {},
|
||||||
onItemClick = { item -> addToPortfolioItem(item) },
|
onItemClick = { item -> addToPortfolioItem(item) },
|
||||||
visibleIdsChanged = { visibleDefaultMarketItemIds.value = it },
|
visibleIdsChanged = { visibleDefaultMarketItemIds.value = it },
|
||||||
total = total ?: uiItems.size,
|
total = uiItems.size,
|
||||||
marketsTitle = marketsTitle,
|
marketsTitle = marketsTitle,
|
||||||
shouldAssetsCount = false,
|
shouldAssetsCount = false,
|
||||||
categories = categories,
|
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) {
|
private fun addToPortfolioItem(item: MarketsListItemUM) {
|
||||||
val tokenMarket = defaultMarketsListManager.getTokenMarketById(item.id)
|
val tokenMarket = defaultMarketsListManager.getTokenMarketById(item.id)
|
||||||
?: searchMarketsListManager.getTokenMarketById(item.id) ?: return
|
?: searchMarketsListManager.getTokenMarketById(item.id) ?: return
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -116,6 +117,23 @@ internal class MarketBlockDelegateTest {
|
||||||
assertThat((result as SwapMarketState.Content).items).containsExactly(item1, item2).inOrder()
|
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
|
@Test
|
||||||
fun `GIVEN single-currency wallet WHEN trending emitted THEN market block is hidden`() = runTest {
|
fun `GIVEN single-currency wallet WHEN trending emitted THEN market block is hidden`() = runTest {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -188,6 +206,28 @@ internal class MarketBlockDelegateTest {
|
||||||
assertThat(result).isNull()
|
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
|
// region Helpers
|
||||||
|
|
||||||
private fun TestScope.lastMarketState(delegate: MarketBlockDelegate): SwapMarketState? {
|
private fun TestScope.lastMarketState(delegate: MarketBlockDelegate): SwapMarketState? {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue