Updated on 2026-08-14
This commit is contained in:
parent
8009743fb9
commit
a72837204d
35 changed files with 628 additions and 132 deletions
|
|
@ -5,8 +5,10 @@ import com.tangem.domain.card.repository.DerivationsRepository
|
||||||
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.networks.multi.MultiNetworkStatusFetcher
|
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||||
|
import com.tangem.domain.promo.PromoRepository
|
||||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
||||||
|
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||||
import com.tangem.domain.staking.repositories.StakingRepository
|
import com.tangem.domain.staking.repositories.StakingRepository
|
||||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||||
|
|
@ -109,4 +111,18 @@ object MarketsDomainModule {
|
||||||
fun provideGetTokenExchangesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenExchangesUseCase {
|
fun provideGetTokenExchangesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenExchangesUseCase {
|
||||||
return GetTokenExchangesUseCase(marketsTokenRepository = marketsTokenRepository)
|
return GetTokenExchangesUseCase(marketsTokenRepository = marketsTokenRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetStakingNotificationMaxApyUseCase(
|
||||||
|
settingsRepository: SettingsRepository,
|
||||||
|
promoRepository: PromoRepository,
|
||||||
|
marketsTokenRepository: MarketsTokenRepository,
|
||||||
|
): GetStakingNotificationMaxApyUseCase {
|
||||||
|
return GetStakingNotificationMaxApyUseCase(
|
||||||
|
settingsRepository = settingsRepository,
|
||||||
|
promoRepository = promoRepository,
|
||||||
|
marketsTokenRepository = marketsTokenRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,5 +226,13 @@ internal object SettingsDomainModule {
|
||||||
fun provideIsGooglePayAvailableUseCase(settingsRepository: SettingsRepository): IsGooglePayAvailableUseCase {
|
fun provideIsGooglePayAvailableUseCase(settingsRepository: SettingsRepository): IsGooglePayAvailableUseCase {
|
||||||
return IsGooglePayAvailableUseCase(settingsRepository)
|
return IsGooglePayAvailableUseCase(settingsRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideMaybeSetWalletFirstTimeUsageUseCase(
|
||||||
|
settingsRepository: SettingsRepository,
|
||||||
|
): SetWalletFirstTimeUsageUseCase {
|
||||||
|
return SetWalletFirstTimeUsageUseCase(settingsRepository)
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
@ -49,12 +49,12 @@ internal class HomeModel @Inject constructor(
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val scanCardProcessor: ScanCardProcessor,
|
private val scanCardProcessor: ScanCardProcessor,
|
||||||
private val saveWalletUseCase: SaveWalletUseCase,
|
private val saveWalletUseCase: SaveWalletUseCase,
|
||||||
private val getUserCountryUseCase: GetUserCountryUseCase,
|
|
||||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val urlOpener: UrlOpener,
|
private val urlOpener: UrlOpener,
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
|
||||||
|
getUserCountryUseCase: GetUserCountryUseCase,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val tangemErrorHandler = TangemTangemErrorsHandler(store)
|
private val tangemErrorHandler = TangemTangemErrorsHandler(store)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ data class TokenMarketListResponse(
|
||||||
@Json(name = "limit") val limit: Int,
|
@Json(name = "limit") val limit: Int,
|
||||||
@Json(name = "offset") val offset: Int,
|
@Json(name = "offset") val offset: Int,
|
||||||
@Json(name = "timestamp") val timestamp: Long? = null,
|
@Json(name = "timestamp") val timestamp: Long? = null,
|
||||||
|
@Json(name = "summary") val summary: Summary? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
|
@ -24,6 +25,7 @@ data class TokenMarketListResponse(
|
||||||
@Json(name = "market_rating") val marketRating: Int?,
|
@Json(name = "market_rating") val marketRating: Int?,
|
||||||
@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>?,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
|
@ -32,5 +34,27 @@ data class TokenMarketListResponse(
|
||||||
@Json(name = "1w") val week1: BigDecimal?,
|
@Json(name = "1w") val week1: BigDecimal?,
|
||||||
@Json(name = "30d") val day30: BigDecimal?,
|
@Json(name = "30d") val day30: BigDecimal?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class StakingOpportunities(
|
||||||
|
@Json(name = "id") val id: Int?,
|
||||||
|
@Json(name = "apy") val apy: BigDecimal?,
|
||||||
|
@Json(name = "network_id") val networkId: String?,
|
||||||
|
@Json(name = "reward_type") val rewardType: RewardType?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
|
enum class RewardType {
|
||||||
|
@Json(name = "apy") APY,
|
||||||
|
|
||||||
|
@Json(name = "apr") APR,
|
||||||
|
|
||||||
|
UNKNOWN,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Summary(
|
||||||
|
@Json(name = "max_apy") val maxApy: BigDecimal?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -86,6 +86,12 @@ 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 {
|
||||||
|
booleanPreferencesKey(name = "marketsStakingNotificationHideClicked")
|
||||||
|
}
|
||||||
|
|
||||||
|
val WALLET_FIRST_USAGE_DATE_KEY by lazy { longPreferencesKey(name = "walletFirstUsageDate") }
|
||||||
|
|
||||||
val IS_WALLET_NAMES_MIGRATION_DONE_KEY by lazy { booleanPreferencesKey(name = "isWalletNamesMigrationDone") }
|
val IS_WALLET_NAMES_MIGRATION_DONE_KEY by lazy { booleanPreferencesKey(name = "isWalletNamesMigrationDone") }
|
||||||
|
|
||||||
val UNSUBMITTED_TRANSACTIONS_KEY by lazy { stringPreferencesKey(name = "unsubmittedTransactions") }
|
val UNSUBMITTED_TRANSACTIONS_KEY by lazy { stringPreferencesKey(name = "unsubmittedTransactions") }
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,7 @@ private fun SecondaryPairButtons(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun CloseableIconButton(
|
fun CloseableIconButton(
|
||||||
onClick: (() -> Unit)?,
|
onClick: (() -> Unit)?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
isEnabled: Boolean = true,
|
isEnabled: Boolean = true,
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -37,6 +37,7 @@ dependencies {
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Others dependencies
|
// region Others dependencies
|
||||||
|
implementation(deps.androidx.datastore)
|
||||||
implementation(deps.kotlin.coroutines)
|
implementation(deps.kotlin.coroutines)
|
||||||
implementation(deps.jodatime)
|
implementation(deps.jodatime)
|
||||||
implementation(deps.moshi)
|
implementation(deps.moshi)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,9 @@ import com.tangem.domain.wallets.models.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 java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
|
|
@ -41,6 +43,7 @@ internal class DefaultMarketsTokenRepository(
|
||||||
private val excludedBlockchains: ExcludedBlockchains,
|
private val excludedBlockchains: ExcludedBlockchains,
|
||||||
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?>,
|
||||||
) : MarketsTokenRepository {
|
) : MarketsTokenRepository {
|
||||||
|
|
||||||
private val tokenMarketInfoConverter: TokenMarketInfoConverter = TokenMarketInfoConverter(excludedBlockchains)
|
private val tokenMarketInfoConverter: TokenMarketInfoConverter = TokenMarketInfoConverter(excludedBlockchains)
|
||||||
|
|
@ -88,8 +91,12 @@ internal class DefaultMarketsTokenRepository(
|
||||||
|
|
||||||
val last = res.tokens.size < request.limit
|
val last = res.tokens.size < request.limit
|
||||||
|
|
||||||
|
val tokenMarketListWithMaxApy = TokenMarketListConverter.convert(res)
|
||||||
|
|
||||||
|
maxApyStore.store(tokenMarketListWithMaxApy.maxApy)
|
||||||
|
|
||||||
return BatchFetchResult.Success(
|
return BatchFetchResult.Success(
|
||||||
data = TokenMarketListConverter.convert(res),
|
data = tokenMarketListWithMaxApy.tokens,
|
||||||
last = last,
|
last = last,
|
||||||
empty = res.tokens.isEmpty(),
|
empty = res.tokens.isEmpty(),
|
||||||
)
|
)
|
||||||
|
|
@ -275,6 +282,10 @@ internal class DefaultMarketsTokenRepository(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMaxApy(): Flow<BigDecimal?> {
|
||||||
|
return maxApyStore.get()
|
||||||
|
}
|
||||||
|
|
||||||
inline fun <T> catchListErrorAndSendEvent(block: () -> T): T {
|
inline fun <T> catchListErrorAndSendEvent(block: () -> T): T {
|
||||||
return catchErrorAndSendEvent(block, ::createListErrorEvent)
|
return catchErrorAndSendEvent(block, ::createListErrorEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ internal fun TokenMarketListConfig.Order.toRequestParam(): String = when (this)
|
||||||
TokenMarketListConfig.Order.Buyers -> "buyers"
|
TokenMarketListConfig.Order.Buyers -> "buyers"
|
||||||
TokenMarketListConfig.Order.TopGainers -> "gainers"
|
TokenMarketListConfig.Order.TopGainers -> "gainers"
|
||||||
TokenMarketListConfig.Order.TopLosers -> "losers"
|
TokenMarketListConfig.Order.TopLosers -> "losers"
|
||||||
|
TokenMarketListConfig.Order.Staking -> "staking"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun PriceChangeInterval.toRequestParam(): String = when (this) {
|
internal fun PriceChangeInterval.toRequestParam(): String = when (this) {
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,29 @@ package com.tangem.data.markets.converters
|
||||||
|
|
||||||
import com.tangem.datasource.api.markets.models.response.TokenMarketListResponse
|
import com.tangem.datasource.api.markets.models.response.TokenMarketListResponse
|
||||||
import com.tangem.domain.markets.TokenMarket
|
import com.tangem.domain.markets.TokenMarket
|
||||||
|
import com.tangem.domain.markets.TokenMarketListWithMaxApy
|
||||||
import com.tangem.domain.markets.TokenQuotesShort
|
import com.tangem.domain.markets.TokenQuotesShort
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
|
import com.tangem.utils.extensions.isPositive
|
||||||
|
|
||||||
internal object TokenMarketListConverter : Converter<TokenMarketListResponse, List<TokenMarket>> {
|
internal object TokenMarketListConverter : Converter<TokenMarketListResponse, TokenMarketListWithMaxApy> {
|
||||||
|
|
||||||
override fun convert(value: TokenMarketListResponse): List<TokenMarket> {
|
override fun convert(value: TokenMarketListResponse): TokenMarketListWithMaxApy {
|
||||||
val imageHost = value.imageHost ?: run {
|
val imageHost = value.imageHost ?: run {
|
||||||
if (value.tokens.isEmpty()) {
|
if (value.tokens.isEmpty()) {
|
||||||
return emptyList()
|
return TokenMarketListWithMaxApy(emptyList(), null)
|
||||||
} else {
|
} else {
|
||||||
error("imageHost cannot be null")
|
error("imageHost cannot be null")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return value.tokens.map { token ->
|
val tokens = value.tokens.map { token ->
|
||||||
|
val stakingRate = token.stakingOpportunities
|
||||||
|
?.mapNotNull { it.apy }
|
||||||
|
?.max()
|
||||||
|
.takeIf { it?.isPositive() == true }
|
||||||
|
|
||||||
TokenMarket(
|
TokenMarket(
|
||||||
id = CryptoCurrency.RawID(token.id),
|
id = CryptoCurrency.RawID(token.id),
|
||||||
name = token.name,
|
name = token.name,
|
||||||
|
|
@ -33,7 +40,9 @@ internal object TokenMarketListConverter : Converter<TokenMarketListResponse, Li
|
||||||
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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
return TokenMarketListWithMaxApy(tokens, value.summary?.maxApy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ internal object MarketsDataModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideMarketsRepository(
|
fun provideMarketsTokenRepository(
|
||||||
marketsApi: TangemTechMarketsApi,
|
marketsApi: TangemTechMarketsApi,
|
||||||
tangemTechApi: TangemTechApi,
|
tangemTechApi: TangemTechApi,
|
||||||
userWalletsStore: UserWalletsStore,
|
userWalletsStore: UserWalletsStore,
|
||||||
|
|
@ -40,6 +40,7 @@ internal object MarketsDataModule {
|
||||||
cacheRegistry = cacheRegistry,
|
cacheRegistry = cacheRegistry,
|
||||||
tokenExchangesStore = RuntimeStateStore(defaultValue = emptyList()),
|
tokenExchangesStore = RuntimeStateStore(defaultValue = emptyList()),
|
||||||
excludedBlockchains = excludedBlockchains,
|
excludedBlockchains = excludedBlockchains,
|
||||||
|
maxApyStore = RuntimeStateStore(defaultValue = null),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,6 +67,20 @@ internal class DefaultPromoRepository(
|
||||||
appPreferencesStore.store(PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name), false)
|
appPreferencesStore.store(PreferencesKeys.getShouldShowPromoKey(promoId = promoId.name), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun isMarketsStakingNotificationHideClicked(): Flow<Boolean> {
|
||||||
|
return appPreferencesStore.get(
|
||||||
|
key = PreferencesKeys.MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY,
|
||||||
|
default = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setMarketsStakingNotificationHideClicked() {
|
||||||
|
appPreferencesStore.store(
|
||||||
|
key = PreferencesKeys.MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY,
|
||||||
|
value = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getStoryById(id: String): Flow<StoryContent?> = isReadyToShowStories(id).mapLatest {
|
override fun getStoryById(id: String): Flow<StoryContent?> = isReadyToShowStories(id).mapLatest {
|
||||||
getStoryByIdSync(id = id, refresh = false)
|
getStoryByIdSync(id = id, refresh = false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,17 @@ internal class DefaultSettingsRepository(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getWalletFirstUsageDate(): Long {
|
||||||
|
return appPreferencesStore.getSyncOrDefault(
|
||||||
|
key = PreferencesKeys.WALLET_FIRST_USAGE_DATE_KEY,
|
||||||
|
default = 0L,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setWalletFirstUsageDate(value: Long) {
|
||||||
|
appPreferencesStore.store(key = PreferencesKeys.WALLET_FIRST_USAGE_DATE_KEY, value = value)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun shouldShowMarketsTooltip(): Boolean {
|
override suspend fun shouldShowMarketsTooltip(): Boolean {
|
||||||
return appPreferencesStore.getSyncOrDefault(
|
return appPreferencesStore.getSyncOrDefault(
|
||||||
key = PreferencesKeys.SHOULD_SHOW_MARKETS_TOOLTIP_KEY,
|
key = PreferencesKeys.SHOULD_SHOW_MARKETS_TOOLTIP_KEY,
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,11 @@ dependencies {
|
||||||
api(projects.domain.quotes)
|
api(projects.domain.quotes)
|
||||||
api(projects.domain.wallets)
|
api(projects.domain.wallets)
|
||||||
api(projects.domain.wallets.models)
|
api(projects.domain.wallets.models)
|
||||||
|
api(projects.domain.promo)
|
||||||
|
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.tokens)
|
implementation(projects.domain.tokens)
|
||||||
|
implementation(projects.domain.settings)
|
||||||
|
|
||||||
api(projects.core.pagination)
|
api(projects.core.pagination)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +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?,
|
||||||
private val imageHost: String,
|
private val imageHost: String,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ data class TokenMarketListConfig(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
enum class Order {
|
enum class Order {
|
||||||
ByRating, Trending, Buyers, TopGainers, TopLosers
|
ByRating, Trending, Buyers, TopGainers, TopLosers, Staking
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Interval {
|
enum class Interval {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.domain.markets
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
data class TokenMarketListWithMaxApy(
|
||||||
|
val tokens: List<TokenMarket>,
|
||||||
|
val maxApy: BigDecimal?,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ package com.tangem.domain.markets.repositories
|
||||||
import com.tangem.domain.markets.*
|
import com.tangem.domain.markets.*
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
interface MarketsTokenRepository {
|
interface MarketsTokenRepository {
|
||||||
|
|
||||||
|
|
@ -51,4 +53,6 @@ interface MarketsTokenRepository {
|
||||||
* @param tokenId token id
|
* @param tokenId token id
|
||||||
*/
|
*/
|
||||||
suspend fun getTokenExchanges(tokenId: CryptoCurrency.RawID): List<TokenMarketExchange>
|
suspend fun getTokenExchanges(tokenId: CryptoCurrency.RawID): List<TokenMarketExchange>
|
||||||
|
|
||||||
|
suspend fun getMaxApy(): Flow<BigDecimal?>
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +15,10 @@ interface PromoRepository {
|
||||||
suspend fun setNeverToShowWalletPromo(promoId: PromoId)
|
suspend fun setNeverToShowWalletPromo(promoId: PromoId)
|
||||||
|
|
||||||
suspend fun setNeverToShowTokenPromo(promoId: PromoId)
|
suspend fun setNeverToShowTokenPromo(promoId: PromoId)
|
||||||
|
|
||||||
|
suspend fun isMarketsStakingNotificationHideClicked(): Flow<Boolean>
|
||||||
|
|
||||||
|
suspend fun setMarketsStakingNotificationHideClicked()
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Stories
|
// region Stories
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.tangem.domain.settings
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
class SetWalletFirstTimeUsageUseCase(private val settingsRepository: SettingsRepository) {
|
||||||
|
|
||||||
|
suspend operator fun invoke() = Either.catch {
|
||||||
|
val savedTime = settingsRepository.getWalletFirstUsageDate()
|
||||||
|
|
||||||
|
if (savedTime == 0L) {
|
||||||
|
settingsRepository.setWalletFirstUsageDate(Calendar.getInstance().timeInMillis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,10 @@ interface SettingsRepository {
|
||||||
|
|
||||||
suspend fun incrementAppLaunchCounter()
|
suspend fun incrementAppLaunchCounter()
|
||||||
|
|
||||||
|
suspend fun getWalletFirstUsageDate(): Long
|
||||||
|
|
||||||
|
suspend fun setWalletFirstUsageDate(value: Long)
|
||||||
|
|
||||||
suspend fun shouldShowMarketsTooltip(): Boolean
|
suspend fun shouldShowMarketsTooltip(): Boolean
|
||||||
|
|
||||||
suspend fun setMarketsTooltipShown(value: Boolean)
|
suspend fun setMarketsTooltipShown(value: Boolean)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ internal sealed class MarketsListAnalyticsEvent(
|
||||||
SortByTypeUM.ExperiencedBuyers -> "Buyers"
|
SortByTypeUM.ExperiencedBuyers -> "Buyers"
|
||||||
SortByTypeUM.TopGainers -> "Gainers"
|
SortByTypeUM.TopGainers -> "Gainers"
|
||||||
SortByTypeUM.TopLosers -> "Losers"
|
SortByTypeUM.TopLosers -> "Losers"
|
||||||
|
SortByTypeUM.Staking -> "Staking"
|
||||||
},
|
},
|
||||||
"Period" to when (interval) {
|
"Period" to when (interval) {
|
||||||
MarketsListUM.TrendInterval.H24 -> "24h"
|
MarketsListUM.TrendInterval.H24 -> "24h"
|
||||||
|
|
@ -31,4 +32,10 @@ internal sealed class MarketsListAnalyticsEvent(
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data object StakingPromoShown : MarketsListAnalyticsEvent(event = "Notice - Staking Promo")
|
||||||
|
|
||||||
|
data object StakingPromoClosed : MarketsListAnalyticsEvent(event = "Staking Promo Closed")
|
||||||
|
|
||||||
|
data object StakingMoreInfoClicked : MarketsListAnalyticsEvent(event = "Staking More Info")
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.features.markets.tokenlist.impl.model
|
package com.tangem.features.markets.tokenlist.impl.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import arrow.core.Either
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
|
|
@ -8,7 +9,13 @@ import com.tangem.core.decompose.model.Model
|
||||||
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.TokenMarket
|
import com.tangem.domain.markets.TokenMarket
|
||||||
|
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.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
import com.tangem.features.markets.entry.BottomSheetState
|
import com.tangem.features.markets.entry.BottomSheetState
|
||||||
import com.tangem.features.markets.tokenlist.impl.analytics.MarketsListAnalyticsEvent
|
import com.tangem.features.markets.tokenlist.impl.analytics.MarketsListAnalyticsEvent
|
||||||
|
|
@ -21,8 +28,10 @@ import com.tangem.utils.Provider
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
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.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
|
||||||
|
|
@ -31,17 +40,20 @@ private const val SEARCH_QUERY_DEBOUNCE_MILLIS = 800L
|
||||||
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
|
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
|
||||||
@ModelScoped
|
@ModelScoped
|
||||||
@Stable
|
@Stable
|
||||||
|
@Suppress("LongParameterList")
|
||||||
internal class MarketsListModel @Inject constructor(
|
internal class MarketsListModel @Inject constructor(
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase,
|
getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase,
|
||||||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||||
|
getStakingNotificationMaxApyUseCase: GetStakingNotificationMaxApyUseCase,
|
||||||
|
private val promoRepository: PromoRepository,
|
||||||
|
private val getUserCountryUseCase: GetUserCountryUseCase,
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private var updateQuotesJob = JobHolder()
|
private var updateQuotesJob = JobHolder()
|
||||||
|
|
||||||
private val currentAppCurrency = getSelectedAppCurrencyUseCase()
|
private val currentAppCurrency = getSelectedAppCurrencyUseCase().map { maybeAppCurrency ->
|
||||||
.map { maybeAppCurrency ->
|
|
||||||
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
scope = modelScope,
|
scope = modelScope,
|
||||||
|
|
@ -57,7 +69,10 @@ 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() },
|
||||||
)
|
)
|
||||||
|
|
||||||
private val mainMarketsListManager = MarketsListBatchFlowManager(
|
private val mainMarketsListManager = MarketsListBatchFlowManager(
|
||||||
getMarketsTokenListFlowUseCase = getMarketsTokenListFlowUseCase,
|
getMarketsTokenListFlowUseCase = getMarketsTokenListFlowUseCase,
|
||||||
batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Main,
|
batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Main,
|
||||||
|
|
@ -92,29 +107,58 @@ internal class MarketsListModel @Inject constructor(
|
||||||
val state = marketsListUMStateManager.state.asStateFlow()
|
val state = marketsListUMStateManager.state.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@Suppress("UnnecessaryParentheses")
|
@Suppress("UnnecessaryParentheses") modelScope.launch {
|
||||||
modelScope.launch {
|
marketsListUMStateManager.isInSearchStateFlow.flatMapLatest { isInSearchMode ->
|
||||||
marketsListUMStateManager.isInSearchStateFlow
|
|
||||||
.flatMapLatest { isInSearchMode ->
|
|
||||||
if (isInSearchMode) {
|
if (isInSearchMode) {
|
||||||
combine(
|
combine(
|
||||||
searchMarketsListManager.uiItems,
|
searchMarketsListManager.uiItems,
|
||||||
searchMarketsListManager.isInInitialLoadingErrorState,
|
searchMarketsListManager.isInInitialLoadingErrorState,
|
||||||
searchMarketsListManager.isSearchNotFoundState,
|
searchMarketsListManager.isSearchNotFoundState,
|
||||||
) { items, isError, notFound ->
|
getStakingNotificationMaxApyUseCase(),
|
||||||
(items to isError) to notFound
|
getUserCountryUseCase.invoke(),
|
||||||
|
) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, stakingMaxApy, userCountry ->
|
||||||
|
MarketsItemsData(
|
||||||
|
items = uiItems,
|
||||||
|
isInErrorState = isInInitialLoadingErrorState,
|
||||||
|
isSearchNotFound = isSearchNotFoundState,
|
||||||
|
stakingNotificationMaxApy = stakingMaxApy,
|
||||||
|
userCountry = userCountry,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
combine(
|
combine(
|
||||||
mainMarketsListManager.uiItems,
|
mainMarketsListManager.uiItems,
|
||||||
mainMarketsListManager.isInInitialLoadingErrorState,
|
mainMarketsListManager.isInInitialLoadingErrorState,
|
||||||
) { items, isError -> (items to isError) to false }
|
getStakingNotificationMaxApyUseCase(),
|
||||||
|
getUserCountryUseCase.invoke(),
|
||||||
|
) { uiItems, isInInitialLoadingErrorState, stakingNotificationMaxApy, userCountry ->
|
||||||
|
MarketsItemsData(
|
||||||
|
items = uiItems,
|
||||||
|
isInErrorState = isInInitialLoadingErrorState,
|
||||||
|
isSearchNotFound = false,
|
||||||
|
stakingNotificationMaxApy = stakingNotificationMaxApy,
|
||||||
|
userCountry = userCountry,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}.collect {
|
}
|
||||||
|
}.collect { marketsItemsData ->
|
||||||
|
val stakingNotificationMaxApy = marketsItemsData.stakingNotificationMaxApy?.takeUnless {
|
||||||
|
marketsItemsData.userCountry.getOrNull().needApplyFCARestrictions()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (marketsListUMStateManager.state.value.stakingNotificationMaxApy == null &&
|
||||||
|
stakingNotificationMaxApy != null
|
||||||
|
) {
|
||||||
|
analyticsEventHandler.send(MarketsListAnalyticsEvent.StakingPromoShown)
|
||||||
|
}
|
||||||
|
|
||||||
marketsListUMStateManager.onUiItemsChanged(
|
marketsListUMStateManager.onUiItemsChanged(
|
||||||
uiItems = it.first.first,
|
uiItems = marketsItemsData.items,
|
||||||
isInErrorState = it.first.second,
|
isInErrorState = marketsItemsData.isInErrorState,
|
||||||
isSearchNotFound = it.second,
|
isSearchNotFound = marketsItemsData.isSearchNotFound,
|
||||||
|
stakingNotificationMaxApy = marketsItemsData.stakingNotificationMaxApy?.takeUnless {
|
||||||
|
marketsItemsData.userCountry.getOrNull().needApplyFCARestrictions()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -126,9 +170,7 @@ internal class MarketsListModel @Inject constructor(
|
||||||
}.launchIn(modelScope)
|
}.launchIn(modelScope)
|
||||||
|
|
||||||
// update all lists when user's currency has changed
|
// update all lists when user's currency has changed
|
||||||
currentAppCurrency
|
currentAppCurrency.drop(1).onEach {
|
||||||
.drop(1)
|
|
||||||
.onEach {
|
|
||||||
mainMarketsListManager.reload()
|
mainMarketsListManager.reload()
|
||||||
if (marketsListUMStateManager.isInSearchState) {
|
if (marketsListUMStateManager.isInSearchState) {
|
||||||
searchMarketsListManager.reload()
|
searchMarketsListManager.reload()
|
||||||
|
|
@ -136,19 +178,14 @@ internal class MarketsListModel @Inject constructor(
|
||||||
}.launchIn(modelScope)
|
}.launchIn(modelScope)
|
||||||
|
|
||||||
// load charts when new batch is being loaded
|
// load charts when new batch is being loaded
|
||||||
mainMarketsListManager.onLastBatchLoadedSuccess
|
mainMarketsListManager.onLastBatchLoadedSuccess.onEach {
|
||||||
.onEach {
|
|
||||||
mainMarketsListManager.loadCharts(setOf(it), marketsListUMStateManager.selectedInterval)
|
mainMarketsListManager.loadCharts(setOf(it), marketsListUMStateManager.selectedInterval)
|
||||||
modelScope.loadQuotesWithTimer(timeMillis = UPDATE_QUOTES_TIMER_MILLIS)
|
modelScope.loadQuotesWithTimer(timeMillis = UPDATE_QUOTES_TIMER_MILLIS)
|
||||||
}
|
}.launchIn(modelScope)
|
||||||
.launchIn(modelScope)
|
|
||||||
|
|
||||||
// listen currently selected interval, update charts if sorting=rating, or reload all list
|
// listen currently selected interval, update charts if sorting=rating, or reload all list
|
||||||
modelScope.launch(dispatchers.default) {
|
modelScope.launch(dispatchers.default) {
|
||||||
marketsListUMStateManager.state
|
marketsListUMStateManager.state.map { it.selectedInterval }.distinctUntilChanged().drop(1)
|
||||||
.map { it.selectedInterval }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.drop(1)
|
|
||||||
.collectLatest { interval ->
|
.collectLatest { interval ->
|
||||||
when (marketsListUMStateManager.selectedSortByType) {
|
when (marketsListUMStateManager.selectedSortByType) {
|
||||||
SortByTypeUM.Rating -> {
|
SortByTypeUM.Rating -> {
|
||||||
|
|
@ -163,27 +200,20 @@ internal class MarketsListModel @Inject constructor(
|
||||||
|
|
||||||
// reload list when sorting type has changed
|
// reload list when sorting type has changed
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
marketsListUMStateManager.state
|
marketsListUMStateManager.state.map { it.selectedSortBy }.distinctUntilChanged().drop(1).collectLatest {
|
||||||
.map { it.selectedSortBy }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.drop(1)
|
|
||||||
.collectLatest {
|
|
||||||
mainMarketsListManager.reload()
|
mainMarketsListManager.reload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// listen current visible batch and update charts
|
// listen current visible batch and update charts
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
visibleItemIds
|
visibleItemIds.mapNotNull {
|
||||||
.mapNotNull {
|
|
||||||
if (it.isNotEmpty()) {
|
if (it.isNotEmpty()) {
|
||||||
activeListManager.getBatchKeysByItemIds(visibleItemIds.value)
|
activeListManager.getBatchKeysByItemIds(visibleItemIds.value)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}.distinctUntilChanged().collectLatest { visibleBatchKeys ->
|
||||||
.distinctUntilChanged()
|
|
||||||
.collectLatest { visibleBatchKeys ->
|
|
||||||
// TODO load batch on scroll heat area
|
// TODO load batch on scroll heat area
|
||||||
activeListManager.loadCharts(visibleBatchKeys, marketsListUMStateManager.selectedInterval)
|
activeListManager.loadCharts(visibleBatchKeys, marketsListUMStateManager.selectedInterval)
|
||||||
}
|
}
|
||||||
|
|
@ -192,8 +222,7 @@ internal class MarketsListModel @Inject constructor(
|
||||||
// ===Search===
|
// ===Search===
|
||||||
|
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
marketsListUMStateManager.isInSearchStateFlow
|
marketsListUMStateManager.isInSearchStateFlow.collectLatest { isInSearchMode ->
|
||||||
.collectLatest { isInSearchMode ->
|
|
||||||
activeListManager = if (isInSearchMode) {
|
activeListManager = if (isInSearchMode) {
|
||||||
searchMarketsListManager
|
searchMarketsListManager
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -204,22 +233,16 @@ internal class MarketsListModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
marketsListUMStateManager.searchQueryFlow
|
marketsListUMStateManager.searchQueryFlow.debounce(timeoutMillis = SEARCH_QUERY_DEBOUNCE_MILLIS)
|
||||||
.debounce(timeoutMillis = SEARCH_QUERY_DEBOUNCE_MILLIS)
|
.distinctUntilChanged().onEach {
|
||||||
.distinctUntilChanged()
|
|
||||||
.onEach {
|
|
||||||
if (it.isEmpty()) searchMarketsListManager.clearStateAndStopAllActions()
|
if (it.isEmpty()) searchMarketsListManager.clearStateAndStopAllActions()
|
||||||
}
|
}.filter { it.isNotEmpty() && activeListManager == searchMarketsListManager }.collectLatest {
|
||||||
.filter { it.isNotEmpty() && activeListManager == searchMarketsListManager }
|
|
||||||
.collectLatest {
|
|
||||||
searchMarketsListManager.reload(searchText = it)
|
searchMarketsListManager.reload(searchText = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
searchMarketsListManager
|
searchMarketsListManager.onLastBatchLoadedSuccess.collectLatest {
|
||||||
.onLastBatchLoadedSuccess
|
|
||||||
.collectLatest {
|
|
||||||
searchMarketsListManager.loadCharts(setOf(it), marketsListUMStateManager.selectedInterval)
|
searchMarketsListManager.loadCharts(setOf(it), marketsListUMStateManager.selectedInterval)
|
||||||
modelScope.loadQuotesWithTimer(timeMillis = UPDATE_QUOTES_TIMER_MILLIS)
|
modelScope.loadQuotesWithTimer(timeMillis = UPDATE_QUOTES_TIMER_MILLIS)
|
||||||
}
|
}
|
||||||
|
|
@ -233,17 +256,14 @@ internal class MarketsListModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initAnalytics() {
|
private fun initAnalytics() {
|
||||||
containerBottomSheetState
|
containerBottomSheetState.onEach {
|
||||||
.onEach {
|
|
||||||
if (it == BottomSheetState.EXPANDED) {
|
if (it == BottomSheetState.EXPANDED) {
|
||||||
analyticsEventHandler.send(MarketsListAnalyticsEvent.BottomSheetOpened)
|
analyticsEventHandler.send(MarketsListAnalyticsEvent.BottomSheetOpened)
|
||||||
}
|
}
|
||||||
}.launchIn(modelScope)
|
}.launchIn(modelScope)
|
||||||
|
|
||||||
state
|
state.filter { it.isInSearchMode.not() }
|
||||||
.filter { it.isInSearchMode.not() }
|
.map { MarketsListAnalyticsEvent.SortBy(it.selectedSortBy, it.selectedInterval) }.distinctUntilChanged()
|
||||||
.map { MarketsListAnalyticsEvent.SortBy(it.selectedSortBy, it.selectedInterval) }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.onEach {
|
.onEach {
|
||||||
analyticsEventHandler.send(it)
|
analyticsEventHandler.send(it)
|
||||||
}.launchIn(modelScope)
|
}.launchIn(modelScope)
|
||||||
|
|
@ -270,4 +290,19 @@ internal class MarketsListModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}.saveIn(updateQuotesJob)
|
}.saveIn(updateQuotesJob)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onStakingNotificationCloseClick() {
|
||||||
|
analyticsEventHandler.send(MarketsListAnalyticsEvent.StakingPromoClosed)
|
||||||
|
modelScope.launch {
|
||||||
|
promoRepository.setMarketsStakingNotificationHideClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MarketsItemsData(
|
||||||
|
val items: ImmutableList<MarketsListItemUM>,
|
||||||
|
val isInErrorState: Boolean,
|
||||||
|
val isSearchNotFound: Boolean,
|
||||||
|
val stakingNotificationMaxApy: BigDecimal?,
|
||||||
|
val userCountry: Either<UserCountryError, UserCountry>,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,8 @@ import com.tangem.common.ui.charts.state.MarketChartRawData
|
||||||
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
|
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
|
||||||
import com.tangem.common.ui.charts.state.sorted
|
import com.tangem.common.ui.charts.state.sorted
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.core.ui.format.bigdecimal.compact
|
import com.tangem.core.ui.format.bigdecimal.compact
|
||||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
|
|
@ -12,6 +14,7 @@ import com.tangem.core.ui.format.bigdecimal.percent
|
||||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.markets.TokenMarket
|
import com.tangem.domain.markets.TokenMarket
|
||||||
|
import com.tangem.features.markets.impl.R
|
||||||
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.MarketsListUM.TrendInterval
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
@ -37,8 +40,11 @@ internal class MarketsTokenItemConverter(
|
||||||
price = value.getCurrentPrice(),
|
price = value.getCurrentPrice(),
|
||||||
trendPercentText = value.getTrendPercent(),
|
trendPercentText = value.getTrendPercent(),
|
||||||
trendType = value.getTrendType(),
|
trendType = value.getTrendType(),
|
||||||
chardData = value.getChartData(),
|
chartData = value.getChartData(),
|
||||||
isUnder100kMarketCap = value.isUnderMarketCapLimit,
|
isUnder100kMarketCap = value.isUnderMarketCapLimit,
|
||||||
|
stakingRate = value.stakingRate?.format { percent() }?.let {
|
||||||
|
resourceReference(R.string.markets_apy_placeholder, wrappedList(it))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,7 +70,7 @@ internal class MarketsTokenItemConverter(
|
||||||
prevUI.trendPercentText,
|
prevUI.trendPercentText,
|
||||||
) { new.getTrendPercent() },
|
) { new.getTrendPercent() },
|
||||||
trendType = ifChanged(prev.tokenQuotesShort, new.tokenQuotesShort, prevUI.trendType) { new.getTrendType() },
|
trendType = ifChanged(prev.tokenQuotesShort, new.tokenQuotesShort, prevUI.trendType) { new.getTrendType() },
|
||||||
chardData = ifChanged(prev.tokenCharts, new.tokenCharts, prevUI.chardData) { new.getChartData() },
|
chartData = ifChanged(prev.tokenCharts, new.tokenCharts, prevUI.chartData) { new.getChartData() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -323,6 +323,7 @@ internal class MarketsListBatchFlowManager(
|
||||||
SortByTypeUM.ExperiencedBuyers -> TokenMarketListConfig.Order.Buyers
|
SortByTypeUM.ExperiencedBuyers -> TokenMarketListConfig.Order.Buyers
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,18 @@ 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")
|
||||||
internal class MarketsListUMStateManager(
|
internal class MarketsListUMStateManager(
|
||||||
private val currentVisibleIds: Provider<List<CryptoCurrency.RawID>>,
|
private val currentVisibleIds: Provider<List<CryptoCurrency.RawID>>,
|
||||||
private val onLoadMoreUiItems: () -> Unit,
|
private val onLoadMoreUiItems: () -> Unit,
|
||||||
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 var sortByBottomSheetIsShown
|
private var sortByBottomSheetIsShown
|
||||||
|
|
@ -87,6 +91,7 @@ internal class MarketsListUMStateManager(
|
||||||
isInErrorState: Boolean,
|
isInErrorState: Boolean,
|
||||||
isSearchNotFound: Boolean,
|
isSearchNotFound: Boolean,
|
||||||
uiItems: ImmutableList<MarketsListItemUM>,
|
uiItems: ImmutableList<MarketsListItemUM>,
|
||||||
|
stakingNotificationMaxApy: BigDecimal?,
|
||||||
) {
|
) {
|
||||||
state.update {
|
state.update {
|
||||||
when {
|
when {
|
||||||
|
|
@ -102,13 +107,19 @@ internal class MarketsListUMStateManager(
|
||||||
it.copy(list = ListUM.Loading)
|
it.copy(list = ListUM.Loading)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
it.updateItems(newItems = uiItems)
|
it.updateItems(
|
||||||
|
newItems = uiItems,
|
||||||
|
stakingNotificationMaxApy = stakingNotificationMaxApy,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MarketsListUM.updateItems(newItems: ImmutableList<MarketsListItemUM>): MarketsListUM {
|
private fun MarketsListUM.updateItems(
|
||||||
|
newItems: ImmutableList<MarketsListItemUM>,
|
||||||
|
stakingNotificationMaxApy: BigDecimal?,
|
||||||
|
): MarketsListUM {
|
||||||
val currentState = this
|
val currentState = this
|
||||||
|
|
||||||
if (isInSearchMode.not() || currentState.showUnder100kButtonAlreadyPressed()) {
|
if (isInSearchMode.not() || currentState.showUnder100kButtonAlreadyPressed()) {
|
||||||
|
|
@ -119,6 +130,7 @@ internal class MarketsListUMStateManager(
|
||||||
.copy(
|
.copy(
|
||||||
showUnder100kTokensNotificationWasHidden = currentState.showUnder100kButtonAlreadyPressed(),
|
showUnder100kTokensNotificationWasHidden = currentState.showUnder100kButtonAlreadyPressed(),
|
||||||
),
|
),
|
||||||
|
stakingNotificationMaxApy = stakingNotificationMaxApy,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,6 +223,12 @@ internal class MarketsListUMStateManager(
|
||||||
onOptionClicked = ::onBottomSheetOptionClicked,
|
onOptionClicked = ::onBottomSheetOptionClicked,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
stakingNotificationMaxApy = null,
|
||||||
|
onStakingNotificationClick = {
|
||||||
|
onStakingNotificationClick()
|
||||||
|
selectedSortByType = SortByTypeUM.Staking
|
||||||
|
},
|
||||||
|
onStakingNotificationCloseClick = onStakingNotificationCloseClick,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun onBottomSheetOptionClicked(sortByTypeUM: SortByTypeUM) {
|
private fun onBottomSheetOptionClicked(sortByTypeUM: SortByTypeUM) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
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
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import com.tangem.core.ui.components.Keyboard
|
import com.tangem.core.ui.components.Keyboard
|
||||||
import com.tangem.core.ui.components.SpacerH12
|
import com.tangem.core.ui.components.SpacerH12
|
||||||
import com.tangem.core.ui.components.SpacerH8
|
import com.tangem.core.ui.components.SpacerH8
|
||||||
|
|
@ -29,10 +30,11 @@ 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.resolveReference
|
import com.tangem.core.ui.extensions.*
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
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
|
||||||
|
|
@ -41,6 +43,7 @@ import com.tangem.features.markets.entry.BottomSheetState
|
||||||
import com.tangem.features.markets.impl.R
|
import com.tangem.features.markets.impl.R
|
||||||
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.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
|
||||||
|
|
@ -48,6 +51,9 @@ 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"
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun MarketsList(
|
internal fun MarketsList(
|
||||||
|
|
@ -110,6 +116,7 @@ private fun Content(state: MarketsListUM, onHeaderSizeChange: (Dp) -> Unit, modi
|
||||||
SpacerH12()
|
SpacerH12()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Column {
|
||||||
AnimatedVisibility(state.isInSearchMode.not()) {
|
AnimatedVisibility(state.isInSearchMode.not()) {
|
||||||
Options(
|
Options(
|
||||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
|
||||||
|
|
@ -119,6 +126,41 @@ private fun Content(state: MarketsListUM, onHeaderSizeChange: (Dp) -> Unit, modi
|
||||||
onSortByClick = state.onSortByButtonClick,
|
onSortByClick = state.onSortByButtonClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
state.isInSearchMode.not() &&
|
||||||
|
state.stakingNotificationMaxApy != null &&
|
||||||
|
state.selectedSortBy != SortByTypeUM.Staking,
|
||||||
|
) {
|
||||||
|
val showMore = stringResourceSafe(R.string.markets_staking_banner_description_show_more)
|
||||||
|
val description = stringResourceSafe(
|
||||||
|
R.string.markets_staking_banner_description_placeholder,
|
||||||
|
showMore,
|
||||||
|
)
|
||||||
|
|
||||||
|
val clickableDescription = buildAnnotatedString {
|
||||||
|
append(description.substringBefore(showMore))
|
||||||
|
|
||||||
|
pushStringAnnotation(SHOW_MORE_KEY, "")
|
||||||
|
appendColored(showMore, TangemTheme.colors.text.accent)
|
||||||
|
pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
StakingInMarketsPromoNotification(
|
||||||
|
config = NotificationConfig(
|
||||||
|
iconResId = R.drawable.img_staking_in_market_notification,
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val strokeWidth = TangemTheme.dimens.size0_5
|
val strokeWidth = TangemTheme.dimens.size0_5
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -326,6 +368,9 @@ private fun Preview() {
|
||||||
onDismissRequest = {},
|
onDismissRequest = {},
|
||||||
content = SortByBottomSheetContentUM(selectedOption = SortByTypeUM.Rating) {},
|
content = SortByBottomSheetContentUM(selectedOption = SortByTypeUM.Rating) {},
|
||||||
),
|
),
|
||||||
|
stakingNotificationMaxApy = BigDecimal(0.12345),
|
||||||
|
onStakingNotificationClick = {},
|
||||||
|
onStakingNotificationCloseClick = {},
|
||||||
),
|
),
|
||||||
onHeaderSizeChange = {},
|
onHeaderSizeChange = {},
|
||||||
bottomSheetState = BottomSheetState.EXPANDED,
|
bottomSheetState = BottomSheetState.EXPANDED,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.markets.tokenlist.impl.ui.components
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
|
@ -22,6 +23,8 @@ import com.tangem.core.ui.components.*
|
||||||
import com.tangem.core.ui.components.currency.icon.CoinIcon
|
import com.tangem.core.ui.components.currency.icon.CoinIcon
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeInPercent
|
import com.tangem.core.ui.components.marketprice.PriceChangeInPercent
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.res.LocalWindowSize
|
import com.tangem.core.ui.res.LocalWindowSize
|
||||||
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
|
||||||
|
|
@ -95,6 +98,7 @@ private fun MarketsListItemContent(model: MarketsListItemUM, modifier: Modifier
|
||||||
.alignByBaseline(),
|
.alignByBaseline(),
|
||||||
ratingPosition = model.ratingPosition,
|
ratingPosition = model.ratingPosition,
|
||||||
marketCap = model.marketCap,
|
marketCap = model.marketCap,
|
||||||
|
stakingRate = model.stakingRate,
|
||||||
)
|
)
|
||||||
PriceChangeInPercent(
|
PriceChangeInPercent(
|
||||||
modifier = Modifier.alignByBaseline(),
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
|
@ -110,7 +114,7 @@ private fun MarketsListItemContent(model: MarketsListItemUM, modifier: Modifier
|
||||||
|
|
||||||
Chart(
|
Chart(
|
||||||
chartType = model.chartType,
|
chartType = model.chartType,
|
||||||
chartRawData = model.chardData,
|
chartRawData = model.chartData,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +146,12 @@ private fun TokenTitle(name: String, currencySymbol: String, modifier: Modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TokenSubtitle(ratingPosition: String?, marketCap: String?, modifier: Modifier = Modifier) {
|
private fun TokenSubtitle(
|
||||||
|
ratingPosition: String?,
|
||||||
|
marketCap: String?,
|
||||||
|
stakingRate: TextReference?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
|
@ -150,6 +159,10 @@ private fun TokenSubtitle(ratingPosition: String?, marketCap: String?, modifier:
|
||||||
TokenRatingPlace(ratingPosition = ratingPosition)
|
TokenRatingPlace(ratingPosition = ratingPosition)
|
||||||
SpacerW4()
|
SpacerW4()
|
||||||
TokenMarketCapText(text = marketCap ?: "")
|
TokenMarketCapText(text = marketCap ?: "")
|
||||||
|
if (stakingRate != null) {
|
||||||
|
SpacerW4()
|
||||||
|
StakingRate(stakingRate = stakingRate.resolveReference())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,6 +187,28 @@ private fun RowScope.TokenRatingPlace(ratingPosition: String?) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RowScope.StakingRate(stakingRate: String) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.alignByBaseline()
|
||||||
|
.heightIn(min = TangemTheme.dimens.size16)
|
||||||
|
.border(
|
||||||
|
width = TangemTheme.dimens.size1,
|
||||||
|
color = TangemTheme.colors.field.primary,
|
||||||
|
shape = TangemTheme.shapes.roundedCornersSmall2,
|
||||||
|
)
|
||||||
|
.padding(horizontal = TangemTheme.dimens.spacing5),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stakingRate,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
style = TangemTheme.typography.caption1,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RowScope.TokenMarketCapText(text: String) {
|
private fun RowScope.TokenMarketCapText(text: String) {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -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(0x1F8CD9FF)
|
||||||
|
private val borderColor = Color(0x3D8CD9FF)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StakingInMarketsPromoNotification(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(12.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = iconRes),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
modifier = Modifier
|
||||||
|
.requiredWidth(56.dp)
|
||||||
|
.wrapContentHeight(Alignment.Top, 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 = 76.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 {
|
||||||
|
StakingInMarketsPromoNotification(
|
||||||
|
config = NotificationConfig(
|
||||||
|
title = stringReference("Earn up to 14% APY"),
|
||||||
|
subtitle = stringReference("Staking is the easiest way to earn rewards on your crypto. Show more"),
|
||||||
|
iconResId = R.drawable.img_staking_in_market_notification,
|
||||||
|
onCloseClick = { },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
@ -4,6 +4,7 @@ package com.tangem.features.markets.tokenlist.impl.ui.preview
|
||||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||||
import com.tangem.common.ui.charts.state.MarketChartRawData
|
import com.tangem.common.ui.charts.state.MarketChartRawData
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM
|
import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -20,10 +21,11 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
||||||
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
||||||
trendPercentText = "12.43%",
|
trendPercentText = "12.43%",
|
||||||
trendType = PriceChangeType.UP,
|
trendType = PriceChangeType.UP,
|
||||||
chardData = MarketChartRawData(
|
chartData = MarketChartRawData(
|
||||||
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
||||||
),
|
),
|
||||||
isUnder100kMarketCap = false,
|
isUnder100kMarketCap = false,
|
||||||
|
stakingRate = stringReference("APY 12.34%"),
|
||||||
),
|
),
|
||||||
MarketsListItemUM(
|
MarketsListItemUM(
|
||||||
id = CryptoCurrency.RawID("1"),
|
id = CryptoCurrency.RawID("1"),
|
||||||
|
|
@ -35,8 +37,9 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
||||||
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
||||||
trendPercentText = "12.43%",
|
trendPercentText = "12.43%",
|
||||||
trendType = PriceChangeType.NEUTRAL,
|
trendType = PriceChangeType.NEUTRAL,
|
||||||
chardData = null,
|
chartData = null,
|
||||||
isUnder100kMarketCap = false,
|
isUnder100kMarketCap = false,
|
||||||
|
stakingRate = stringReference("APY 12.34%"),
|
||||||
),
|
),
|
||||||
MarketsListItemUM(
|
MarketsListItemUM(
|
||||||
id = CryptoCurrency.RawID("1"),
|
id = CryptoCurrency.RawID("1"),
|
||||||
|
|
@ -48,10 +51,11 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
||||||
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
||||||
trendPercentText = "12.43%",
|
trendPercentText = "12.43%",
|
||||||
trendType = PriceChangeType.DOWN,
|
trendType = PriceChangeType.DOWN,
|
||||||
chardData = MarketChartRawData(
|
chartData = MarketChartRawData(
|
||||||
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
||||||
),
|
),
|
||||||
isUnder100kMarketCap = false,
|
isUnder100kMarketCap = false,
|
||||||
|
stakingRate = stringReference("APY 12.34%"),
|
||||||
),
|
),
|
||||||
MarketsListItemUM(
|
MarketsListItemUM(
|
||||||
id = CryptoCurrency.RawID("1"),
|
id = CryptoCurrency.RawID("1"),
|
||||||
|
|
@ -63,10 +67,11 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
||||||
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
||||||
trendPercentText = "12.43%",
|
trendPercentText = "12.43%",
|
||||||
trendType = PriceChangeType.UP,
|
trendType = PriceChangeType.UP,
|
||||||
chardData = MarketChartRawData(
|
chartData = MarketChartRawData(
|
||||||
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
||||||
),
|
),
|
||||||
isUnder100kMarketCap = false,
|
isUnder100kMarketCap = false,
|
||||||
|
stakingRate = stringReference("APY 12.34%"),
|
||||||
),
|
),
|
||||||
MarketsListItemUM(
|
MarketsListItemUM(
|
||||||
id = CryptoCurrency.RawID("1"),
|
id = CryptoCurrency.RawID("1"),
|
||||||
|
|
@ -78,10 +83,11 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
||||||
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
||||||
trendPercentText = "12.43%",
|
trendPercentText = "12.43%",
|
||||||
trendType = PriceChangeType.UP,
|
trendType = PriceChangeType.UP,
|
||||||
chardData = MarketChartRawData(
|
chartData = MarketChartRawData(
|
||||||
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
||||||
),
|
),
|
||||||
isUnder100kMarketCap = false,
|
isUnder100kMarketCap = false,
|
||||||
|
stakingRate = stringReference("APY 12.34%"),
|
||||||
),
|
),
|
||||||
MarketsListItemUM(
|
MarketsListItemUM(
|
||||||
id = CryptoCurrency.RawID("1"),
|
id = CryptoCurrency.RawID("1"),
|
||||||
|
|
@ -93,10 +99,11 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet
|
||||||
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
price = MarketsListItemUM.Price(text = "31 285.72$"),
|
||||||
trendPercentText = "12.43%",
|
trendPercentText = "12.43%",
|
||||||
trendType = PriceChangeType.UP,
|
trendType = PriceChangeType.UP,
|
||||||
chardData = MarketChartRawData(
|
chartData = MarketChartRawData(
|
||||||
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0),
|
||||||
),
|
),
|
||||||
isUnder100kMarketCap = false,
|
isUnder100kMarketCap = false,
|
||||||
|
stakingRate = stringReference("APY 12.34%"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
|
||||||
import com.tangem.common.ui.charts.state.MarketChartLook
|
import com.tangem.common.ui.charts.state.MarketChartLook
|
||||||
import com.tangem.common.ui.charts.state.MarketChartRawData
|
import com.tangem.common.ui.charts.state.MarketChartRawData
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
@ -17,8 +18,9 @@ data class MarketsListItemUM(
|
||||||
val price: Price,
|
val price: Price,
|
||||||
val trendPercentText: String,
|
val trendPercentText: String,
|
||||||
val trendType: PriceChangeType,
|
val trendType: PriceChangeType,
|
||||||
val chardData: MarketChartRawData?,
|
val chartData: MarketChartRawData?,
|
||||||
val isUnder100kMarketCap: Boolean,
|
val isUnder100kMarketCap: Boolean,
|
||||||
|
val stakingRate: TextReference?,
|
||||||
) {
|
) {
|
||||||
val chartType: MarketChartLook.Type = when (trendType) {
|
val chartType: MarketChartLook.Type = when (trendType) {
|
||||||
PriceChangeType.UP -> MarketChartLook.Type.Growing
|
PriceChangeType.UP -> MarketChartLook.Type.Growing
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
import com.tangem.features.markets.impl.R
|
import com.tangem.features.markets.impl.R
|
||||||
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,
|
||||||
|
|
@ -18,6 +19,9 @@ 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 onStakingNotificationClick: () -> Unit,
|
||||||
|
val onStakingNotificationCloseClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val isInSearchMode
|
val isInSearchMode
|
||||||
get() = searchBar.isActive
|
get() = searchBar.isActive
|
||||||
|
|
@ -35,6 +39,7 @@ enum class SortByTypeUM(val text: TextReference) {
|
||||||
ExperiencedBuyers(resourceReference(R.string.markets_sort_by_experienced_buyers_title)),
|
ExperiencedBuyers(resourceReference(R.string.markets_sort_by_experienced_buyers_title)),
|
||||||
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)),
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ internal class WalletModel @Inject constructor(
|
||||||
private val getWalletsUseCase: GetWalletsUseCase,
|
private val getWalletsUseCase: GetWalletsUseCase,
|
||||||
private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase,
|
private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase,
|
||||||
private val shouldShowMarketsTooltipUseCase: ShouldShowMarketsTooltipUseCase,
|
private val shouldShowMarketsTooltipUseCase: ShouldShowMarketsTooltipUseCase,
|
||||||
|
private val setWalletFirstTimeUsageUseCase: SetWalletFirstTimeUsageUseCase,
|
||||||
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
||||||
private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled,
|
private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled,
|
||||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||||
|
|
@ -103,6 +104,7 @@ internal class WalletModel @Inject constructor(
|
||||||
suggestToOpenMarkets()
|
suggestToOpenMarkets()
|
||||||
|
|
||||||
maybeMigrateNames()
|
maybeMigrateNames()
|
||||||
|
maybeSetWalletFirstTimeUsage()
|
||||||
subscribeToUserWalletsUpdates()
|
subscribeToUserWalletsUpdates()
|
||||||
subscribeOnBalanceHiding()
|
subscribeOnBalanceHiding()
|
||||||
subscribeOnSelectedWalletFlow()
|
subscribeOnSelectedWalletFlow()
|
||||||
|
|
@ -117,6 +119,12 @@ internal class WalletModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun maybeSetWalletFirstTimeUsage() {
|
||||||
|
modelScope.launch {
|
||||||
|
setWalletFirstTimeUsageUseCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue