Updated on 2026-08-14
This commit is contained in:
parent
ae4029c4d5
commit
7db3810064
16 changed files with 159 additions and 40 deletions
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 ->
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue