Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-18 18:16:55 +01:00
parent 64693f66ca
commit 43f27a2f33
26 changed files with 21 additions and 370 deletions

View file

@ -353,8 +353,6 @@ internal class FeedComponentModel @Inject constructor(
SortByTypeUM.ExperiencedBuyers,
SortByTypeUM.TopGainers,
SortByTypeUM.TopLosers,
SortByTypeUM.Staking,
SortByTypeUM.YieldSupply,
-> {
analyticsEventHandler.send(
FeedAnalyticsEvent.TokenListOpened(
@ -439,8 +437,6 @@ internal class FeedComponentModel @Inject constructor(
SortByTypeUM.ExperiencedBuyers -> TokenMarketListConfig.Order.Buyers
SortByTypeUM.TopGainers -> TokenMarketListConfig.Order.TopGainers
SortByTypeUM.TopLosers -> TokenMarketListConfig.Order.TopLosers
SortByTypeUM.Staking -> TokenMarketListConfig.Order.Staking
SortByTypeUM.YieldSupply -> TokenMarketListConfig.Order.YieldSupply
}
}

View file

@ -341,8 +341,6 @@ internal class FeedMarketsBatchFlowManager(
TokenMarketListConfig.Order.Buyers -> SortByTypeUM.ExperiencedBuyers
TokenMarketListConfig.Order.TopGainers -> SortByTypeUM.TopGainers
TokenMarketListConfig.Order.TopLosers -> SortByTypeUM.TopLosers
TokenMarketListConfig.Order.Staking -> SortByTypeUM.Staking
TokenMarketListConfig.Order.YieldSupply -> SortByTypeUM.YieldSupply
}
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.features.feed.model.market.list
import androidx.compose.runtime.Stable
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -12,20 +11,11 @@ import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase
import com.tangem.domain.markets.ShouldShowYieldModeMarketPromoUseCase
import com.tangem.domain.markets.TokenMarketListConfig
import com.tangem.domain.markets.toSerializableParam
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.UserCountryError
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListComponent
import com.tangem.features.feed.model.market.list.analytics.MarketsListAnalyticsEvent
import com.tangem.features.feed.model.market.list.state.ListUM
import com.tangem.features.feed.model.market.list.state.MarketsListUM
import com.tangem.features.feed.model.market.list.state.MarketsNotificationUM
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.MarketsListUMStateManager
@ -49,11 +39,8 @@ internal class MarketsListModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
shouldShowYieldModeMarketPromoUseCase: ShouldShowYieldModeMarketPromoUseCase,
paramsContainer: ParamsContainer,
private val promoRepository: PromoRepository,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getUserCountryUseCase: GetUserCountryUseCase,
) : Model() {
private val updateQuotesJob = JobHolder()
@ -126,61 +113,30 @@ internal class MarketsListModel @Inject constructor(
flow = searchMarketsListManager.uiItems,
flow2 = searchMarketsListManager.isInInitialLoadingErrorState,
flow3 = searchMarketsListManager.isSearchNotFoundState,
flow4 = shouldShowYieldModeMarketPromoUseCase(
appCurrency = currentAppCurrency.value,
interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(),
).conflate(),
flow5 = getUserCountryUseCase.invoke(),
) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, isYieldModePromo, userCountry ->
) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState ->
MarketsItemsData(
items = uiItems,
isInErrorState = isInInitialLoadingErrorState,
isSearchNotFound = isSearchNotFoundState,
shouldShowYieldModePromo = isYieldModePromo,
userCountry = userCountry,
)
}
} else {
combine(
flow = mainMarketsListManager.uiItems,
flow2 = mainMarketsListManager.isInInitialLoadingErrorState,
flow3 = shouldShowYieldModeMarketPromoUseCase(
appCurrency = currentAppCurrency.value,
interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(),
).conflate(),
flow4 = getUserCountryUseCase.invoke(),
) { uiItems, isInInitialLoadingErrorState, shouldShowYieldModePromo, userCountry ->
) { uiItems, isInInitialLoadingErrorState ->
MarketsItemsData(
items = uiItems,
isInErrorState = isInInitialLoadingErrorState,
isSearchNotFound = false,
shouldShowYieldModePromo = shouldShowYieldModePromo,
userCountry = userCountry,
)
}
}
}.collect { marketsItemsData ->
val isApplyFCARestrictions = marketsItemsData.userCountry.getOrNull().needApplyFCARestrictions()
val shouldShowYieldModePromo = marketsItemsData.shouldShowYieldModePromo && !isApplyFCARestrictions
if (marketsListUMStateManager.state.value.marketsNotificationUM == null && shouldShowYieldModePromo) {
analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModePromoShown())
}
marketsListUMStateManager.onUiItemsChanged(
uiItems = marketsItemsData.items,
isInErrorState = marketsItemsData.isInErrorState,
isSearchNotFound = marketsItemsData.isSearchNotFound,
marketsNotificationUM = if (shouldShowYieldModePromo) {
MarketsNotificationUM.YieldSupplyPromo(
onClick = {
analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModeMoreInfoClicked())
marketsListUMStateManager.selectedSortByType = SortByTypeUM.YieldSupply
},
onCloseClick = { onYieldModeNotificationCloseClick() },
)
} else {
null
},
)
}
}
@ -282,14 +238,6 @@ internal class MarketsListModel @Inject constructor(
mainMarketsListManager.reload()
}
private fun MarketsListUM.TrendInterval.toBatchRequestInterval(): TokenMarketListConfig.Interval {
return when (this) {
MarketsListUM.TrendInterval.H24 -> TokenMarketListConfig.Interval.H24
MarketsListUM.TrendInterval.D7 -> TokenMarketListConfig.Interval.WEEK
MarketsListUM.TrendInterval.M1 -> TokenMarketListConfig.Interval.MONTH
}
}
private fun initAnalytics() {
searchMarketsListManager
.isSearchNotFoundState
@ -331,18 +279,9 @@ internal class MarketsListModel @Inject constructor(
}.saveIn(updateQuotesJob)
}
private fun onYieldModeNotificationCloseClick() {
analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModePromoClosed())
modelScope.launch {
promoRepository.setMarketsYieldSupplyNotificationHideClicked()
}
}
private class MarketsItemsData(
val items: ImmutableList<MarketsListItemUM>,
val isInErrorState: Boolean,
val isSearchNotFound: Boolean,
val shouldShowYieldModePromo: Boolean,
val userCountry: Either<UserCountryError, UserCountry>,
)
}

