Updated on 2026-08-14
This commit is contained in:
parent
6416c6f4d1
commit
e67a56ba1d
8 changed files with 140 additions and 30 deletions
|
|
@ -28,7 +28,7 @@ internal class MarketsListBatchFlowManager @AssistedInject constructor(
|
|||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
@Assisted private val batchFlowType: GetMarketsTokenListFlowUseCase.BatchFlowType,
|
||||
@Assisted private val order: TokenMarketListConfig.Order,
|
||||
@Assisted private val currentOrder: Provider<TokenMarketListConfig.Order>,
|
||||
@Assisted private val currentSearchText: Provider<String?>,
|
||||
@Assisted private val modelScope: CoroutineScope,
|
||||
) {
|
||||
|
|
@ -192,7 +192,7 @@ internal class MarketsListBatchFlowManager @AssistedInject constructor(
|
|||
searchText ?: currentSearchText()
|
||||
},
|
||||
priceChangeInterval = TokenMarketListConfig.Interval.H24,
|
||||
order = order,
|
||||
order = currentOrder(),
|
||||
shouldNetworks = true,
|
||||
),
|
||||
),
|
||||
|
|
@ -262,7 +262,7 @@ internal class MarketsListBatchFlowManager @AssistedInject constructor(
|
|||
interface Factory {
|
||||
fun create(
|
||||
batchFlowType: GetMarketsTokenListFlowUseCase.BatchFlowType,
|
||||
order: TokenMarketListConfig.Order,
|
||||
currentOrder: Provider<TokenMarketListConfig.Order>,
|
||||
currentSearchText: Provider<String?>,
|
||||
modelScope: CoroutineScope,
|
||||
): MarketsListBatchFlowManager
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.features.commonfeatures.impl.choosetoken.market.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.markets.TokenMarketListConfig
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* Category of the "Market Pulse" block on the Choose asset screen.
|
||||
*
|
||||
* Each category maps to a [TokenMarketListConfig.Order] used to fetch the markets list.
|
||||
*/
|
||||
internal enum class SwapMarketCategory(
|
||||
val title: TextReference,
|
||||
val order: TokenMarketListConfig.Order,
|
||||
) {
|
||||
Trending(
|
||||
title = resourceReference(R.string.markets_sort_by_trending_title),
|
||||
order = TokenMarketListConfig.Order.Trending,
|
||||
),
|
||||
ExperiencedBuyers(
|
||||
title = resourceReference(R.string.markets_sort_by_experienced_buyers_title),
|
||||
order = TokenMarketListConfig.Order.Buyers,
|
||||
),
|
||||
TopGainers(
|
||||
title = resourceReference(R.string.markets_sort_by_top_gainers_title),
|
||||
order = TokenMarketListConfig.Order.TopGainers,
|
||||
),
|
||||
TopLosers(
|
||||
title = resourceReference(R.string.markets_sort_by_top_losers_title),
|
||||
order = TokenMarketListConfig.Order.TopLosers,
|
||||
),
|
||||
}
|
||||
|
||||
/**
|
||||
* UI model for the selectable category tabs of the "Market Pulse" block.
|
||||
*
|
||||
* @property items all available categories in display order.
|
||||
* @property selected currently selected category.
|
||||
* @property onCategoryClick invoked when a category tab is tapped.
|
||||
*/
|
||||
@Immutable
|
||||
internal data class SwapMarketCategoriesUM(
|
||||
val items: ImmutableList<SwapMarketCategory>,
|
||||
val selected: SwapMarketCategory,
|
||||
val onCategoryClick: (SwapMarketCategory) -> Unit,
|
||||
)
|
||||
|
|
@ -13,6 +13,8 @@ internal sealed class SwapMarketState {
|
|||
abstract val marketsTitle: TextReference
|
||||
abstract val shouldAssetsCount: Boolean
|
||||
|
||||
open val categories: SwapMarketCategoriesUM? = null
|
||||
|
||||
data class Content(
|
||||
val items: ImmutableList<MarketsListItemUM>,
|
||||
val total: Int,
|
||||
|
|
@ -21,17 +23,20 @@ internal sealed class SwapMarketState {
|
|||
val visibleIdsChanged: (List<CryptoCurrency.RawID>) -> Unit,
|
||||
override val marketsTitle: TextReference,
|
||||
override val shouldAssetsCount: Boolean,
|
||||
override val categories: SwapMarketCategoriesUM? = null,
|
||||
) : SwapMarketState()
|
||||
|
||||
data class Loading(
|
||||
override val marketsTitle: TextReference,
|
||||
override val shouldAssetsCount: Boolean,
|
||||
override val categories: SwapMarketCategoriesUM? = null,
|
||||
) : SwapMarketState()
|
||||
|
||||
data class LoadingError(
|
||||
val onRetryClicked: () -> Unit,
|
||||
override val marketsTitle: TextReference,
|
||||
override val shouldAssetsCount: Boolean,
|
||||
override val categories: SwapMarketCategoriesUM? = null,
|
||||
) : SwapMarketState()
|
||||
|
||||
data object SearchNothingFound : SwapMarketState() {
|
||||
|
|
@ -40,11 +45,6 @@ internal sealed class SwapMarketState {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val DefaultLoading
|
||||
get() = Loading(
|
||||
marketsTitle = TextReference.Res(R.string.feed_trending_now),
|
||||
shouldAssetsCount = false,
|
||||
)
|
||||
val SearchLoading
|
||||
get() = Loading(
|
||||
marketsTitle = TextReference.Res(R.string.markets_common_title),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridgeInter
|
|||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridgeInternal.SearchQuery.Companion.isSearchingState
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.AddToPortfolioRoute
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.market.MarketsListBatchFlowManager
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMarketCategoriesUM
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMarketCategory
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMarketState
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.Provider
|
||||
|
|
@ -49,6 +51,8 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
private val visibleMarketItemIds = MutableStateFlow<List<CryptoCurrency.RawID>>(emptyList())
|
||||
private val visibleDefaultMarketItemIds = MutableStateFlow<List<CryptoCurrency.RawID>>(emptyList())
|
||||
|
||||
private val selectedCategoryFlow = MutableStateFlow(SwapMarketCategory.Trending)
|
||||
|
||||
val addToPortfolioSlot: SlotNavigation<AddToPortfolioRoute> = SlotNavigation()
|
||||
val addToPortfolioManager: AddToPortfolioManager = addToPortfolioManagerFactory.create(
|
||||
scope = modelScope,
|
||||
|
|
@ -91,7 +95,7 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
private val defaultMarketsListManager by lazy {
|
||||
marketsListBatchFlowManagerFactory.create(
|
||||
batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Main,
|
||||
order = TokenMarketListConfig.Order.Trending,
|
||||
currentOrder = Provider { selectedCategoryFlow.value.order },
|
||||
currentSearchText = Provider { null },
|
||||
modelScope = modelScope,
|
||||
)
|
||||
|
|
@ -100,7 +104,7 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
private val searchMarketsListManager by lazy {
|
||||
marketsListBatchFlowManagerFactory.create(
|
||||
batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Search,
|
||||
order = TokenMarketListConfig.Order.ByRating,
|
||||
currentOrder = Provider { TokenMarketListConfig.Order.ByRating },
|
||||
currentSearchText = Provider { searchQueryState.value.value },
|
||||
modelScope = modelScope,
|
||||
)
|
||||
|
|
@ -148,19 +152,26 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun createDefaultMarketsFlow(): Flow<SwapMarketState> {
|
||||
val marketsTitle = TextReference.Res(R.string.feed_trending_now)
|
||||
val marketsTitle = TextReference.Res(R.string.markets_pulse_common_title)
|
||||
return combine(
|
||||
defaultMarketsListManager.uiItems,
|
||||
defaultMarketsListManager.isInInitialLoadingErrorState,
|
||||
defaultMarketsListManager.totalCount,
|
||||
) { uiItems, isError, total ->
|
||||
flow = defaultMarketsListManager.uiItems,
|
||||
flow2 = defaultMarketsListManager.isInInitialLoadingErrorState,
|
||||
flow3 = defaultMarketsListManager.totalCount,
|
||||
flow4 = selectedCategoryFlow,
|
||||
) { uiItems, isError, total, selectedCategory ->
|
||||
val categories = buildCategoriesUM(selectedCategory)
|
||||
when {
|
||||
isError -> SwapMarketState.LoadingError(
|
||||
onRetryClicked = { defaultMarketsListManager.reload() },
|
||||
marketsTitle = marketsTitle,
|
||||
shouldAssetsCount = false,
|
||||
categories = categories,
|
||||
)
|
||||
uiItems.isEmpty() -> SwapMarketState.Loading(
|
||||
marketsTitle = marketsTitle,
|
||||
shouldAssetsCount = false,
|
||||
categories = categories,
|
||||
)
|
||||
uiItems.isEmpty() -> SwapMarketState.DefaultLoading
|
||||
else -> SwapMarketState.Content(
|
||||
items = uiItems,
|
||||
loadMore = { defaultMarketsListManager.loadMore() },
|
||||
|
|
@ -169,11 +180,24 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
total = total ?: uiItems.size,
|
||||
marketsTitle = marketsTitle,
|
||||
shouldAssetsCount = false,
|
||||
categories = categories,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildCategoriesUM(selected: SwapMarketCategory): SwapMarketCategoriesUM = SwapMarketCategoriesUM(
|
||||
items = SwapMarketCategory.entries.toImmutableList(),
|
||||
selected = selected,
|
||||
onCategoryClick = ::onCategorySelected,
|
||||
)
|
||||
|
||||
private fun onCategorySelected(category: SwapMarketCategory) {
|
||||
if (selectedCategoryFlow.value == category) return
|
||||
selectedCategoryFlow.value = category
|
||||
defaultMarketsListManager.reload()
|
||||
}
|
||||
|
||||
private fun createSearchMarketsFlow(): Flow<SwapMarketState> {
|
||||
val marketsTitle = TextReference.Res(R.string.markets_common_title)
|
||||
return combine(
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ private fun Content(state: ChooseTokenFullUM, modifier: Modifier = Modifier) {
|
|||
)
|
||||
|
||||
if (state.marketsBlock != null) {
|
||||
item("markets_title_spacer") { SpacerH(height = 20.dp) }
|
||||
item("markets_title_spacer") { SpacerH(height = 40.dp) }
|
||||
swapMarketsListItems(state.marketsBlock)
|
||||
}
|
||||
}
|
||||
|
|
@ -206,9 +206,9 @@ private fun VisibleItemsTracker(lazyListState: LazyListState, marketState: SwapM
|
|||
private fun LazyListScope.assetsTitle() {
|
||||
item(key = "assets_title") {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.swap_your_assets_title),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
text = stringResourceSafe(R.string.markets_portfolio_block_subtitle),
|
||||
style = TangemTheme.typography2.headingSemibold20,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
|
|
@ -224,7 +224,7 @@ private fun LazyListScope.walletListItem(walletList: WalletListUM) {
|
|||
if (walletList.items.isEmpty()) return
|
||||
item("wallet_list") {
|
||||
LazyRow(
|
||||
modifier = Modifier.padding(top = 12.dp, bottom = 4.dp),
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
|
|
@ -249,7 +249,7 @@ private fun WalletTabItem(state: WalletTabUM, modifier: Modifier = Modifier) {
|
|||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.clip(RoundedCornerShape(percent = 50))
|
||||
.background(backgroundColor)
|
||||
.clickable(onClick = state.onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
|
|
@ -259,7 +259,7 @@ private fun WalletTabItem(state: WalletTabUM, modifier: Modifier = Modifier) {
|
|||
Text(
|
||||
text = state.text.resolveReference(),
|
||||
color = buttonTextColor,
|
||||
style = TangemTheme.typography.button,
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
)
|
||||
|
||||
val count = state.count
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.tangem.features.commonfeatures.impl.choosetoken.ui
|
|||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -14,10 +16,12 @@ import com.tangem.common.ui.markets.MarketsListItem
|
|||
import com.tangem.common.ui.markets.MarketsListItemPlaceholder
|
||||
import com.tangem.core.ui.components.UnableToLoadData
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.ds.tabs.TangemTab
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.commonfeatures.impl.R
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMarketCategoriesUM
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMarketState
|
||||
|
||||
internal fun LazyListScope.swapMarketsListItems(state: SwapMarketState) {
|
||||
|
|
@ -26,17 +30,22 @@ internal fun LazyListScope.swapMarketsListItems(state: SwapMarketState) {
|
|||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append(state.marketsTitle.resolveReference())
|
||||
if (totalCount != null) {
|
||||
if (totalCount != null && state.shouldAssetsCount) {
|
||||
withStyle(SpanStyle(color = TangemTheme.colors.text.tertiary)) {
|
||||
append(" $totalCount")
|
||||
}
|
||||
}
|
||||
},
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography2.headingSemibold20,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
state.categories?.let { categories ->
|
||||
item(key = "market_categories") {
|
||||
MarketCategoriesRow(categories = categories)
|
||||
}
|
||||
}
|
||||
when (state) {
|
||||
is SwapMarketState.Loading -> {
|
||||
items(count = 100, key = { "market_placeholder_$it" }) {
|
||||
|
|
@ -69,7 +78,7 @@ internal fun LazyListScope.swapMarketsListItems(state: SwapMarketState) {
|
|||
modifier = Modifier.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = state.items.lastIndex,
|
||||
backgroundColor = TangemTheme.colors.background.action,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -77,6 +86,31 @@ internal fun LazyListScope.swapMarketsListItems(state: SwapMarketState) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MarketCategoriesRow(categories: SwapMarketCategoriesUM, modifier: Modifier = Modifier) {
|
||||
LazyRow(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
items(
|
||||
items = categories.items,
|
||||
key = { category -> category.name },
|
||||
) { category ->
|
||||
TangemTab(
|
||||
text = category.title,
|
||||
isChecked = category == categories.selected,
|
||||
onCheckedChange = { categories.onCategoryClick(category) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingErrorItem(onTryAgain: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ internal class DefaultManageFundsComponent @AssistedInject constructor(
|
|||
iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_arrow_back_28),
|
||||
onClick = onBackClick,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
variant = TangemButton.Variant.Secondary,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -211,7 +211,7 @@ internal class DefaultManageFundsComponent @AssistedInject constructor(
|
|||
iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_close_24),
|
||||
onClick = onCloseClick,
|
||||
size = TangemButton.Size.X11,
|
||||
variant = TangemButton.Variant.Material,
|
||||
variant = TangemButton.Variant.Secondary,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue