diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt index cdd42cdde9..5b5902f3cc 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -36,6 +36,7 @@ class CardContextInterceptor( is SignIn.ScreenOpened, is SignIn.ButtonAddWallet, -> false + is SignIn.ErrorBiometricUpdated -> !event.isFromUnlockAll else -> true } } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/HotWalletContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/HotWalletContextInterceptor.kt index f67eaab3f8..e24d5594e5 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/HotWalletContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/HotWalletContextInterceptor.kt @@ -19,6 +19,7 @@ class HotWalletContextInterceptor( is SignIn.ButtonUnlockAllWithBiometric, is IntroductionProcess.ButtonScanCard, -> false + is SignIn.ErrorBiometricUpdated -> !event.isFromUnlockAll else -> true } } diff --git a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt index d6bfbdb63a..6e154f3fc1 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt @@ -8,7 +8,6 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.promo.PromoRepository import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher 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.multi.MultiStakingBalanceFetcher import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -129,13 +128,11 @@ object MarketsDomainModule { @Provides @Singleton - fun provideGetStakingNotificationMaxApyUseCase( - settingsRepository: SettingsRepository, + fun provideShouldShowYieldModeMarketPromoUseCase( promoRepository: PromoRepository, marketsTokenRepository: MarketsTokenRepository, - ): GetStakingNotificationMaxApyUseCase { - return GetStakingNotificationMaxApyUseCase( - settingsRepository = settingsRepository, + ): ShouldShowYieldModeMarketPromoUseCase { + return ShouldShowYieldModeMarketPromoUseCase( promoRepository = promoRepository, marketsTokenRepository = marketsTokenRepository, ) diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletUnlockError.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletUnlockError.kt index e7e4b2a884..41fdd9e856 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletUnlockError.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletUnlockError.kt @@ -16,6 +16,7 @@ inline fun UnlockWalletError.handle( onAlreadyUnlocked: () -> Unit = {}, onUserCancelled: () -> Unit = {}, analyticsEventHandler: AnalyticsEventHandler, + isFromUnlockAll: Boolean, noinline showMessage: (EventMessage) -> Unit, ) { when (this) { @@ -33,11 +34,17 @@ inline fun UnlockWalletError.handle( // This should never happen in this flow, as we always check for the wallet existence before unlocking showMessage(SnackbarMessage(TextReference.Res(R.string.generic_error))) } - is UnlockWalletError.UnableToUnlock -> handleUnableToUnlock(this, analyticsEventHandler, showMessage) + is UnlockWalletError.UnableToUnlock -> handleUnableToUnlock( + isFromUnlockAll = isFromUnlockAll, + error = this, + analyticsEventHandler = analyticsEventHandler, + showDialog = showMessage, + ) } } fun handleUnableToUnlock( + isFromUnlockAll: Boolean, error: UnlockWalletError.UnableToUnlock, analyticsEventHandler: AnalyticsEventHandler, showDialog: (DialogMessage) -> Unit, @@ -46,7 +53,7 @@ fun handleUnableToUnlock( is UnlockWalletError.UnableToUnlock.WithReason -> { when (error.reason) { Reason.AllKeysInvalidated -> { - analyticsEventHandler.send(SignIn.ErrorBiometricUpdated()) + analyticsEventHandler.send(SignIn.ErrorBiometricUpdated(isFromUnlockAll)) DialogMessage( title = resourceReference(R.string.biometric_updated_warning_title), message = resourceReference(R.string.biometric_updated_warning_description), diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SignIn.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SignIn.kt index 9b88374046..f62e70074f 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SignIn.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SignIn.kt @@ -21,7 +21,9 @@ sealed class SignIn( class ButtonUnlockAllWithBiometric : SignIn(event = "Button - Unlock All With Biometric") - class ErrorBiometricUpdated : SignIn(event = "Error - Biometric Updated") + data class ErrorBiometricUpdated( + val isFromUnlockAll: Boolean, + ) : SignIn(event = "Error - Biometric Updated") class ButtonWallet( signInType: SignInType, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketListResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketListResponse.kt index 3c1829900b..31168b8c5b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketListResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketListResponse.kt @@ -26,6 +26,7 @@ data class TokenMarketListResponse( @Json(name = "market_cap") val marketCap: BigDecimal?, @Json(name = "is_under_market_cap_limit") val isUnderMarketCapLimit: Boolean?, @Json(name = "staking_opportunities") val stakingOpportunities: List?, + @Json(name = "max_yield_apy") val maxYieldApy: BigDecimal?, ) { @JsonClass(generateAdapter = true) diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 3fafa4462a..b52614222b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -77,8 +77,8 @@ object PreferencesKeys { val SHOULD_SHOW_MARKETS_TOOLTIP_KEY by lazy { booleanPreferencesKey(name = "shouldShowMarketsTooltip") } - val MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY by lazy { - booleanPreferencesKey(name = "marketsStakingNotificationHideClicked") + val MARKETS_YIELD_SUPPLY_NOTIFICATION_HIDE_CLICKED_KEY by lazy { + booleanPreferencesKey(name = "marketsYieldSupplyNotificationHideClicked") } val WALLET_FIRST_USAGE_DATE_KEY by lazy { longPreferencesKey(name = "walletFirstUsageDate") } diff --git a/core/ui/src/main/res/drawable/img_yield_supply_in_market_notification.webp b/core/ui/src/main/res/drawable/img_yield_supply_in_market_notification.webp new file mode 100644 index 0000000000..6f7bfc38ce Binary files /dev/null and b/core/ui/src/main/res/drawable/img_yield_supply_in_market_notification.webp differ diff --git a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt index 9dcc66efb0..2f974d4f31 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt @@ -20,6 +20,7 @@ import com.tangem.datasource.api.markets.TangemTechMarketsApi import com.tangem.datasource.api.markets.models.response.TokenMarketExchangesResponse import com.tangem.datasource.local.datastore.RuntimeStateStore import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.* import com.tangem.domain.markets.repositories.MarketsTokenRepository 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.fetcher.LimitOffsetBatchFetcher import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext -import java.math.BigDecimal +import timber.log.Timber import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong @@ -43,7 +43,6 @@ internal class DefaultMarketsTokenRepository( private val analyticsEventHandler: AnalyticsEventHandler, private val cacheRegistry: CacheRegistry, private val tokenExchangesStore: RuntimeStateStore>, - private val maxApyStore: RuntimeStateStore, private val networkFactory: NetworkFactory, excludedBlockchains: ExcludedBlockchains, ) : MarketsTokenRepository { @@ -95,8 +94,6 @@ internal class DefaultMarketsTokenRepository( val tokenMarketListWithMaxApy = TokenMarketListConverter.convert(res) - maxApyStore.store(tokenMarketListWithMaxApy.maxApy) - return BatchFetchResult.Success( data = tokenMarketListWithMaxApy.tokens, last = last, @@ -294,8 +291,24 @@ internal class DefaultMarketsTokenRepository( } } - override suspend fun getMaxApy(): Flow { - return maxApyStore.get() + override suspend fun showYieldModePromo( + 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 catchListErrorAndSendEvent(block: () -> T): T { diff --git a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenListConfigConverters.kt b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenListConfigConverters.kt index e280e160e1..7dce907fe8 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenListConfigConverters.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenListConfigConverters.kt @@ -16,6 +16,7 @@ internal fun TokenMarketListConfig.Order.toRequestParam(): String = when (this) TokenMarketListConfig.Order.TopGainers -> "gainers" TokenMarketListConfig.Order.TopLosers -> "losers" TokenMarketListConfig.Order.Staking -> "staking" + TokenMarketListConfig.Order.YieldSupply -> "yield" } internal fun PriceChangeInterval.toRequestParam(): String = when (this) { diff --git a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketListConverter.kt b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketListConverter.kt index 067f886293..ed6cb59dc4 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketListConverter.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketListConverter.kt @@ -40,7 +40,7 @@ internal object TokenMarketListConverter : Converter { + override fun isMarketsYieldSupplyNotificationHideClicked(): Flow { return appPreferencesStore.get( - key = PreferencesKeys.MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY, + key = PreferencesKeys.MARKETS_YIELD_SUPPLY_NOTIFICATION_HIDE_CLICKED_KEY, default = false, ) } - override suspend fun setMarketsStakingNotificationHideClicked() { + override suspend fun setMarketsYieldSupplyNotificationHideClicked() { appPreferencesStore.store( - key = PreferencesKeys.MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY, + key = PreferencesKeys.MARKETS_YIELD_SUPPLY_NOTIFICATION_HIDE_CLICKED_KEY, value = true, ) } diff --git a/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarket.kt b/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarket.kt index 36b179897a..3deeee528d 100644 --- a/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarket.kt +++ b/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarket.kt @@ -12,7 +12,7 @@ data class TokenMarket( val isUnderMarketCapLimit: Boolean, val tokenQuotesShort: TokenQuotesShort, val tokenCharts: Charts, - val stakingRate: BigDecimal?, + val yieldRate: BigDecimal?, val updateTimestamp: Long?, private val imageHost: String, ) { diff --git a/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketListConfig.kt b/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketListConfig.kt index 011df2c630..ffd82b8909 100644 --- a/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketListConfig.kt +++ b/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketListConfig.kt @@ -8,7 +8,7 @@ data class TokenMarketListConfig( ) { enum class Order { - ByRating, Trending, Buyers, TopGainers, TopLosers, Staking + ByRating, Trending, Buyers, TopGainers, TopLosers, Staking, YieldSupply, } enum class Interval { diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/GetStakingNotificationMaxApyUseCase.kt b/domain/markets/src/main/java/com/tangem/domain/markets/GetStakingNotificationMaxApyUseCase.kt deleted file mode 100644 index 4ccaea6680..0000000000 --- a/domain/markets/src/main/java/com/tangem/domain/markets/GetStakingNotificationMaxApyUseCase.kt +++ /dev/null @@ -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 { - 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 - } -} \ No newline at end of file diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/ShouldShowYieldModeMarketPromoUseCase.kt b/domain/markets/src/main/java/com/tangem/domain/markets/ShouldShowYieldModeMarketPromoUseCase.kt new file mode 100644 index 0000000000..599e7cefc3 --- /dev/null +++ b/domain/markets/src/main/java/com/tangem/domain/markets/ShouldShowYieldModeMarketPromoUseCase.kt @@ -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 { + val hideClickedFlow = promoRepository.isMarketsYieldSupplyNotificationHideClicked() + + return hideClickedFlow.map { hideClicked -> + !hideClicked && marketsTokenRepository.showYieldModePromo( + appCurrency = appCurrency, + interval = interval, + ) + } + } +} \ No newline at end of file diff --git a/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt b/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt index d813fdcc17..00a0e86540 100644 --- a/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt +++ b/domain/markets/src/main/java/com/tangem/domain/markets/repositories/MarketsTokenRepository.kt @@ -1,11 +1,10 @@ package com.tangem.domain.markets.repositories +import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.* import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId -import kotlinx.coroutines.flow.Flow -import java.math.BigDecimal interface MarketsTokenRepository { @@ -56,5 +55,5 @@ interface MarketsTokenRepository { */ suspend fun getTokenExchanges(tokenId: CryptoCurrency.RawID): List - suspend fun getMaxApy(): Flow + suspend fun showYieldModePromo(appCurrency: AppCurrency, interval: TokenMarketListConfig.Interval): Boolean } \ No newline at end of file diff --git a/domain/promo/src/main/java/com/tangem/domain/promo/PromoRepository.kt b/domain/promo/src/main/java/com/tangem/domain/promo/PromoRepository.kt index 6e7afbdb02..de65de82b0 100644 --- a/domain/promo/src/main/java/com/tangem/domain/promo/PromoRepository.kt +++ b/domain/promo/src/main/java/com/tangem/domain/promo/PromoRepository.kt @@ -16,9 +16,9 @@ interface PromoRepository { suspend fun setNeverToShowTokenPromo(promoId: PromoId) - fun isMarketsStakingNotificationHideClicked(): Flow + fun isMarketsYieldSupplyNotificationHideClicked(): Flow - suspend fun setMarketsStakingNotificationHideClicked() + suspend fun setMarketsYieldSupplyNotificationHideClicked() suspend fun isMoonpayPromoActive(): Boolean // endregion diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt index f0b9432ebf..badebd605e 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt @@ -91,7 +91,7 @@ internal class UserWalletListModel @Inject constructor( private fun onAddNewWalletClick() { if (hotWalletFeatureToggles.isHotWalletEnabled) { - analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.SignIn)) + analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.Settings)) router.push(AppRoute.CreateWalletSelection) } else { withProgress(isWalletSavingInProgress) { @@ -109,6 +109,7 @@ internal class UserWalletListModel @Inject constructor( Timber.e("Failed to unlock wallet $userWalletId: $error") error.handle( onUserCancelled = {}, + isFromUnlockAll = false, onAlreadyUnlocked = { router.push(AppRoute.WalletSettings(userWalletId)) }, analyticsEventHandler = analyticsEventHandler, showMessage = messageSender::send, diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/converter/MarketsTokenItemConverter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/converter/MarketsTokenItemConverter.kt index b513310fb1..6bc58aab6e 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/converter/MarketsTokenItemConverter.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/converter/MarketsTokenItemConverter.kt @@ -38,7 +38,7 @@ internal class MarketsTokenItemConverter( trendType = value.getTrendType(), chartData = value.getChartData(), isUnder100kMarketCap = value.isUnderMarketCapLimit, - stakingRate = value.stakingRate?.format { percent() }?.let { + stakingRate = value.yieldRate?.format { percent() }?.let { resourceReference(R.string.markets_apy_placeholder, wrappedList(it)) }, updateTimestamp = value.updateTimestamp, diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt index ee02fa8424..6b7fb68da4 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt @@ -338,6 +338,7 @@ internal class FeedComponentModel @Inject constructor( SortByTypeUM.TopGainers -> TokenMarketListConfig.Order.TopGainers SortByTypeUM.TopLosers -> TokenMarketListConfig.Order.TopLosers SortByTypeUM.Staking -> TokenMarketListConfig.Order.Staking + SortByTypeUM.YieldSupply -> TokenMarketListConfig.Order.YieldSupply } } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/state/FeedMarketsBatchFlowManager.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/state/FeedMarketsBatchFlowManager.kt index 7fa17ca9f5..b053465040 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/state/FeedMarketsBatchFlowManager.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/state/FeedMarketsBatchFlowManager.kt @@ -386,6 +386,7 @@ internal class FeedMarketsBatchFlowManager( TokenMarketListConfig.Order.TopGainers -> SortByTypeUM.TopGainers TokenMarketListConfig.Order.TopLosers -> SortByTypeUM.TopLosers TokenMarketListConfig.Order.Staking -> SortByTypeUM.Staking + TokenMarketListConfig.Order.YieldSupply -> SortByTypeUM.YieldSupply } } } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/state/MarketsListUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/state/MarketsListUM.kt index 27e1c0daaf..88dc1834e7 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/state/MarketsListUM.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/market/state/MarketsListUM.kt @@ -41,6 +41,7 @@ enum class SortByTypeUM(val text: TextReference) { TopGainers(resourceReference(R.string.markets_sort_by_top_gainers_title)), TopLosers(resourceReference(R.string.markets_sort_by_top_losers_title)), Staking(resourceReference(R.string.common_staking)), + YieldSupply(resourceReference(R.string.markets_sort_by_yield_mode_title)), } @Immutable diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/stepper/impl/HotWalletStepperModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/stepper/impl/HotWalletStepperModel.kt index 5e26e5570d..3f86e09a9d 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/stepper/impl/HotWalletStepperModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/stepper/impl/HotWalletStepperModel.kt @@ -31,7 +31,6 @@ internal class HotWalletStepperModel @Inject constructor( } fun onSkipClick() { - // TODO send analytics params.callback.onSkipClick() } } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/analytics/MarketsListAnalyticsEvent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/analytics/MarketsListAnalyticsEvent.kt index c5f92d0700..996e95d31b 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/analytics/MarketsListAnalyticsEvent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/analytics/MarketsListAnalyticsEvent.kt @@ -24,6 +24,7 @@ internal sealed class MarketsListAnalyticsEvent( SortByTypeUM.TopGainers -> "Gainers" SortByTypeUM.TopLosers -> "Losers" SortByTypeUM.Staking -> "Staking" + SortByTypeUM.YieldSupply -> "Yield Supply" }, "Period" to when (interval) { 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 StakingMoreInfoClicked : MarketsListAnalyticsEvent(event = "Staking More Info") + class YieldModeMoreInfoClicked : MarketsListAnalyticsEvent(event = "Yield Mode More Info") data class TokenSearched(val tokenFound: Boolean) : MarketsListAnalyticsEvent( event = "Token Searched", diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt index 600133bc7c..e214f30658 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt @@ -10,19 +10,20 @@ import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase -import com.tangem.domain.markets.GetStakingNotificationMaxApyUseCase +import com.tangem.domain.markets.ShouldShowYieldModeMarketPromoUseCase import com.tangem.domain.markets.TokenMarket +import com.tangem.domain.markets.TokenMarketListConfig import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.promo.PromoRepository import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.settings.usercountry.models.UserCountryError -import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions import com.tangem.features.markets.tokenlist.impl.analytics.MarketsListAnalyticsEvent 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.ui.state.ListUM 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.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -31,7 +32,6 @@ import com.tangem.utils.coroutines.saveIn import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import java.math.BigDecimal import javax.inject.Inject private const val UPDATE_QUOTES_TIMER_MILLIS = 60000L @@ -45,7 +45,7 @@ internal class MarketsListModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - getStakingNotificationMaxApyUseCase: GetStakingNotificationMaxApyUseCase, + shouldShowYieldModeMarketPromoUseCase: ShouldShowYieldModeMarketPromoUseCase, private val promoRepository: PromoRepository, private val getUserCountryUseCase: GetUserCountryUseCase, private val analyticsEventHandler: AnalyticsEventHandler, @@ -69,8 +69,6 @@ internal class MarketsListModel @Inject constructor( visibleItemsChanged = { visibleItemIds.value = it }, onRetryButtonClicked = { activeListManager.reload() }, onTokenClick = { onTokenUIClicked(it) }, - onStakingNotificationClick = { analyticsEventHandler.send(MarketsListAnalyticsEvent.StakingMoreInfoClicked()) }, - onStakingNotificationCloseClick = { onStakingNotificationCloseClick() }, onShowTokensUnder100kClicked = { analyticsEventHandler.send(MarketsListAnalyticsEvent.ShowTokens()) }, ) @@ -112,53 +110,62 @@ internal class MarketsListModel @Inject constructor( marketsListUMStateManager.isInSearchStateFlow.flatMapLatest { isInSearchMode -> if (isInSearchMode) { combine( - searchMarketsListManager.uiItems, - searchMarketsListManager.isInInitialLoadingErrorState, - searchMarketsListManager.isSearchNotFoundState, - getStakingNotificationMaxApyUseCase(), - getUserCountryUseCase.invoke(), - ) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, stakingMaxApy, userCountry -> + flow = searchMarketsListManager.uiItems, + flow2 = searchMarketsListManager.isInInitialLoadingErrorState, + flow3 = searchMarketsListManager.isSearchNotFoundState, + flow4 = shouldShowYieldModeMarketPromoUseCase( + appCurrency = currentAppCurrency.value, + interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(), + ), + flow5 = getUserCountryUseCase.invoke(), + ) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, isYieldModePromo, userCountry -> MarketsItemsData( items = uiItems, isInErrorState = isInInitialLoadingErrorState, isSearchNotFound = isSearchNotFoundState, - stakingNotificationMaxApy = stakingMaxApy, + shouldShowYieldModePromo = isYieldModePromo, userCountry = userCountry, ) } } else { combine( - mainMarketsListManager.uiItems, - mainMarketsListManager.isInInitialLoadingErrorState, - getStakingNotificationMaxApyUseCase(), - getUserCountryUseCase.invoke(), - ) { uiItems, isInInitialLoadingErrorState, stakingNotificationMaxApy, userCountry -> + flow = mainMarketsListManager.uiItems, + flow2 = mainMarketsListManager.isInInitialLoadingErrorState, + flow3 = shouldShowYieldModeMarketPromoUseCase( + appCurrency = currentAppCurrency.value, + interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(), + ), + flow4 = getUserCountryUseCase.invoke(), + ) { uiItems, isInInitialLoadingErrorState, shouldShowYieldModePromo, userCountry -> MarketsItemsData( items = uiItems, isInErrorState = isInInitialLoadingErrorState, isSearchNotFound = false, - stakingNotificationMaxApy = stakingNotificationMaxApy, + shouldShowYieldModePromo = shouldShowYieldModePromo, userCountry = userCountry, ) } } }.collect { marketsItemsData -> - val stakingNotificationMaxApy = marketsItemsData.stakingNotificationMaxApy?.takeUnless { - marketsItemsData.userCountry.getOrNull().needApplyFCARestrictions() - } - - if (marketsListUMStateManager.state.value.stakingNotificationMaxApy == null && - stakingNotificationMaxApy != null - ) { - analyticsEventHandler.send(MarketsListAnalyticsEvent.StakingPromoShown()) + val shouldShowYieldModePromo = marketsItemsData.shouldShowYieldModePromo + if (marketsListUMStateManager.state.value.marketsNotificationUM == null && shouldShowYieldModePromo) { + analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModePromoShown()) } marketsListUMStateManager.onUiItemsChanged( uiItems = marketsItemsData.items, isInErrorState = marketsItemsData.isInErrorState, isSearchNotFound = marketsItemsData.isSearchNotFound, - stakingNotificationMaxApy = marketsItemsData.stakingNotificationMaxApy?.takeUnless { - marketsItemsData.userCountry.getOrNull().needApplyFCARestrictions() + marketsNotificationUM = if (shouldShowYieldModePromo) { + 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() } + 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() { containerBottomSheetState.onEach { bottomSheetState -> if (bottomSheetState == BottomSheetState.EXPANDED) { @@ -302,10 +317,10 @@ internal class MarketsListModel @Inject constructor( }.saveIn(updateQuotesJob) } - private fun onStakingNotificationCloseClick() { - analyticsEventHandler.send(MarketsListAnalyticsEvent.StakingPromoClosed()) + private fun onYieldModeNotificationCloseClick() { + analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModePromoClosed()) modelScope.launch { - promoRepository.setMarketsStakingNotificationHideClicked() + promoRepository.setMarketsYieldSupplyNotificationHideClicked() } } @@ -313,7 +328,7 @@ internal class MarketsListModel @Inject constructor( val items: ImmutableList, val isInErrorState: Boolean, val isSearchNotFound: Boolean, - val stakingNotificationMaxApy: BigDecimal?, + val shouldShowYieldModePromo: Boolean, val userCountry: Either, ) } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsNotificationUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsNotificationUM.kt new file mode 100644 index 0000000000..5870289d78 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsNotificationUM.kt @@ -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, + ), + ) +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt index ec31aac834..2e0926eaf5 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt @@ -38,7 +38,7 @@ internal class MarketsTokenItemConverter( trendType = value.getTrendType(), chartData = value.getChartData(), isUnder100kMarketCap = value.isUnderMarketCapLimit, - stakingRate = value.stakingRate?.format { percent() }?.let { + stakingRate = value.yieldRate?.format { percent() }?.let { resourceReference(R.string.markets_apy_placeholder, wrappedList(it)) }, updateTimestamp = value.updateTimestamp, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListBatchFlowManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListBatchFlowManager.kt index 03b8b24eeb..4847ff333a 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListBatchFlowManager.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListBatchFlowManager.kt @@ -343,6 +343,7 @@ internal class MarketsListBatchFlowManager( SortByTypeUM.TopGainers -> TokenMarketListConfig.Order.TopGainers SortByTypeUM.TopLosers -> TokenMarketListConfig.Order.TopLosers SortByTypeUM.Staking -> TokenMarketListConfig.Order.Staking + SortByTypeUM.YieldSupply -> TokenMarketListConfig.Order.YieldSupply } } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt index f2c6ee64d3..33f89173a9 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt @@ -8,6 +8,7 @@ import com.tangem.core.ui.event.triggeredEvent import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.models.currency.CryptoCurrency 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.utils.Provider import kotlinx.collections.immutable.ImmutableList @@ -16,7 +17,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update -import java.math.BigDecimal @Stable @Suppress("LongParameterList") @@ -26,8 +26,6 @@ internal class MarketsListUMStateManager( private val visibleItemsChanged: (itemsKeys: List) -> Unit, private val onRetryButtonClicked: () -> Unit, private val onTokenClick: (MarketsListItemUM) -> Unit, - private val onStakingNotificationClick: () -> Unit, - private val onStakingNotificationCloseClick: () -> Unit, private val onShowTokensUnder100kClicked: () -> Unit, ) { @@ -92,25 +90,25 @@ internal class MarketsListUMStateManager( isInErrorState: Boolean, isSearchNotFound: Boolean, uiItems: ImmutableList, - stakingNotificationMaxApy: BigDecimal?, + marketsNotificationUM: MarketsNotificationUM?, ) { - state.update { + state.update { currentState -> when { isInErrorState -> { - it.copy( + currentState.copy( list = ListUM.LoadingError(onRetryClicked = onRetryButtonClicked), ) } isSearchNotFound -> { - it.copy(list = ListUM.SearchNothingFound) + currentState.copy(list = ListUM.SearchNothingFound) } uiItems.isEmpty() -> { - it.copy(list = ListUM.Loading) + currentState.copy(list = ListUM.Loading) } else -> { - it.updateItems( + currentState.updateItems( newItems = uiItems, - stakingNotificationMaxApy = stakingNotificationMaxApy, + marketsNotificationUM = marketsNotificationUM, ) } } @@ -119,7 +117,7 @@ internal class MarketsListUMStateManager( private fun MarketsListUM.updateItems( newItems: ImmutableList, - stakingNotificationMaxApy: BigDecimal?, + marketsNotificationUM: MarketsNotificationUM?, ): MarketsListUM { val currentState = this @@ -131,7 +129,7 @@ internal class MarketsListUMStateManager( .copy( showUnder100kTokensNotificationWasHidden = currentState.showUnder100kButtonAlreadyPressed(), ), - stakingNotificationMaxApy = stakingNotificationMaxApy, + marketsNotificationUM = marketsNotificationUM, ) } @@ -225,12 +223,7 @@ internal class MarketsListUMStateManager( onOptionClicked = ::onBottomSheetOptionClicked, ), ), - stakingNotificationMaxApy = null, - onStakingNotificationClick = { - onStakingNotificationClick() - selectedSortByType = SortByTypeUM.Staking - }, - onStakingNotificationCloseClick = onStakingNotificationCloseClick, + marketsNotificationUM = null, ) private fun onBottomSheetOptionClicked(sortByTypeUM: SortByTypeUM) { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt index 5b80958c46..0930d3c1e4 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.tooling.preview.Preview 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.entity.SearchBarUM 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.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.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.models.currency.CryptoCurrency 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.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.state.ListUM 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 kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import java.math.BigDecimal private const val SHOW_MORE_KEY = "privacyPolicy" @@ -180,38 +176,38 @@ private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modif ) } + val marketsNotification = state.marketsNotificationUM AnimatedVisibility( state.isInSearchMode.not() && - state.stakingNotificationMaxApy != null && - state.selectedSortBy != SortByTypeUM.Staking, + state.selectedSortBy != SortByTypeUM.YieldSupply, ) { val showMore = stringResourceSafe(R.string.common_show_more) - val description = stringResourceSafe( - R.string.markets_staking_banner_description_placeholder, - showMore, - ) - val clickableDescription = buildAnnotatedString { - append(description.substringBefore(showMore)) + when (marketsNotification) { + is MarketsNotificationUM.YieldSupplyPromo -> { + val description = stringResourceSafe( + R.string.markets_yield_supply_banner_description, + showMore, + ) - pushStringAnnotation(SHOW_MORE_KEY, "") - appendColored(showMore, TangemTheme.colors.text.accent) - pop() + val clickableDescription = annotatedReference { + append(description.substringBefore(showMore)) + + pushStringAnnotation(SHOW_MORE_KEY, "") + appendColored(showMore, TangemTheme.colors.text.accent) + pop() + } + + YieldSupplyInMarketsPromoNotification( + config = marketsNotification.config.copy( + subtitle = clickableDescription, + ), + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), + ) + } + else -> { /* no-op */ + } } - - 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), - ) } } } @@ -420,9 +416,10 @@ private fun Preview() { onDismissRequest = {}, content = SortByBottomSheetContentUM(selectedOption = SortByTypeUM.Rating) {}, ), - stakingNotificationMaxApy = BigDecimal(0.12345), - onStakingNotificationClick = {}, - onStakingNotificationCloseClick = {}, + marketsNotificationUM = MarketsNotificationUM.YieldSupplyPromo( + onClick = {}, + onCloseClick = {}, + ), ), onHeaderSizeChange = {}, bottomSheetState = BottomSheetState.EXPANDED, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/YieldSupplyInMarketsPromoNotification.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/YieldSupplyInMarketsPromoNotification.kt new file mode 100644 index 0000000000..e2f16f2735 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/YieldSupplyInMarketsPromoNotification.kt @@ -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 \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListUM.kt index da29b428d1..8404856a87 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListUM.kt @@ -8,8 +8,8 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.features.markets.impl.R +import com.tangem.features.markets.tokenlist.impl.model.MarketsNotificationUM import kotlinx.collections.immutable.ImmutableList -import java.math.BigDecimal internal data class MarketsListUM( val list: ListUM, @@ -19,9 +19,7 @@ internal data class MarketsListUM( val selectedInterval: TrendInterval, val onIntervalClick: (TrendInterval) -> Unit, val onSortByButtonClick: () -> Unit, - val stakingNotificationMaxApy: BigDecimal?, - val onStakingNotificationClick: () -> Unit, - val onStakingNotificationCloseClick: () -> Unit, + val marketsNotificationUM: MarketsNotificationUM?, ) { val isInSearchMode get() = searchBar.isActive @@ -40,6 +38,7 @@ enum class SortByTypeUM(val text: TextReference) { TopGainers(resourceReference(R.string.markets_sort_by_top_gainers_title)), TopLosers(resourceReference(R.string.markets_sort_by_top_losers_title)), Staking(resourceReference(R.string.common_staking)), + YieldSupply(resourceReference(R.string.yield_module_earn_sheet_title)), } @Immutable diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index aba0e7c275..8d1c6bd283 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -200,6 +200,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( onAlreadyUnlocked = {}, onUserCancelled = {}, analyticsEventHandler = analyticsEventHandler, + isFromUnlockAll = true, showMessage = uiMessageSender::send, ) } diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt index 249109f0e4..b5b89566f2 100644 --- a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt @@ -108,9 +108,10 @@ internal class WelcomeModel @Inject constructor( routedOut = true router.replaceAll(AppRoute.Wallet) } - .onLeft { - it.handle( + .onLeft { error -> + error.handle( specificWalletId = null, + isFromUnlockAll = true, onUserCancelled = { tryToUnlockWithAccessCodeRightAway() }, ) setSelectWalletState() @@ -146,8 +147,12 @@ internal class WelcomeModel @Inject constructor( .onRight { router.replaceAll(AppRoute.Wallet) } - .onLeft { - it.handle(null, onUserCancelled = { /* ignore */ }) + .onLeft { error -> + error.handle( + specificWalletId = null, + isFromUnlockAll = true, + onUserCancelled = { /* ignore */ }, + ) } } }, @@ -202,11 +207,19 @@ internal class WelcomeModel @Inject constructor( router.replaceAll(AppRoute.Wallet) } .onLeft { error -> - error.handle(specificWalletId = userWalletId, onUserCancelled = { /* ignore*/ }) + error.handle( + specificWalletId = userWalletId, + isFromUnlockAll = false, + onUserCancelled = { /* ignore*/ }, + ) } } - suspend fun UnlockWalletError.handle(specificWalletId: UserWalletId?, onUserCancelled: suspend () -> Unit = { }) { + suspend fun UnlockWalletError.handle( + specificWalletId: UserWalletId?, + isFromUnlockAll: Boolean, + onUserCancelled: suspend () -> Unit = { }, + ) { handle( onAlreadyUnlocked = { // this should not happen, as we check for locked state before this @@ -215,6 +228,7 @@ internal class WelcomeModel @Inject constructor( }, onUserCancelled = { onUserCancelled() }, analyticsEventHandler = analyticsEventHandler, + isFromUnlockAll = isFromUnlockAll, showMessage = uiMessageSender::send, ) }