Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-17 19:20:51 +05:00
parent ed89b4bcd0
commit 533c036819
28 changed files with 334 additions and 162 deletions

View file

@ -8,7 +8,6 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
import com.tangem.domain.promo.PromoRepository import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.staking.StakingIdFactory import com.tangem.domain.staking.StakingIdFactory
import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
@ -129,13 +128,11 @@ object MarketsDomainModule {
@Provides @Provides
@Singleton @Singleton
fun provideGetStakingNotificationMaxApyUseCase( fun provideShouldShowYieldModeMarketPromoUseCase(
settingsRepository: SettingsRepository,
promoRepository: PromoRepository, promoRepository: PromoRepository,
marketsTokenRepository: MarketsTokenRepository, marketsTokenRepository: MarketsTokenRepository,
): GetStakingNotificationMaxApyUseCase { ): ShouldShowYieldModeMarketPromoUseCase {
return GetStakingNotificationMaxApyUseCase( return ShouldShowYieldModeMarketPromoUseCase(
settingsRepository = settingsRepository,
promoRepository = promoRepository, promoRepository = promoRepository,
marketsTokenRepository = marketsTokenRepository, marketsTokenRepository = marketsTokenRepository,
) )

View file

@ -26,6 +26,7 @@ data class TokenMarketListResponse(
@Json(name = "market_cap") val marketCap: BigDecimal?, @Json(name = "market_cap") val marketCap: BigDecimal?,
@Json(name = "is_under_market_cap_limit") val isUnderMarketCapLimit: Boolean?, @Json(name = "is_under_market_cap_limit") val isUnderMarketCapLimit: Boolean?,
@Json(name = "staking_opportunities") val stakingOpportunities: List<StakingOpportunities>?, @Json(name = "staking_opportunities") val stakingOpportunities: List<StakingOpportunities>?,
@Json(name = "max_yield_apy") val maxYieldApy: BigDecimal?,
) { ) {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)

View file

@ -77,8 +77,8 @@ object PreferencesKeys {
val SHOULD_SHOW_MARKETS_TOOLTIP_KEY by lazy { booleanPreferencesKey(name = "shouldShowMarketsTooltip") } val SHOULD_SHOW_MARKETS_TOOLTIP_KEY by lazy { booleanPreferencesKey(name = "shouldShowMarketsTooltip") }
val MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY by lazy { val MARKETS_YIELD_SUPPLY_NOTIFICATION_HIDE_CLICKED_KEY by lazy {
booleanPreferencesKey(name = "marketsStakingNotificationHideClicked") booleanPreferencesKey(name = "marketsYieldSupplyNotificationHideClicked")
} }
val WALLET_FIRST_USAGE_DATE_KEY by lazy { longPreferencesKey(name = "walletFirstUsageDate") } val WALLET_FIRST_USAGE_DATE_KEY by lazy { longPreferencesKey(name = "walletFirstUsageDate") }

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -20,6 +20,7 @@ import com.tangem.datasource.api.markets.TangemTechMarketsApi
import com.tangem.datasource.api.markets.models.response.TokenMarketExchangesResponse import com.tangem.datasource.api.markets.models.response.TokenMarketExchangesResponse
import com.tangem.datasource.local.datastore.RuntimeStateStore import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.* import com.tangem.domain.markets.*
import com.tangem.domain.markets.repositories.MarketsTokenRepository import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.account.DerivationIndex
@ -28,9 +29,8 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.pagination.* import com.tangem.pagination.*
import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.math.BigDecimal import timber.log.Timber
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
@ -43,7 +43,6 @@ internal class DefaultMarketsTokenRepository(
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
private val cacheRegistry: CacheRegistry, private val cacheRegistry: CacheRegistry,
private val tokenExchangesStore: RuntimeStateStore<List<TokenMarketExchangesResponse.Exchange>>, private val tokenExchangesStore: RuntimeStateStore<List<TokenMarketExchangesResponse.Exchange>>,
private val maxApyStore: RuntimeStateStore<BigDecimal?>,
private val networkFactory: NetworkFactory, private val networkFactory: NetworkFactory,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
) : MarketsTokenRepository { ) : MarketsTokenRepository {
@ -95,8 +94,6 @@ internal class DefaultMarketsTokenRepository(
val tokenMarketListWithMaxApy = TokenMarketListConverter.convert(res) val tokenMarketListWithMaxApy = TokenMarketListConverter.convert(res)
maxApyStore.store(tokenMarketListWithMaxApy.maxApy)
return BatchFetchResult.Success( return BatchFetchResult.Success(
data = tokenMarketListWithMaxApy.tokens, data = tokenMarketListWithMaxApy.tokens,
last = last, last = last,
@ -294,8 +291,24 @@ internal class DefaultMarketsTokenRepository(
} }
} }
override suspend fun getMaxApy(): Flow<BigDecimal?> { override suspend fun showYieldModePromo(
return maxApyStore.get() appCurrency: AppCurrency,
interval: TokenMarketListConfig.Interval,
): Boolean = try {
val hasYieldSupplyTokens = marketsApi.getCoinsList(
currency = appCurrency.code,
interval = interval.toRequestParam(),
order = TokenMarketListConfig.Order.YieldSupply.toRequestParam(),
offset = 0,
limit = 40,
timestamp = null,
search = null,
).getOrThrow().tokens.isNotEmpty()
hasYieldSupplyTokens
} catch (error: Exception) {
Timber.e(error)
false
} }
inline fun <T> catchListErrorAndSendEvent(block: () -> T): T { inline fun <T> catchListErrorAndSendEvent(block: () -> T): T {

View file

@ -16,6 +16,7 @@ internal fun TokenMarketListConfig.Order.toRequestParam(): String = when (this)
TokenMarketListConfig.Order.TopGainers -> "gainers" TokenMarketListConfig.Order.TopGainers -> "gainers"
TokenMarketListConfig.Order.TopLosers -> "losers" TokenMarketListConfig.Order.TopLosers -> "losers"
TokenMarketListConfig.Order.Staking -> "staking" TokenMarketListConfig.Order.Staking -> "staking"
TokenMarketListConfig.Order.YieldSupply -> "yield"
} }
internal fun PriceChangeInterval.toRequestParam(): String = when (this) { internal fun PriceChangeInterval.toRequestParam(): String = when (this) {

View file

@ -40,7 +40,7 @@ internal object TokenMarketListConverter : Converter<TokenMarketListResponse, To
monthChangePercent = token.priceChangePercentage?.day30?.movePointLeft(2), monthChangePercent = token.priceChangePercentage?.day30?.movePointLeft(2),
), ),
tokenCharts = TokenMarket.Charts(h24 = null, week = null, month = null), tokenCharts = TokenMarket.Charts(h24 = null, week = null, month = null),
stakingRate = stakingRate, yieldRate = stakingRate ?: token.maxYieldApy?.movePointLeft(2),
updateTimestamp = value.timestamp, updateTimestamp = value.timestamp,
) )
} }

View file

@ -42,7 +42,6 @@ internal object MarketsDataModule {
cacheRegistry = cacheRegistry, cacheRegistry = cacheRegistry,
tokenExchangesStore = RuntimeStateStore(defaultValue = emptyList()), tokenExchangesStore = RuntimeStateStore(defaultValue = emptyList()),
excludedBlockchains = excludedBlockchains, excludedBlockchains = excludedBlockchains,
maxApyStore = RuntimeStateStore(defaultValue = null),
networkFactory = networkFactory, networkFactory = networkFactory,
) )
} }

View file

@ -90,16 +90,16 @@ internal class DefaultPromoRepository(
appPreferencesStore.store(PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name), false) appPreferencesStore.store(PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name), false)
} }
override fun isMarketsStakingNotificationHideClicked(): Flow<Boolean> { override fun isMarketsYieldSupplyNotificationHideClicked(): Flow<Boolean> {
return appPreferencesStore.get( return appPreferencesStore.get(
key = PreferencesKeys.MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY, key = PreferencesKeys.MARKETS_YIELD_SUPPLY_NOTIFICATION_HIDE_CLICKED_KEY,
default = false, default = false,
) )
} }
override suspend fun setMarketsStakingNotificationHideClicked() { override suspend fun setMarketsYieldSupplyNotificationHideClicked() {
appPreferencesStore.store( appPreferencesStore.store(
key = PreferencesKeys.MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY, key = PreferencesKeys.MARKETS_YIELD_SUPPLY_NOTIFICATION_HIDE_CLICKED_KEY,
value = true, value = true,
) )
} }

View file

@ -12,7 +12,7 @@ data class TokenMarket(
val isUnderMarketCapLimit: Boolean, val isUnderMarketCapLimit: Boolean,
val tokenQuotesShort: TokenQuotesShort, val tokenQuotesShort: TokenQuotesShort,
val tokenCharts: Charts, val tokenCharts: Charts,
val stakingRate: BigDecimal?, val yieldRate: BigDecimal?,
val updateTimestamp: Long?, val updateTimestamp: Long?,
private val imageHost: String, private val imageHost: String,
) { ) {

View file

@ -8,7 +8,7 @@ data class TokenMarketListConfig(
) { ) {
enum class Order { enum class Order {
ByRating, Trending, Buyers, TopGainers, TopLosers, Staking ByRating, Trending, Buyers, TopGainers, TopLosers, Staking, YieldSupply,
} }
enum class Interval { enum class Interval {

View file

@ -1,39 +0,0 @@
package com.tangem.domain.markets
import android.icu.util.Calendar
import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import java.math.BigDecimal
class GetStakingNotificationMaxApyUseCase(
private val settingsRepository: SettingsRepository,
private val promoRepository: PromoRepository,
private val marketsTokenRepository: MarketsTokenRepository,
) {
suspend operator fun invoke(): Flow<BigDecimal?> {
val hideClickedFlow = promoRepository.isMarketsStakingNotificationHideClicked()
val walletFirstUsageDate = settingsRepository.getWalletFirstUsageDate()
val currentDate = Calendar.getInstance().timeInMillis
return combine(
flow = hideClickedFlow,
flow2 = marketsTokenRepository.getMaxApy(),
) { hideClicked, maxApy ->
val showStakingNotification = if (!hideClicked && walletFirstUsageDate != 0L) {
currentDate - walletFirstUsageDate > TWO_WEEKS_IN_MILLIS
} else {
false
}
maxApy.takeIf { showStakingNotification }
}
}
private companion object {
const val TWO_WEEKS_IN_MILLIS = 14 * 24 * 60 * 60 * 1000L
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.domain.markets
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.repositories.MarketsTokenRepository
import com.tangem.domain.promo.PromoRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class ShouldShowYieldModeMarketPromoUseCase(
private val promoRepository: PromoRepository,
private val marketsTokenRepository: MarketsTokenRepository,
) {
operator fun invoke(appCurrency: AppCurrency, interval: TokenMarketListConfig.Interval): Flow<Boolean> {
val hideClickedFlow = promoRepository.isMarketsYieldSupplyNotificationHideClicked()
return hideClickedFlow.map { hideClicked ->
!hideClicked && marketsTokenRepository.showYieldModePromo(
appCurrency = appCurrency,
interval = interval,
)
}
}
}

View file

@ -1,11 +1,10 @@
package com.tangem.domain.markets.repositories package com.tangem.domain.markets.repositories
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.* import com.tangem.domain.markets.*
import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.coroutines.flow.Flow
import java.math.BigDecimal
interface MarketsTokenRepository { interface MarketsTokenRepository {
@ -56,5 +55,5 @@ interface MarketsTokenRepository {
*/ */
suspend fun getTokenExchanges(tokenId: CryptoCurrency.RawID): List<TokenMarketExchange> suspend fun getTokenExchanges(tokenId: CryptoCurrency.RawID): List<TokenMarketExchange>
suspend fun getMaxApy(): Flow<BigDecimal?> suspend fun showYieldModePromo(appCurrency: AppCurrency, interval: TokenMarketListConfig.Interval): Boolean
} }

View file

@ -16,9 +16,9 @@ interface PromoRepository {
suspend fun setNeverToShowTokenPromo(promoId: PromoId) suspend fun setNeverToShowTokenPromo(promoId: PromoId)
fun isMarketsStakingNotificationHideClicked(): Flow<Boolean> fun isMarketsYieldSupplyNotificationHideClicked(): Flow<Boolean>
suspend fun setMarketsStakingNotificationHideClicked() suspend fun setMarketsYieldSupplyNotificationHideClicked()
suspend fun isMoonpayPromoActive(): Boolean suspend fun isMoonpayPromoActive(): Boolean
// endregion // endregion

View file

@ -38,7 +38,7 @@ internal class MarketsTokenItemConverter(
trendType = value.getTrendType(), trendType = value.getTrendType(),
chartData = value.getChartData(), chartData = value.getChartData(),
isUnder100kMarketCap = value.isUnderMarketCapLimit, isUnder100kMarketCap = value.isUnderMarketCapLimit,
stakingRate = value.stakingRate?.format { percent() }?.let { stakingRate = value.yieldRate?.format { percent() }?.let {
resourceReference(R.string.markets_apy_placeholder, wrappedList(it)) resourceReference(R.string.markets_apy_placeholder, wrappedList(it))
}, },
updateTimestamp = value.updateTimestamp, updateTimestamp = value.updateTimestamp,

View file

@ -338,6 +338,7 @@ internal class FeedComponentModel @Inject constructor(
SortByTypeUM.TopGainers -> TokenMarketListConfig.Order.TopGainers SortByTypeUM.TopGainers -> TokenMarketListConfig.Order.TopGainers
SortByTypeUM.TopLosers -> TokenMarketListConfig.Order.TopLosers SortByTypeUM.TopLosers -> TokenMarketListConfig.Order.TopLosers
SortByTypeUM.Staking -> TokenMarketListConfig.Order.Staking SortByTypeUM.Staking -> TokenMarketListConfig.Order.Staking
SortByTypeUM.YieldSupply -> TokenMarketListConfig.Order.YieldSupply
} }
} }

View file

@ -386,6 +386,7 @@ internal class FeedMarketsBatchFlowManager(
TokenMarketListConfig.Order.TopGainers -> SortByTypeUM.TopGainers TokenMarketListConfig.Order.TopGainers -> SortByTypeUM.TopGainers
TokenMarketListConfig.Order.TopLosers -> SortByTypeUM.TopLosers TokenMarketListConfig.Order.TopLosers -> SortByTypeUM.TopLosers
TokenMarketListConfig.Order.Staking -> SortByTypeUM.Staking TokenMarketListConfig.Order.Staking -> SortByTypeUM.Staking
TokenMarketListConfig.Order.YieldSupply -> SortByTypeUM.YieldSupply
} }
} }
} }

View file

@ -41,6 +41,7 @@ enum class SortByTypeUM(val text: TextReference) {
TopGainers(resourceReference(R.string.markets_sort_by_top_gainers_title)), TopGainers(resourceReference(R.string.markets_sort_by_top_gainers_title)),
TopLosers(resourceReference(R.string.markets_sort_by_top_losers_title)), TopLosers(resourceReference(R.string.markets_sort_by_top_losers_title)),
Staking(resourceReference(R.string.common_staking)), Staking(resourceReference(R.string.common_staking)),
YieldSupply(resourceReference(R.string.markets_sort_by_yield_mode_title)),
} }
@Immutable @Immutable

View file

@ -24,6 +24,7 @@ internal sealed class MarketsListAnalyticsEvent(
SortByTypeUM.TopGainers -> "Gainers" SortByTypeUM.TopGainers -> "Gainers"
SortByTypeUM.TopLosers -> "Losers" SortByTypeUM.TopLosers -> "Losers"
SortByTypeUM.Staking -> "Staking" SortByTypeUM.Staking -> "Staking"
SortByTypeUM.YieldSupply -> "Yield Supply"
}, },
"Period" to when (interval) { "Period" to when (interval) {
MarketsListUM.TrendInterval.H24 -> "24h" MarketsListUM.TrendInterval.H24 -> "24h"
@ -32,12 +33,11 @@ internal sealed class MarketsListAnalyticsEvent(
}, },
), ),
) )
class YieldModePromoShown : MarketsListAnalyticsEvent(event = "Notice - Yield Mode Promo")
class StakingPromoShown : MarketsListAnalyticsEvent(event = "Notice - Staking Promo") class YieldModePromoClosed : MarketsListAnalyticsEvent(event = "Yield Mode Promo Closed")
class StakingPromoClosed : MarketsListAnalyticsEvent(event = "Staking Promo Closed") class YieldModeMoreInfoClicked : MarketsListAnalyticsEvent(event = "Yield Mode More Info")
class StakingMoreInfoClicked : MarketsListAnalyticsEvent(event = "Staking More Info")
data class TokenSearched(val tokenFound: Boolean) : MarketsListAnalyticsEvent( data class TokenSearched(val tokenFound: Boolean) : MarketsListAnalyticsEvent(
event = "Token Searched", event = "Token Searched",

View file

@ -10,19 +10,20 @@ import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase
import com.tangem.domain.markets.GetStakingNotificationMaxApyUseCase import com.tangem.domain.markets.ShouldShowYieldModeMarketPromoUseCase
import com.tangem.domain.markets.TokenMarket import com.tangem.domain.markets.TokenMarket
import com.tangem.domain.markets.TokenMarketListConfig
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.promo.PromoRepository import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.UserCountryError import com.tangem.domain.settings.usercountry.models.UserCountryError
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.features.markets.tokenlist.impl.analytics.MarketsListAnalyticsEvent import com.tangem.features.markets.tokenlist.impl.analytics.MarketsListAnalyticsEvent
import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListBatchFlowManager import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListBatchFlowManager
import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListUMStateManager import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListUMStateManager
import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM
import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM
import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM.TrendInterval
import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM
import com.tangem.utils.Provider import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -31,7 +32,6 @@ import com.tangem.utils.coroutines.saveIn
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import java.math.BigDecimal
import javax.inject.Inject import javax.inject.Inject
private const val UPDATE_QUOTES_TIMER_MILLIS = 60000L private const val UPDATE_QUOTES_TIMER_MILLIS = 60000L
@ -45,7 +45,7 @@ internal class MarketsListModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase, getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
getStakingNotificationMaxApyUseCase: GetStakingNotificationMaxApyUseCase, shouldShowYieldModeMarketPromoUseCase: ShouldShowYieldModeMarketPromoUseCase,
private val promoRepository: PromoRepository, private val promoRepository: PromoRepository,
private val getUserCountryUseCase: GetUserCountryUseCase, private val getUserCountryUseCase: GetUserCountryUseCase,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
@ -69,8 +69,6 @@ internal class MarketsListModel @Inject constructor(
visibleItemsChanged = { visibleItemIds.value = it }, visibleItemsChanged = { visibleItemIds.value = it },
onRetryButtonClicked = { activeListManager.reload() }, onRetryButtonClicked = { activeListManager.reload() },
onTokenClick = { onTokenUIClicked(it) }, onTokenClick = { onTokenUIClicked(it) },
onStakingNotificationClick = { analyticsEventHandler.send(MarketsListAnalyticsEvent.StakingMoreInfoClicked()) },
onStakingNotificationCloseClick = { onStakingNotificationCloseClick() },
onShowTokensUnder100kClicked = { analyticsEventHandler.send(MarketsListAnalyticsEvent.ShowTokens()) }, onShowTokensUnder100kClicked = { analyticsEventHandler.send(MarketsListAnalyticsEvent.ShowTokens()) },
) )
@ -112,53 +110,62 @@ internal class MarketsListModel @Inject constructor(
marketsListUMStateManager.isInSearchStateFlow.flatMapLatest { isInSearchMode -> marketsListUMStateManager.isInSearchStateFlow.flatMapLatest { isInSearchMode ->
if (isInSearchMode) { if (isInSearchMode) {
combine( combine(
searchMarketsListManager.uiItems, flow = searchMarketsListManager.uiItems,
searchMarketsListManager.isInInitialLoadingErrorState, flow2 = searchMarketsListManager.isInInitialLoadingErrorState,
searchMarketsListManager.isSearchNotFoundState, flow3 = searchMarketsListManager.isSearchNotFoundState,
getStakingNotificationMaxApyUseCase(), flow4 = shouldShowYieldModeMarketPromoUseCase(
getUserCountryUseCase.invoke(), appCurrency = currentAppCurrency.value,
) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, stakingMaxApy, userCountry -> interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(),
),
flow5 = getUserCountryUseCase.invoke(),
) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, isYieldModePromo, userCountry ->
MarketsItemsData( MarketsItemsData(
items = uiItems, items = uiItems,
isInErrorState = isInInitialLoadingErrorState, isInErrorState = isInInitialLoadingErrorState,
isSearchNotFound = isSearchNotFoundState, isSearchNotFound = isSearchNotFoundState,
stakingNotificationMaxApy = stakingMaxApy, shouldShowYieldModePromo = isYieldModePromo,
userCountry = userCountry, userCountry = userCountry,
) )
} }
} else { } else {
combine( combine(
mainMarketsListManager.uiItems, flow = mainMarketsListManager.uiItems,
mainMarketsListManager.isInInitialLoadingErrorState, flow2 = mainMarketsListManager.isInInitialLoadingErrorState,
getStakingNotificationMaxApyUseCase(), flow3 = shouldShowYieldModeMarketPromoUseCase(
getUserCountryUseCase.invoke(), appCurrency = currentAppCurrency.value,
) { uiItems, isInInitialLoadingErrorState, stakingNotificationMaxApy, userCountry -> interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(),
),
flow4 = getUserCountryUseCase.invoke(),
) { uiItems, isInInitialLoadingErrorState, shouldShowYieldModePromo, userCountry ->
MarketsItemsData( MarketsItemsData(
items = uiItems, items = uiItems,
isInErrorState = isInInitialLoadingErrorState, isInErrorState = isInInitialLoadingErrorState,
isSearchNotFound = false, isSearchNotFound = false,
stakingNotificationMaxApy = stakingNotificationMaxApy, shouldShowYieldModePromo = shouldShowYieldModePromo,
userCountry = userCountry, userCountry = userCountry,
) )
} }
} }
}.collect { marketsItemsData -> }.collect { marketsItemsData ->
val stakingNotificationMaxApy = marketsItemsData.stakingNotificationMaxApy?.takeUnless { val shouldShowYieldModePromo = marketsItemsData.shouldShowYieldModePromo
marketsItemsData.userCountry.getOrNull().needApplyFCARestrictions() if (marketsListUMStateManager.state.value.marketsNotificationUM == null && shouldShowYieldModePromo) {
} analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModePromoShown())
if (marketsListUMStateManager.state.value.stakingNotificationMaxApy == null &&
stakingNotificationMaxApy != null
) {
analyticsEventHandler.send(MarketsListAnalyticsEvent.StakingPromoShown())
} }
marketsListUMStateManager.onUiItemsChanged( marketsListUMStateManager.onUiItemsChanged(
uiItems = marketsItemsData.items, uiItems = marketsItemsData.items,
isInErrorState = marketsItemsData.isInErrorState, isInErrorState = marketsItemsData.isInErrorState,
isSearchNotFound = marketsItemsData.isSearchNotFound, isSearchNotFound = marketsItemsData.isSearchNotFound,
stakingNotificationMaxApy = marketsItemsData.stakingNotificationMaxApy?.takeUnless { marketsNotificationUM = if (shouldShowYieldModePromo) {
marketsItemsData.userCountry.getOrNull().needApplyFCARestrictions() MarketsNotificationUM.YieldSupplyPromo(
onClick = {
analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModeMoreInfoClicked())
marketsListUMStateManager.selectedSortByType = SortByTypeUM.YieldSupply
},
onCloseClick = { onYieldModeNotificationCloseClick() },
)
} else {
null
}, },
) )
} }
@ -266,6 +273,14 @@ internal class MarketsListModel @Inject constructor(
mainMarketsListManager.reload() mainMarketsListManager.reload()
} }
fun TrendInterval.toBatchRequestInterval(): TokenMarketListConfig.Interval {
return when (this) {
TrendInterval.H24 -> TokenMarketListConfig.Interval.H24
TrendInterval.D7 -> TokenMarketListConfig.Interval.WEEK
TrendInterval.M1 -> TokenMarketListConfig.Interval.MONTH
}
}
private fun initAnalytics() { private fun initAnalytics() {
containerBottomSheetState.onEach { bottomSheetState -> containerBottomSheetState.onEach { bottomSheetState ->
if (bottomSheetState == BottomSheetState.EXPANDED) { if (bottomSheetState == BottomSheetState.EXPANDED) {
@ -302,10 +317,10 @@ internal class MarketsListModel @Inject constructor(
}.saveIn(updateQuotesJob) }.saveIn(updateQuotesJob)
} }
private fun onStakingNotificationCloseClick() { private fun onYieldModeNotificationCloseClick() {
analyticsEventHandler.send(MarketsListAnalyticsEvent.StakingPromoClosed()) analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModePromoClosed())
modelScope.launch { modelScope.launch {
promoRepository.setMarketsStakingNotificationHideClicked() promoRepository.setMarketsYieldSupplyNotificationHideClicked()
} }
} }
@ -313,7 +328,7 @@ internal class MarketsListModel @Inject constructor(
val items: ImmutableList<MarketsListItemUM>, val items: ImmutableList<MarketsListItemUM>,
val isInErrorState: Boolean, val isInErrorState: Boolean,
val isSearchNotFound: Boolean, val isSearchNotFound: Boolean,
val stakingNotificationMaxApy: BigDecimal?, val shouldShowYieldModePromo: Boolean,
val userCountry: Either<UserCountryError, UserCountry>, val userCountry: Either<UserCountryError, UserCountry>,
) )
} }

View file

@ -0,0 +1,22 @@
package com.tangem.features.markets.tokenlist.impl.model
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.markets.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

@ -38,7 +38,7 @@ internal class MarketsTokenItemConverter(
trendType = value.getTrendType(), trendType = value.getTrendType(),
chartData = value.getChartData(), chartData = value.getChartData(),
isUnder100kMarketCap = value.isUnderMarketCapLimit, isUnder100kMarketCap = value.isUnderMarketCapLimit,
stakingRate = value.stakingRate?.format { percent() }?.let { stakingRate = value.yieldRate?.format { percent() }?.let {
resourceReference(R.string.markets_apy_placeholder, wrappedList(it)) resourceReference(R.string.markets_apy_placeholder, wrappedList(it))
}, },
updateTimestamp = value.updateTimestamp, updateTimestamp = value.updateTimestamp,

View file

@ -343,6 +343,7 @@ internal class MarketsListBatchFlowManager(
SortByTypeUM.TopGainers -> TokenMarketListConfig.Order.TopGainers SortByTypeUM.TopGainers -> TokenMarketListConfig.Order.TopGainers
SortByTypeUM.TopLosers -> TokenMarketListConfig.Order.TopLosers SortByTypeUM.TopLosers -> TokenMarketListConfig.Order.TopLosers
SortByTypeUM.Staking -> TokenMarketListConfig.Order.Staking SortByTypeUM.Staking -> TokenMarketListConfig.Order.Staking
SortByTypeUM.YieldSupply -> TokenMarketListConfig.Order.YieldSupply
} }
} }

