From e67a56ba1dbea77ffb5de077960f3655953aa4e3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 25 Jun 2026 17:49:13 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/core/ui/ds/tabs/TangemTab.kt | 5 +- .../market/MarketsListBatchFlowManager.kt | 6 +-- .../market/state/SwapMarketCategory.kt | 49 +++++++++++++++++++ .../market/state/SwapMarketState.kt | 10 ++-- .../choosetoken/model/MarketBlockDelegate.kt | 40 ++++++++++++--- .../impl/choosetoken/ui/ChooseTokenScreen.kt | 14 +++--- .../ui/SwapMarketsListLazyColumn.kt | 42 ++++++++++++++-- .../DefaultManageFundsComponent.kt | 4 +- 8 files changed, 140 insertions(+), 30 deletions(-) create mode 100644 features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketCategory.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/tabs/TangemTab.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/tabs/TangemTab.kt index 15dccb3a1d..fb3522efef 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds/tabs/TangemTab.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/tabs/TangemTab.kt @@ -67,7 +67,10 @@ fun TangemTab( interactionSource = remember { MutableInteractionSource() }, indication = ripple(), ) - .padding(all = TangemTheme.dimens2.x3), + .padding( + vertical = TangemTheme.dimens2.x2, + horizontal = TangemTheme.dimens2.x3, + ), ) { Text( text = text.resolveReference(), diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/MarketsListBatchFlowManager.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/MarketsListBatchFlowManager.kt index bff9bfb159..d7099f3159 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/MarketsListBatchFlowManager.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/MarketsListBatchFlowManager.kt @@ -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, @Assisted private val currentSearchText: Provider, @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, currentSearchText: Provider, modelScope: CoroutineScope, ): MarketsListBatchFlowManager diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketCategory.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketCategory.kt new file mode 100644 index 0000000000..5e480142ac --- /dev/null +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketCategory.kt @@ -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, + val selected: SwapMarketCategory, + val onCategoryClick: (SwapMarketCategory) -> Unit, +) \ No newline at end of file diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketState.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketState.kt index dd11a69c73..98505e6f2c 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketState.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/market/state/SwapMarketState.kt @@ -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, val total: Int, @@ -21,17 +23,20 @@ internal sealed class SwapMarketState { val visibleIdsChanged: (List) -> 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), diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/MarketBlockDelegate.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/MarketBlockDelegate.kt index 5703c8d4a0..15fffa4ab4 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/MarketBlockDelegate.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/MarketBlockDelegate.kt @@ -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>(emptyList()) private val visibleDefaultMarketItemIds = MutableStateFlow>(emptyList()) + private val selectedCategoryFlow = MutableStateFlow(SwapMarketCategory.Trending) + val addToPortfolioSlot: SlotNavigation = 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 { - 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 { val marketsTitle = TextReference.Res(R.string.markets_common_title) return combine( diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenScreen.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenScreen.kt index 179376d9d2..f4b67a6364 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenScreen.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenScreen.kt @@ -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 diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/SwapMarketsListLazyColumn.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/SwapMarketsListLazyColumn.kt index adf96a1463..2368913a12 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/SwapMarketsListLazyColumn.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/SwapMarketsListLazyColumn.kt @@ -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( diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/managefunds/DefaultManageFundsComponent.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/managefunds/DefaultManageFundsComponent.kt index acd951b2b2..355331a109 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/managefunds/DefaultManageFundsComponent.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/managefunds/DefaultManageFundsComponent.kt @@ -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, ) }, )