Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-15 11:30:30 +02:00
parent ae4029c4d5
commit 7db3810064
16 changed files with 159 additions and 40 deletions

View file

@ -5,10 +5,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.router.stack.*
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.Value
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
@ -87,8 +84,9 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
} }
override fun onMarketOpenClick(sortBy: SortByTypeUM?) { override fun onMarketOpenClick(sortBy: SortByTypeUM?) {
innerRouter.push( // Markets list and search can reach each other
route = FeedEntryChildFactory.Child.TokenList( stackNavigation.bringToFront(
FeedEntryChildFactory.Child.TokenList(
params = DefaultMarketsTokenListComponent.Params( params = DefaultMarketsTokenListComponent.Params(
preselectedSortType = sortBy ?: SortByTypeUM.Rating, preselectedSortType = sortBy ?: SortByTypeUM.Rating,
preselectedInterval = MarketsListUM.TrendInterval.H24, preselectedInterval = MarketsListUM.TrendInterval.H24,
@ -142,7 +140,7 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
} }
override fun openSearch(source: String) { override fun openSearch(source: String) {
innerRouter.push(FeedEntryChildFactory.Child.Search(source)) stackNavigation.bringToFront(FeedEntryChildFactory.Child.Search(source))
} }
} }

View file

@ -7,17 +7,18 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.navigation.Route import com.tangem.core.decompose.navigation.Route
import com.tangem.core.ui.DesignFeatureToggles import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent
import com.tangem.features.feed.components.earn.DefaultEarnComponent import com.tangem.features.feed.components.earn.DefaultEarnComponent
import com.tangem.features.feed.components.feed.DefaultFeedComponent import com.tangem.features.feed.components.feed.DefaultFeedComponent
import com.tangem.features.feed.components.feed.DefaultFeedComponent.FeedParams import com.tangem.features.feed.components.feed.DefaultFeedComponent.FeedParams
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockComponent import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockComponent
import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListComponent import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListComponent
import com.tangem.features.feed.components.news.details.DefaultNewsDetailsComponent import com.tangem.features.feed.components.news.details.DefaultNewsDetailsComponent
import com.tangem.features.feed.components.news.list.DefaultNewsListComponent import com.tangem.features.feed.components.news.list.DefaultNewsListComponent
import com.tangem.features.feed.components.search.DefaultSearchComponent import com.tangem.features.feed.components.search.DefaultSearchComponent
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
import com.tangem.features.promobanners.api.PromoBannersBlockComponent import com.tangem.features.promobanners.api.PromoBannersBlockComponent
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import javax.inject.Inject import javax.inject.Inject
@ -142,6 +143,7 @@ internal class FeedEntryChildFactory @Inject constructor(
) )
}, },
sourceParams = child.source, sourceParams = child.source,
onSeeAllMarketsClick = { feedEntryClickIntents.onMarketOpenClick(SortByTypeUM.Rating) },
), ),
) )
} }

View file

@ -84,11 +84,14 @@ internal class DefaultSearchComponent(
onTextHintClick = model::onTextHintClick, onTextHintClick = model::onTextHintClick,
onResultMarketTokenClick = model::onResultMarketTokenClick, onResultMarketTokenClick = model::onResultMarketTokenClick,
onHistoryTokenClick = model::onHistoryTokenClick, onHistoryTokenClick = model::onHistoryTokenClick,
onTopMarketSeeAllClick = model::onTopMarketSeeAllClick,
onTopMarketItemClick = model::onTopMarketItemClick,
) )
} }
SearchContent( SearchContent(
modifier = modifier, modifier = modifier,
content = state.content, content = state.content,
topMarkets = state.topMarkets,
searchCallbacks = searchCallbacks, searchCallbacks = searchCallbacks,
contentPadding = contentPadding, contentPadding = contentPadding,
) )
@ -114,5 +117,6 @@ internal class DefaultSearchComponent(
val onBackClick: () -> Unit, val onBackClick: () -> Unit,
val onMarketTokenClick: ((TokenMarketParams, AppCurrency) -> Unit), val onMarketTokenClick: ((TokenMarketParams, AppCurrency) -> Unit),
val sourceParams: String, val sourceParams: String,
val onSeeAllMarketsClick: () -> Unit,
) )
} }

View file