View file

@ -8,6 +8,7 @@ import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.markets.impl.R import com.tangem.features.markets.impl.R
import com.tangem.features.markets.tokenlist.impl.model.MarketsNotificationUM
import com.tangem.features.markets.tokenlist.impl.ui.state.* import com.tangem.features.markets.tokenlist.impl.ui.state.*
import com.tangem.utils.Provider import com.tangem.utils.Provider
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@ -16,7 +17,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import java.math.BigDecimal
@Stable @Stable
@Suppress("LongParameterList") @Suppress("LongParameterList")
@ -26,8 +26,6 @@ internal class MarketsListUMStateManager(
private val visibleItemsChanged: (itemsKeys: List<CryptoCurrency.RawID>) -> Unit, private val visibleItemsChanged: (itemsKeys: List<CryptoCurrency.RawID>) -> Unit,
private val onRetryButtonClicked: () -> Unit, private val onRetryButtonClicked: () -> Unit,
private val onTokenClick: (MarketsListItemUM) -> Unit, private val onTokenClick: (MarketsListItemUM) -> Unit,
private val onStakingNotificationClick: () -> Unit,
private val onStakingNotificationCloseClick: () -> Unit,
private val onShowTokensUnder100kClicked: () -> Unit, private val onShowTokensUnder100kClicked: () -> Unit,
) { ) {
@ -92,25 +90,25 @@ internal class MarketsListUMStateManager(
isInErrorState: Boolean, isInErrorState: Boolean,
isSearchNotFound: Boolean, isSearchNotFound: Boolean,
uiItems: ImmutableList<MarketsListItemUM>, uiItems: ImmutableList<MarketsListItemUM>,
stakingNotificationMaxApy: BigDecimal?, marketsNotificationUM: MarketsNotificationUM?,
) { ) {
state.update { state.update { currentState ->
when { when {
isInErrorState -> { isInErrorState -> {
it.copy( currentState.copy(
list = ListUM.LoadingError(onRetryClicked = onRetryButtonClicked), list = ListUM.LoadingError(onRetryClicked = onRetryButtonClicked),
) )
} }
isSearchNotFound -> { isSearchNotFound -> {
it.copy(list = ListUM.SearchNothingFound) currentState.copy(list = ListUM.SearchNothingFound)
} }
uiItems.isEmpty() -> { uiItems.isEmpty() -> {
it.copy(list = ListUM.Loading) currentState.copy(list = ListUM.Loading)
} }
else -> { else -> {
it.updateItems( currentState.updateItems(
newItems = uiItems, newItems = uiItems,
stakingNotificationMaxApy = stakingNotificationMaxApy, marketsNotificationUM = marketsNotificationUM,
) )
} }
} }
@ -119,7 +117,7 @@ internal class MarketsListUMStateManager(
private fun MarketsListUM.updateItems( private fun MarketsListUM.updateItems(
newItems: ImmutableList<MarketsListItemUM>, newItems: ImmutableList<MarketsListItemUM>,
stakingNotificationMaxApy: BigDecimal?, marketsNotificationUM: MarketsNotificationUM?,
): MarketsListUM { ): MarketsListUM {
val currentState = this val currentState = this
@ -131,7 +129,7 @@ internal class MarketsListUMStateManager(
.copy( .copy(
showUnder100kTokensNotificationWasHidden = currentState.showUnder100kButtonAlreadyPressed(), showUnder100kTokensNotificationWasHidden = currentState.showUnder100kButtonAlreadyPressed(),
), ),
stakingNotificationMaxApy = stakingNotificationMaxApy, marketsNotificationUM = marketsNotificationUM,
) )
} }
@ -225,12 +223,7 @@ internal class MarketsListUMStateManager(
onOptionClicked = ::onBottomSheetOptionClicked, onOptionClicked = ::onBottomSheetOptionClicked,
), ),
), ),
stakingNotificationMaxApy = null, marketsNotificationUM = null,
onStakingNotificationClick = {
onStakingNotificationClick()
selectedSortByType = SortByTypeUM.Staking
},
onStakingNotificationCloseClick = onStakingNotificationCloseClick,
) )
private fun onBottomSheetOptionClicked(sortByTypeUM: SortByTypeUM) { private fun onBottomSheetOptionClicked(sortByTypeUM: SortByTypeUM) {

View file

@ -22,7 +22,6 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -38,19 +37,17 @@ import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
import com.tangem.core.ui.components.fields.SearchBar import com.tangem.core.ui.components.fields.SearchBar
import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.components.keyboardAsState
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.* import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.percent
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.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.markets.impl.R import com.tangem.features.markets.impl.R
import com.tangem.features.markets.tokenlist.impl.model.MarketsNotificationUM
import com.tangem.features.markets.tokenlist.impl.ui.components.MarketsListLazyColumn import com.tangem.features.markets.tokenlist.impl.ui.components.MarketsListLazyColumn
import com.tangem.features.markets.tokenlist.impl.ui.components.MarketsListSortByBottomSheet import com.tangem.features.markets.tokenlist.impl.ui.components.MarketsListSortByBottomSheet
import com.tangem.features.markets.tokenlist.impl.ui.components.StakingInMarketsPromoNotification import com.tangem.features.markets.tokenlist.impl.ui.components.YieldSupplyInMarketsPromoNotification
import com.tangem.features.markets.tokenlist.impl.ui.preview.MarketChartListItemPreviewDataProvider import com.tangem.features.markets.tokenlist.impl.ui.preview.MarketChartListItemPreviewDataProvider
import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM
import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM
@ -58,7 +55,6 @@ import com.tangem.features.markets.tokenlist.impl.ui.state.SortByBottomSheetCont
import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import java.math.BigDecimal
private const val SHOW_MORE_KEY = "privacyPolicy" private const val SHOW_MORE_KEY = "privacyPolicy"
@ -180,18 +176,21 @@ private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modif
) )
} }
val marketsNotification = state.marketsNotificationUM
AnimatedVisibility( AnimatedVisibility(
state.isInSearchMode.not() && state.isInSearchMode.not() &&
state.stakingNotificationMaxApy != null && state.selectedSortBy != SortByTypeUM.YieldSupply,
state.selectedSortBy != SortByTypeUM.Staking,
) { ) {
val showMore = stringResourceSafe(R.string.common_show_more) val showMore = stringResourceSafe(R.string.common_show_more)
when (marketsNotification) {
is MarketsNotificationUM.YieldSupplyPromo -> {
val description = stringResourceSafe( val description = stringResourceSafe(
R.string.markets_staking_banner_description_placeholder, R.string.markets_yield_supply_banner_description,
showMore, showMore,
) )
val clickableDescription = buildAnnotatedString { val clickableDescription = annotatedReference {
append(description.substringBefore(showMore)) append(description.substringBefore(showMore))
pushStringAnnotation(SHOW_MORE_KEY, "") pushStringAnnotation(SHOW_MORE_KEY, "")
@ -199,20 +198,17 @@ private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modif
pop() pop()
} }
StakingInMarketsPromoNotification( YieldSupplyInMarketsPromoNotification(
config = NotificationConfig( config = marketsNotification.config.copy(
iconResId = R.drawable.img_staking_in_market_notification, subtitle = clickableDescription,
title = resourceReference(
R.string.markets_staking_banner_title,
wrappedList(state.stakingNotificationMaxApy.format { percent() }),
),
subtitle = annotatedReference(clickableDescription),
onClick = state.onStakingNotificationClick,
onCloseClick = state.onStakingNotificationCloseClick,
), ),
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
) )
} }
else -> { /* no-op */
}
}
}
} }
} }
val strokeWidth = TangemTheme.dimens.size0_5 val strokeWidth = TangemTheme.dimens.size0_5
@ -420,9 +416,10 @@ private fun Preview() {
onDismissRequest = {}, onDismissRequest = {},
content = SortByBottomSheetContentUM(selectedOption = SortByTypeUM.Rating) {}, content = SortByBottomSheetContentUM(selectedOption = SortByTypeUM.Rating) {},
), ),
stakingNotificationMaxApy = BigDecimal(0.12345), marketsNotificationUM = MarketsNotificationUM.YieldSupplyPromo(
onStakingNotificationClick = {}, onClick = {},
onStakingNotificationCloseClick = {}, onCloseClick = {},
),
), ),
onHeaderSizeChange = {}, onHeaderSizeChange = {},
bottomSheetState = BottomSheetState.EXPANDED, bottomSheetState = BottomSheetState.EXPANDED,