View file

@ -23,8 +23,6 @@ internal sealed class MarketsListAnalyticsEvent(
SortByTypeUM.ExperiencedBuyers -> "Buyers"
SortByTypeUM.TopGainers -> "Gainers"
SortByTypeUM.TopLosers -> "Losers"
SortByTypeUM.Staking -> "Staking"
SortByTypeUM.YieldSupply -> "Yield Supply"
},
"Period" to when (interval) {
MarketsListUM.TrendInterval.H24 -> "24h"

View file

@ -19,7 +19,6 @@ internal data class MarketsListUM(
val selectedInterval: TrendInterval,
val onIntervalClick: (TrendInterval) -> Unit,
val onSortByButtonClick: () -> Unit,
val marketsNotificationUM: MarketsNotificationUM?,
val onSearchClicked: () -> Unit,
) {
val isInSearchMode
@ -43,8 +42,6 @@ internal enum class SortByTypeUM(val text: TextReference) {
ExperiencedBuyers(resourceReference(R.string.markets_sort_by_experienced_buyers_title)),
TopGainers(resourceReference(R.string.markets_sort_by_top_gainers_title)),
TopLosers(resourceReference(R.string.markets_sort_by_top_losers_title)),
Staking(resourceReference(R.string.common_staking)),
YieldSupply(resourceReference(R.string.markets_sort_by_yield_mode_title)),
}
@Immutable

View file

@ -1,22 +0,0 @@
package com.tangem.features.feed.model.market.list.state
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.feed.impl.R
internal sealed class MarketsNotificationUM(val config: NotificationConfig) {
data class YieldSupplyPromo(
val onClick: () -> Unit,
val onCloseClick: () -> Unit,
) : MarketsNotificationUM(
config = NotificationConfig(
iconResId = R.drawable.img_yield_supply_in_market_notification,
title = resourceReference(R.string.markets_yield_supply_banner_title),
subtitle = TextReference.EMPTY,
onClick = onClick,
onCloseClick = onCloseClick,
),
)
}

View file

@ -24,8 +24,10 @@ import com.tangem.utils.coroutines.saveIn
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
private const val LOG_EVENTS = true
@ -305,8 +307,6 @@ internal class MarketsListBatchFlowManager(
SortByTypeUM.ExperiencedBuyers -> TokenMarketListConfig.Order.Buyers
SortByTypeUM.TopGainers -> TokenMarketListConfig.Order.TopGainers
SortByTypeUM.TopLosers -> TokenMarketListConfig.Order.TopLosers
SortByTypeUM.Staking -> TokenMarketListConfig.Order.Staking
SortByTypeUM.YieldSupply -> TokenMarketListConfig.Order.YieldSupply
}
}

View file

@ -99,7 +99,6 @@ internal class MarketsListUMStateManager(
isInErrorState: Boolean,
isSearchNotFound: Boolean,
uiItems: ImmutableList<MarketsListItemUM>,
marketsNotificationUM: MarketsNotificationUM?,
) {
state.update { marketsListUM ->
when {
@ -115,19 +114,13 @@ internal class MarketsListUMStateManager(
marketsListUM.copy(list = ListUM.Loading)
}
else -> {
marketsListUM.updateItems(
newItems = uiItems,
marketsNotificationUM = marketsNotificationUM,
)
marketsListUM.updateItems(newItems = uiItems)
}
}
}
}
private fun MarketsListUM.updateItems(
newItems: ImmutableList<MarketsListItemUM>,
marketsNotificationUM: MarketsNotificationUM?,
): MarketsListUM {
private fun MarketsListUM.updateItems(newItems: ImmutableList<MarketsListItemUM>): MarketsListUM {
val currentState = this
if (isInSearchMode.not() || currentState.showUnder100kButtonAlreadyPressed()) {
@ -139,7 +132,6 @@ internal class MarketsListUMStateManager(
shouldShowUnder100kTokensNotificationWasHidden = currentState
.showUnder100kButtonAlreadyPressed(),
),
marketsNotificationUM = marketsNotificationUM,
)
}
@ -243,7 +235,6 @@ internal class MarketsListUMStateManager(
onOptionClicked = ::onBottomSheetOptionClicked,
),
),
marketsNotificationUM = null,
onSearchClicked = {
analyticsEventHandler.send(FeedAnalyticsEvent.TokenSearchedClicked())
changeSearchBarIsActive(true)

View file

@ -2,7 +2,6 @@ package com.tangem.features.feed.ui.feed.preview
import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.common.ui.markets.models.MarketsListItemUM
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM
@ -15,6 +14,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
import com.tangem.features.feed.ui.earn.state.EarnListUM
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.features.feed.ui.feed.state.*
import kotlinx.collections.immutable.*
@ -77,8 +77,6 @@ internal object FeedListPreviewDataProvider {
items = items,
isSelected = false,
),
SortByTypeUM.Staking to MarketChartUM.Loading,
SortByTypeUM.TopLosers to MarketChartUM.Loading,
)
val ratingChart = if (includeErrorState) {

View file

@ -33,7 +33,9 @@ import com.tangem.core.ui.components.fields.TangemSearchBarDefaults
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.keyboardAsState
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -42,12 +44,10 @@ import com.tangem.features.feed.impl.R
import com.tangem.features.feed.model.market.list.state.*
import com.tangem.features.feed.ui.market.list.components.MarketsListLazyColumn
import com.tangem.features.feed.ui.market.list.components.MarketsListSortByBottomSheet
import com.tangem.features.feed.ui.market.list.components.YieldSupplyInMarketsPromoNotification
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
private const val SHOW_MORE_KEY = "privacyPolicy"
private const val DELAY_FOR_FOCUS_REQUEST = 500L
@Composable
@ -152,40 +152,6 @@ private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modif
onSortByClick = state.onSortByButtonClick,
)
}
AnimatedVisibility(
state.list !is ListUM.LoadingError && state.isInSearchMode.not() &&
state.selectedSortBy != SortByTypeUM.YieldSupply,
) {
val wrappedNotification = remember(state.list) { state.marketsNotificationUM }
val showMore = stringResourceSafe(R.string.common_show_more)
when (wrappedNotification) {
is MarketsNotificationUM.YieldSupplyPromo -> {
val description = stringResourceSafe(
R.string.markets_yield_supply_banner_description,
showMore,
)
val clickableDescription = annotatedReference {
append(description.substringBefore(showMore))
pushStringAnnotation(SHOW_MORE_KEY, "")
appendColored(showMore, TangemTheme.colors.text.accent)
pop()
}
YieldSupplyInMarketsPromoNotification(
config = wrappedNotification.config.copy(
subtitle = clickableDescription,
),
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
)
}
else -> { /* no-op */
}
}
}
}
}
val strokeWidth = TangemTheme.dimens.size0_5
@ -376,10 +342,6 @@ private fun Preview() {
onDismissRequest = {},
content = SortByBottomSheetContentUM(selectedOption = SortByTypeUM.Rating) {},
),
marketsNotificationUM = MarketsNotificationUM.YieldSupplyPromo(
onClick = {},
onCloseClick = {},
),
onSearchClicked = {},
),
)