@ -29,14 +29,9 @@ internal class FeedMarketsBatchFlowManager(
private val currentAppCurrency: Provider<AppCurrency>, private val currentAppCurrency: Provider<AppCurrency>,
private val modelScope: CoroutineScope, private val modelScope: CoroutineScope,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
orders: List<TokenMarketListConfig.Order> = TokenMarketListConfig.Order.entries,
) { ) {
private val managersByOrder = listOf( private val managersByOrder = orders.associateWith { order ->
TokenMarketListConfig.Order.ByRating,
TokenMarketListConfig.Order.Trending,
TokenMarketListConfig.Order.Buyers,
TokenMarketListConfig.Order.TopGainers,
TokenMarketListConfig.Order.TopLosers,
).associateWith { order ->
createManagerForOrder(order) createManagerForOrder(order)
} }

View file

@ -26,6 +26,7 @@ import com.tangem.domain.search.usecase.SaveRecentSearchTokenUseCase
import com.tangem.domain.search.usecase.SaveSearchQueryUseCase import com.tangem.domain.search.usecase.SaveSearchQueryUseCase
import com.tangem.features.feed.components.search.DefaultSearchComponent import com.tangem.features.feed.components.search.DefaultSearchComponent
import com.tangem.features.feed.components.search.SearchBottomSheetRoute import com.tangem.features.feed.components.search.SearchBottomSheetRoute
import com.tangem.features.feed.model.feed.state.FeedMarketsBatchFlowManager
import com.tangem.features.feed.model.market.list.state.MarketsListUM import com.tangem.features.feed.model.market.list.state.MarketsListUM
import com.tangem.features.feed.model.market.list.state.SortByTypeUM import com.tangem.features.feed.model.market.list.state.SortByTypeUM
import com.tangem.features.feed.model.market.list.statemanager.MarketsListBatchFlowManager import com.tangem.features.feed.model.market.list.statemanager.MarketsListBatchFlowManager
@ -33,6 +34,8 @@ import com.tangem.features.feed.model.search.analytics.SearchAnalyticsHelper
import com.tangem.features.feed.model.search.converter.* import com.tangem.features.feed.model.search.converter.*
import com.tangem.features.feed.model.search.state.SearchStateController import com.tangem.features.feed.model.search.state.SearchStateController
import com.tangem.features.feed.model.search.state.transformers.* import com.tangem.features.feed.model.search.state.transformers.*
import com.tangem.features.feed.ui.feed.state.MarketChartUM
import com.tangem.features.feed.ui.feed.state.SortChartConfigUM
import com.tangem.features.feed.ui.search.state.MarketSearchResultUM import com.tangem.features.feed.ui.search.state.MarketSearchResultUM
import com.tangem.features.feed.ui.search.state.SearchContentUM import com.tangem.features.feed.ui.search.state.SearchContentUM
import com.tangem.features.feed.ui.search.state.SearchUM import com.tangem.features.feed.ui.search.state.SearchUM
@ -42,6 +45,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn import com.tangem.utils.coroutines.saveIn
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -57,6 +61,7 @@ internal class SearchModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer, paramsContainer: ParamsContainer,
getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase, getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase,
private val getTopFiveMarketTokenUseCase: GetTopFiveMarketTokenUseCase,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val getSearchResultsUseCase: GetSearchResultsUseCase, private val getSearchResultsUseCase: GetSearchResultsUseCase,
@ -115,6 +120,16 @@ internal class SearchModel @Inject constructor(
) )
} }
private val topMarketsManager by lazy {
FeedMarketsBatchFlowManager(
getTopFiveMarketTokenUseCase = getTopFiveMarketTokenUseCase,
currentAppCurrency = Provider { currentAppCurrency.value },
modelScope = modelScope,
dispatchers = dispatchers,
orders = listOf(TokenMarketListConfig.Order.ByRating),
)
}
val bottomSheetNavigation: SlotNavigation<SearchBottomSheetRoute> = SlotNavigation() val bottomSheetNavigation: SlotNavigation<SearchBottomSheetRoute> = SlotNavigation()
val state: StateFlow<SearchUM> get() = stateController.uiState val state: StateFlow<SearchUM> get() = stateController.uiState
@ -127,6 +142,7 @@ internal class SearchModel @Inject constructor(
subscribeToAppCurrencyChanges() subscribeToAppCurrencyChanges()
subscribeToMarketLoadingErrors() subscribeToMarketLoadingErrors()
subscribeToResultsShown() subscribeToResultsShown()
subscribeToTopMarkets()
loadHistory() loadHistory()
searchAnalyticsHelper.sendSearchScreenOpened(params.sourceParams) searchAnalyticsHelper.sendSearchScreenOpened(params.sourceParams)
} }
@ -190,6 +206,15 @@ internal class SearchModel @Inject constructor(
params.onMarketTokenClick(tokenMarketParams, currentAppCurrency.value) params.onMarketTokenClick(tokenMarketParams, currentAppCurrency.value)
} }
fun onTopMarketSeeAllClick() {
params.onSeeAllMarketsClick()
}
fun onTopMarketItemClick(item: MarketsListItemUM) {
val token = topMarketsManager.getTokenMarketById(item.id) ?: return
params.onMarketTokenClick(token.toSerializableParam(), currentAppCurrency.value)
}
private fun initCallbacks() { private fun initCallbacks() {
stateController.update(object : SearchUMTransformer { stateController.update(object : SearchUMTransformer {
override fun transform(prevState: SearchUM): SearchUM { override fun transform(prevState: SearchUM): SearchUM {
@ -402,6 +427,35 @@ internal class SearchModel @Inject constructor(
} }
} }
private fun subscribeToTopMarkets() {
combine(
topMarketsManager.itemsByOrder,
topMarketsManager.loadingStatesByOrder,
topMarketsManager.errorStatesByOrder,
) { itemsByOrder, _, errorsByOrder ->
val ratingItems = itemsByOrder[SortByTypeUM.Rating] ?: persistentListOf()
val error = errorsByOrder[SortByTypeUM.Rating]
when {
ratingItems.isNotEmpty() -> MarketChartUM.Content(
items = ratingItems,
sortChartConfig = SortChartConfigUM(sortByType = SortByTypeUM.Rating, isSelected = true),
)
error != null -> MarketChartUM.LoadingError(
onRetryClicked = { topMarketsManager.reloadManager(TokenMarketListConfig.Order.ByRating) },
)
else -> MarketChartUM.Loading
}
}
.distinctUntilChanged()
.onEach { chart ->
stateController.update(SetTopMarketsTransformer(chart))
if (chart is MarketChartUM.Content) {
topMarketsManager.loadCharts(TokenMarketListConfig.Order.ByRating)
}
}
.launchIn(modelScope)
}
private fun loadHistory() { private fun loadHistory() {
modelScope.launch { modelScope.launch {
getSearchResultsUseCase(query = "").collectLatest { searchResult -> getSearchResultsUseCase(query = "").collectLatest { searchResult ->

View file

@ -5,6 +5,7 @@ import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.feed.impl.R import com.tangem.features.feed.impl.R
import com.tangem.features.feed.model.search.state.transformers.SearchUMTransformer import com.tangem.features.feed.model.search.state.transformers.SearchUMTransformer
import com.tangem.features.feed.ui.feed.state.MarketChartUM
import com.tangem.features.feed.ui.search.state.SearchContentUM import com.tangem.features.feed.ui.search.state.SearchContentUM
import com.tangem.features.feed.ui.search.state.SearchUM import com.tangem.features.feed.ui.search.state.SearchUM
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -38,6 +39,7 @@ internal class SearchStateController @Inject constructor() {
onClearClick = {}, onClearClick = {},
), ),
content = SearchContentUM.InitialEmpty, content = SearchContentUM.InitialEmpty,
topMarkets = MarketChartUM.Loading,
) )
} }
} }

View file

@ -0,0 +1,11 @@
package com.tangem.features.feed.model.search.state.transformers
import com.tangem.features.feed.ui.feed.state.MarketChartUM
import com.tangem.features.feed.ui.search.state.SearchUM
internal class SetTopMarketsTransformer(private val topMarkets: MarketChartUM) : SearchUMTransformer {
override fun transform(prevState: SearchUM): SearchUM {
return prevState.copy(topMarkets = topMarkets)
}
}

View file

@ -109,7 +109,9 @@ private fun FeedListContent(
MarketBlock( MarketBlock(
marketChart = state.marketChartConfig.marketCharts[SortByTypeUM.Rating], marketChart = state.marketChartConfig.marketCharts[SortByTypeUM.Rating],
feedListCallbacks = state.feedListCallbacks, onSeeAllClick = { state.feedListCallbacks.onMarketOpenClick(SortByTypeUM.Rating) },
onItemClick = state.feedListCallbacks.onMarketItemClick,
modifier = Modifier.padding(horizontal = 16.dp),
) )
promoBannersBlockComponent?.Content( promoBannersBlockComponent?.Content(

View file

@ -28,17 +28,19 @@ internal fun ColumnScope.Header(
onSeeAllClick: () -> Unit, onSeeAllClick: () -> Unit,
isLoading: Boolean, isLoading: Boolean,
shouldShowSeeAll: Boolean, shouldShowSeeAll: Boolean,
modifier: Modifier = Modifier,
title: @Composable () -> Unit, title: @Composable () -> Unit,
) { ) {
val isRedesignEnabled = LocalRedesignEnabled.current val isRedesignEnabled = LocalRedesignEnabled.current
if (isRedesignEnabled) { if (isRedesignEnabled) {
SpacerH(12.dp) SpacerH(12.dp)
} }
AnimatedContent(isLoading) { animatedState -> AnimatedContent(
targetState = isLoading,
modifier = modifier,
) { animatedState ->
Row( Row(
modifier = Modifier modifier = Modifier.fillMaxWidth(),
.fillMaxWidth()
.padding(horizontal = 20.dp),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {

View file

@ -52,6 +52,7 @@ private fun EarnBlockV1(onSeeAllClick: () -> Unit, earnListUM: EarnListUM, modif
onSeeAllClick = onSeeAllClick, onSeeAllClick = onSeeAllClick,
isLoading = earnListUM is EarnListUM.Loading, isLoading = earnListUM is EarnListUM.Loading,
shouldShowSeeAll = earnListUM is EarnListUM.Content, shouldShowSeeAll = earnListUM is EarnListUM.Content,
modifier = Modifier.padding(horizontal = 20.dp),
) )
SpacerH(12.dp) SpacerH(12.dp)
@ -96,6 +97,7 @@ private fun EarnBlockV2(onSeeAllClick: () -> Unit, earnListUM: EarnListUM, modif
onSeeAllClick = onSeeAllClick, onSeeAllClick = onSeeAllClick,
isLoading = earnListUM is EarnListUM.Loading, isLoading = earnListUM is EarnListUM.Loading,
shouldShowSeeAll = earnListUM is EarnListUM.Content, shouldShowSeeAll = earnListUM is EarnListUM.Content,
modifier = Modifier.padding(horizontal = 20.dp),
) )
SpacerH(12.dp) SpacerH(12.dp)

View file

@ -36,7 +36,12 @@ import com.tangem.features.feed.ui.feed.state.MarketChartConfig
import com.tangem.features.feed.ui.feed.state.MarketChartUM import com.tangem.features.feed.ui.feed.state.MarketChartUM
@Composable @Composable
internal fun MarketBlock(marketChart: MarketChartUM?, feedListCallbacks: FeedListCallbacks) { internal fun MarketBlock(
marketChart: MarketChartUM?,
onSeeAllClick: () -> Unit,
onItemClick: (MarketsListItemUM) -> Unit,
modifier: Modifier = Modifier,
) {
val isRedesignEnabled = LocalRedesignEnabled.current val isRedesignEnabled = LocalRedesignEnabled.current
AnimatedContent( AnimatedContent(
targetState = marketChart, targetState = marketChart,
@ -48,7 +53,7 @@ internal fun MarketBlock(marketChart: MarketChartUM?, feedListCallbacks: FeedLis
MarketChartUM.Loading, MarketChartUM.Loading,
is MarketChartUM.LoadingError, is MarketChartUM.LoadingError,
-> { -> {
Column(modifier = Modifier.fillMaxWidth()) { Column(modifier = modifier.fillMaxWidth()) {
Header( Header(
title = { title = {
Text( Text(
@ -67,16 +72,18 @@ internal fun MarketBlock(marketChart: MarketChartUM?, feedListCallbacks: FeedLis
maxLines = 1, maxLines = 1,
) )
}, },
onSeeAllClick = { feedListCallbacks.onMarketOpenClick(SortByTypeUM.Rating) }, onSeeAllClick = onSeeAllClick,
shouldShowSeeAll = currentChart is MarketChartUM.Content, shouldShowSeeAll = currentChart is MarketChartUM.Content,
isLoading = currentChart is MarketChartUM.Loading, isLoading = currentChart is MarketChartUM.Loading,
// Header text is inset so it aligns with the list item text inside the card; the card itself
// sits at the block edge (supplied by [modifier]).
modifier = Modifier.padding(horizontal = MARKET_BLOCK_HEADER_PADDING),
) )
SpacerH(if (isRedesignEnabled) 20.dp else 12.dp) SpacerH(if (isRedesignEnabled) 20.dp else 12.dp)
Charts( Charts(
onItemClick = feedListCallbacks.onMarketItemClick, onItemClick = onItemClick,
modifier = Modifier.padding(horizontal = 16.dp),
marketChart = currentChart, marketChart = currentChart,
) )
} }
@ -115,6 +122,7 @@ internal fun ColumnScope.MarketPulseBlock(marketChartConfig: MarketChartConfig,
onSeeAllClick = { onSeeAllClick() }, onSeeAllClick = { onSeeAllClick() },
shouldShowSeeAll = true, shouldShowSeeAll = true,
isLoading = marketChartConfig.marketCharts[marketChartConfig.currentSortByType] is MarketChartUM.Loading, isLoading = marketChartConfig.marketCharts[marketChartConfig.currentSortByType] is MarketChartUM.Loading,
modifier = Modifier.padding(horizontal = 20.dp),
) )
LazyRow( LazyRow(
@ -219,4 +227,7 @@ private fun Charts(
} }
} }
private const val DEFAULT_CHART_SIZE_IN_MARKET = 5 private const val DEFAULT_CHART_SIZE_IN_MARKET = 5
/** Header inset so its text aligns with the list item text inside the card (the card sits at the block edge). */
private val MARKET_BLOCK_HEADER_PADDING = 4.dp

View file

@ -2,11 +2,7 @@ package com.tangem.features.feed.ui.feed.components
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -148,6 +144,7 @@ private fun NewsContentBlock(feedListCallbacks: FeedListCallbacks, news: NewsUM,
onSeeAllClick = { feedListCallbacks.onOpenAllNews(false) }, onSeeAllClick = { feedListCallbacks.onOpenAllNews(false) },
isLoading = news.newsUMState == NewsUMState.LOADING, isLoading = news.newsUMState == NewsUMState.LOADING,
shouldShowSeeAll = news.newsUMState == NewsUMState.CONTENT, shouldShowSeeAll = news.newsUMState == NewsUMState.CONTENT,
modifier = Modifier.padding(horizontal = 20.dp),
) )
SpacerH(12.dp) SpacerH(12.dp)
@ -200,6 +197,7 @@ private fun NewsErrorBlock(onRetryClick: () -> Unit) {
onSeeAllClick = {}, onSeeAllClick = {},
shouldShowSeeAll = false, shouldShowSeeAll = false,
isLoading = false, isLoading = false,
modifier = Modifier.padding(horizontal = 20.dp),
) )
SpacerH(12.dp) SpacerH(12.dp)
BlockCard( BlockCard(

View file

@ -36,7 +36,12 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.feed.ui.search.state.* import com.tangem.features.feed.ui.feed.components.MarketBlock
import com.tangem.features.feed.ui.feed.state.MarketChartUM
import com.tangem.features.feed.ui.search.state.MarketSearchResultUM
import com.tangem.features.feed.ui.search.state.SearchCallbacks
import com.tangem.features.feed.ui.search.state.SearchContentUM
import com.tangem.features.feed.ui.search.state.TextHintItemUM
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
private const val PLACEHOLDER_COUNT = 10 private const val PLACEHOLDER_COUNT = 10
@ -46,6 +51,7 @@ private const val USER_ASSETS_LIMIT = 3
@Composable @Composable
internal fun SearchContent( internal fun SearchContent(
content: SearchContentUM, content: SearchContentUM,
topMarkets: MarketChartUM,
searchCallbacks: SearchCallbacks, searchCallbacks: SearchCallbacks,
contentPadding: PaddingValues, contentPadding: PaddingValues,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -85,12 +91,16 @@ internal fun SearchContent(
) { ) {
when (content) { when (content) {
is SearchContentUM.InitialEmpty -> Unit is SearchContentUM.InitialEmpty -> Unit
is SearchContentUM.History -> searchHistoryItems( is SearchContentUM.History -> if (content.textHints.isEmpty() && content.recentTokens.isEmpty()) {
history = content, topMarketsBlock(topMarkets, searchCallbacks)
onClearAllClick = searchCallbacks.onClearHintsClick, } else {
onHintClick = searchCallbacks.onTextHintClick, searchHistoryItems(
onHistoryTokenClick = searchCallbacks.onHistoryTokenClick, history = content,
) onClearAllClick = searchCallbacks.onClearHintsClick,
onHintClick = searchCallbacks.onTextHintClick,
onHistoryTokenClick = searchCallbacks.onHistoryTokenClick,
)
}
is SearchContentUM.Results -> searchResultsItems( is SearchContentUM.Results -> searchResultsItems(
results = content, results = content,
isUserAssetsExpanded = isUserAssetsExpanded, isUserAssetsExpanded = isUserAssetsExpanded,
@ -114,6 +124,16 @@ internal fun SearchContent(
} }
} }
private fun LazyListScope.topMarketsBlock(topMarkets: MarketChartUM, searchCallbacks: SearchCallbacks) {
item(key = "top_markets") {
MarketBlock(
marketChart = topMarkets,
onSeeAllClick = searchCallbacks.onTopMarketSeeAllClick,
onItemClick = searchCallbacks.onTopMarketItemClick,
)
}
}
private fun LazyListScope.searchHistoryItems( private fun LazyListScope.searchHistoryItems(
history: SearchContentUM.History, history: SearchContentUM.History,
onClearAllClick: (() -> Unit), onClearAllClick: (() -> Unit),

View file

@ -21,8 +21,14 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
import com.tangem.features.feed.ui.feed.state.MarketChartUM
import com.tangem.features.feed.ui.feed.state.SortChartConfigUM
import com.tangem.features.feed.ui.search.SearchContent import com.tangem.features.feed.ui.search.SearchContent
import com.tangem.features.feed.ui.search.state.* import com.tangem.features.feed.ui.search.state.MarketSearchResultUM
import com.tangem.features.feed.ui.search.state.SearchCallbacks
import com.tangem.features.feed.ui.search.state.SearchContentUM
import com.tangem.features.feed.ui.search.state.TextHintItemUM
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@ -373,6 +379,11 @@ internal object SearchContentPreviewFixtures {
), ),
).toImmutableList() ).toImmutableList()
fun topMarketsContent(): MarketChartUM = MarketChartUM.Content(
items = marketListShort(),
sortChartConfig = SortChartConfigUM(sortByType = SortByTypeUM.Rating, isSelected = true),
)
fun allScenarios(): List<SearchContentPreviewScenario> = listOf( fun allScenarios(): List<SearchContentPreviewScenario> = listOf(
scenarioInitialEmpty, scenarioInitialEmpty,
scenarioHistoryEmptyBoth, scenarioHistoryEmptyBoth,
@ -398,6 +409,8 @@ private val SearchContentPreviewCallbacks = SearchCallbacks(
onTextHintClick = { _ -> }, onTextHintClick = { _ -> },
onResultMarketTokenClick = { _ -> }, onResultMarketTokenClick = { _ -> },
onHistoryTokenClick = { _ -> }, onHistoryTokenClick = { _ -> },
onTopMarketSeeAllClick = {},
onTopMarketItemClick = { _ -> },
) )
/** All [SearchContentPreviewScenario] values for the Preview Parameter dropdown in Android Studio. */ /** All [SearchContentPreviewScenario] values for the Preview Parameter dropdown in Android Studio. */
@ -420,6 +433,7 @@ private fun SearchContentPreviewHost(
) { ) {
SearchContent( SearchContent(
content = scenario.content, content = scenario.content,
topMarkets = SearchContentPreviewFixtures.topMarketsContent(),
searchCallbacks = SearchContentPreviewCallbacks, searchCallbacks = SearchContentPreviewCallbacks,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(), contentPadding = PaddingValues(),

View file

@ -8,4 +8,6 @@ internal data class SearchCallbacks(
val onTextHintClick: (hint: String) -> Unit, val onTextHintClick: (hint: String) -> Unit,
val onResultMarketTokenClick: (MarketsListItemUM) -> Unit, val onResultMarketTokenClick: (MarketsListItemUM) -> Unit,
val onHistoryTokenClick: (MarketsListItemUM) -> Unit, val onHistoryTokenClick: (MarketsListItemUM) -> Unit,
val onTopMarketSeeAllClick: () -> Unit,
val onTopMarketItemClick: (MarketsListItemUM) -> Unit,
) )

View file

@ -4,11 +4,13 @@ import androidx.compose.runtime.Immutable
import com.tangem.common.ui.markets.models.MarketsListItemUM import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.common.ui.markets.tokenselector.UserAssetItemUM import com.tangem.common.ui.markets.tokenselector.UserAssetItemUM
import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.features.feed.ui.feed.state.MarketChartUM
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
data class SearchUM( internal data class SearchUM(
val searchBar: SearchBarUM, val searchBar: SearchBarUM,
val content: SearchContentUM, val content: SearchContentUM,
val topMarkets: MarketChartUM,
) )
@Immutable @Immutable