View file

@ -0,0 +1,147 @@
package com.tangem.features.markets.tokenlist.impl.ui.components
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.notifications.CloseableIconButton
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
private val bgColor = Color(0x2684B0D7)
private val borderColor = Color(0x2684B0D7)
@Composable
fun YieldSupplyInMarketsPromoNotification(config: NotificationConfig, modifier: Modifier = Modifier) {
var textHeightDp by remember { mutableStateOf(0.dp) }
Box(
modifier = modifier
.fillMaxWidth()
.border(
width = 1.dp,
shape = TangemTheme.shapes.roundedCornersXMedium,
color = borderColor,
)
.clip(shape = TangemTheme.shapes.roundedCornersXMedium)
.background(bgColor)
.clickable { config.onClick?.invoke() },
) {
PromoImage(
iconRes = config.iconResId,
modifier = Modifier.height(textHeightDp),
)
PromoText(
title = config.title,
subtitle = config.subtitle,
onSizeChange = { textHeightDp = it },
)
CloseableIconButton(
onClick = config.onCloseClick,
modifier = Modifier.align(alignment = Alignment.TopEnd),
iconTint = TangemTheme.colors.icon.secondary,
)
}
}
@Composable
private fun PromoImage(@DrawableRes iconRes: Int, modifier: Modifier = Modifier) {
Box(
modifier = modifier.padding(vertical = 8.dp),
contentAlignment = Alignment.Center,
) {
Image(
painter = painterResource(id = iconRes),
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier
.requiredWidth(80.dp)
.wrapContentHeight(Alignment.CenterVertically, unbounded = true),
)
}
}
@Composable
private fun PromoText(title: TextReference?, subtitle: TextReference, onSizeChange: (Dp) -> Unit) {
val density = LocalDensity.current
Box(
modifier = Modifier.onSizeChanged {
with(density) { onSizeChange(it.height.toDp()) }
},
) {
TextsBlock(
title = title,
subtitle = subtitle,
modifier = Modifier
.wrapContentHeight()
.align(Alignment.CenterStart)
.padding(start = 80.dp, top = 12.dp, end = 12.dp, bottom = 12.dp),
)
}
}
@Composable
private fun TextsBlock(title: TextReference?, subtitle: TextReference, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
val titleText = title?.resolveReference()
if (titleText != null) {
Text(
text = titleText,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.button,
)
SpacerH(height = TangemTheme.dimens.spacing2)
}
Text(
text = subtitle.resolveAnnotatedReference(),
color = TangemTheme.colors.text.secondary,
style = TangemTheme.typography.caption2,
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun RingPromoNotification_Preview() {
TangemThemePreview {
YieldSupplyInMarketsPromoNotification(
config = NotificationConfig(
title = stringReference("Activate Yield Mode"),
subtitle = stringReference("Power up your assets while supplying them with instant access. Show more"),
iconResId = R.drawable.img_yield_supply_in_market_notification,
onCloseClick = { },
),
)
}
}
// endregion

View file

@ -8,8 +8,8 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.markets.impl.R import com.tangem.features.markets.impl.R
import com.tangem.features.markets.tokenlist.impl.model.MarketsNotificationUM
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal
internal data class MarketsListUM( internal data class MarketsListUM(
val list: ListUM, val list: ListUM,
@ -19,9 +19,7 @@ internal data class MarketsListUM(
val selectedInterval: TrendInterval, val selectedInterval: TrendInterval,
val onIntervalClick: (TrendInterval) -> Unit, val onIntervalClick: (TrendInterval) -> Unit,
val onSortByButtonClick: () -> Unit, val onSortByButtonClick: () -> Unit,
val stakingNotificationMaxApy: BigDecimal?, val marketsNotificationUM: MarketsNotificationUM?,
val onStakingNotificationClick: () -> Unit,
val onStakingNotificationCloseClick: () -> Unit,
) { ) {
val isInSearchMode val isInSearchMode
get() = searchBar.isActive get() = searchBar.isActive
@ -40,6 +38,7 @@ enum class SortByTypeUM(val text: TextReference) {
TopGainers(resourceReference(R.string.markets_sort_by_top_gainers_title)), TopGainers(resourceReference(R.string.markets_sort_by_top_gainers_title)),
TopLosers(resourceReference(R.string.markets_sort_by_top_losers_title)), TopLosers(resourceReference(R.string.markets_sort_by_top_losers_title)),
Staking(resourceReference(R.string.common_staking)), Staking(resourceReference(R.string.common_staking)),
YieldSupply(resourceReference(R.string.yield_module_earn_sheet_title)),
} }
@Immutable @Immutable