From afbdca53fedfeba9fcc96611e0bcf58abe8ab153 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Jul 2026 16:11:23 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../feed/components/FeedEntryChildFactory.kt | 4 + .../tangem/features/foryou/ForYouComponent.kt | 1 + .../foryou/impl/DefaultForYouComponent.kt | 47 ++++++ .../impl/entity/ForYouBottomSheetConfig.kt | 12 ++ .../entity/ForYouEarnOpportunitiesType.kt | 31 ++++ .../features/foryou/impl/entity/ForYouUM.kt | 1 + .../features/foryou/impl/model/ForYouModel.kt | 135 ++++++++++++++++++ .../impl/model/converter/ForYouUtils.kt | 4 + .../ForYouEarnOpportunitiesConverter.kt | 16 +++ ...orYouEarnOpportunitiesNoTokensConverter.kt | 8 +- ...nOpportunitiesPotentialRewardsConverter.kt | 14 +- ...orYouEarnOpportunitiesTokenRowConverter.kt | 13 +- ...uEarnOpportunitiesTokensActiveConverter.kt | 8 +- ...ouEarnOpportunitiesTopTokenRowConverter.kt | 25 +++- .../foryou/impl/ui/ForYouEarnOpportunities.kt | 16 +++ .../ForYouEarnOpportunitiesPreviewData.kt | 3 + 16 files changed, 332 insertions(+), 6 deletions(-) create mode 100644 features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouBottomSheetConfig.kt create mode 100644 features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouEarnOpportunitiesType.kt diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt index 91e9077c31..48036e5d0a 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt @@ -174,6 +174,10 @@ internal class FeedEntryChildFactory @Inject constructor( token = TokenSummaryComponent.Token.Portfolio(currency), ) } + + override fun onAllEarnTokensClick() { + feedEntryClickIntents.onOpenEarnPage() + } }, ), ) diff --git a/features/for-you/api/src/main/kotlin/com/tangem/features/foryou/ForYouComponent.kt b/features/for-you/api/src/main/kotlin/com/tangem/features/foryou/ForYouComponent.kt index 3edc7a03c2..30822eed93 100644 --- a/features/for-you/api/src/main/kotlin/com/tangem/features/foryou/ForYouComponent.kt +++ b/features/for-you/api/src/main/kotlin/com/tangem/features/foryou/ForYouComponent.kt @@ -13,6 +13,7 @@ interface ForYouComponent : ComposableModularBottomSheetContentComponent { interface ForYouModelCallbacks { fun onTokenClick(userWalletId: UserWalletId, currency: CryptoCurrency) + fun onAllEarnTokensClick() } interface Factory : ComponentFactory diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/DefaultForYouComponent.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/DefaultForYouComponent.kt index a6323a1230..2c33e64590 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/DefaultForYouComponent.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/DefaultForYouComponent.kt @@ -12,12 +12,18 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child +import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.R import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState import com.tangem.core.ui.components.haze.hazeEffectTangem +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.core.ui.ds.topbar.TangemTopBar import com.tangem.core.ui.ds.topbar.TangemTopBarType import com.tangem.core.ui.extensions.clickableSingle @@ -25,7 +31,10 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.generated.icons.Icons import com.tangem.core.ui.res.generated.icons.ic_chevron_left_20 +import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent +import com.tangem.features.commonfeatures.api.managefunds.ManageFundsComponent import com.tangem.features.foryou.ForYouComponent +import com.tangem.features.foryou.impl.entity.ForYouBottomSheetConfig import com.tangem.features.foryou.impl.model.ForYouModel import com.tangem.features.foryou.impl.ui.ForYouContent import com.tangem.features.promobanners.api.PromoBannersBlockComponent @@ -37,6 +46,8 @@ internal class DefaultForYouComponent @AssistedInject constructor( @Assisted context: AppComponentContext, @Assisted params: ForYouComponent.Params, private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory, + private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory, + private val manageFundsComponentFactory: ManageFundsComponent.Factory, ) : AppComponentContext by context, ForYouComponent { private val model: ForYouModel = getOrCreateModel(params = params) @@ -51,6 +62,18 @@ internal class DefaultForYouComponent @AssistedInject constructor( ) } + private val bottomSheetSlot = childSlot( + source = model.bottomSheetNavigation, + serializer = null, + handleBackButton = false, + childFactory = { config, componentContext -> + when (config) { + ForYouBottomSheetConfig.AddToPortfolio -> portfolioSelectorChild(componentContext) + is ForYouBottomSheetConfig.ManageFunds -> manageFundsChild(config, componentContext) + } + }, + ) + @Composable override fun Title(bottomSheetState: State) { TangemTopBar( @@ -82,6 +105,7 @@ internal class DefaultForYouComponent @AssistedInject constructor( modifier: Modifier, ) { val uiState by model.uiState.collectAsStateWithLifecycle() + val bottomSheet by bottomSheetSlot.subscribeAsState() ForYouContent( forYouUM = uiState, @@ -90,8 +114,31 @@ internal class DefaultForYouComponent @AssistedInject constructor( contentPadding = contentPadding, modifier = modifier, ) + + bottomSheet.child?.instance?.BottomSheet() } + private fun portfolioSelectorChild(componentContext: ComponentContext): ComposableBottomSheetComponent = + addToPortfolioComponentFactory.create( + context = childByContext(componentContext), + params = AddToPortfolioComponent.Params( + addToPortfolioManager = checkNotNull(model.addToPortfolioManager) { + "addToPortfolioManager must be set before activating AddToPortfolio slot" + }, + ), + ) + + private fun manageFundsChild( + config: ForYouBottomSheetConfig.ManageFunds, + componentContext: ComponentContext, + ): ComposableBottomSheetComponent = manageFundsComponentFactory.create( + context = childByContext(componentContext), + params = ManageFundsComponent.Params( + launchMode = ManageFundsComponent.LaunchMode.FilteredByRawId(config.rawCurrencyId), + onDismiss = { model.bottomSheetNavigation.dismiss() }, + ), + ) + @AssistedFactory interface Factory : ForYouComponent.Factory { override fun create(context: AppComponentContext, params: ForYouComponent.Params): DefaultForYouComponent diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouBottomSheetConfig.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouBottomSheetConfig.kt new file mode 100644 index 0000000000..1b798d9b26 --- /dev/null +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouBottomSheetConfig.kt @@ -0,0 +1,12 @@ +package com.tangem.features.foryou.impl.entity + +import com.tangem.domain.models.currency.CryptoCurrency + +internal sealed interface ForYouBottomSheetConfig { + + data object AddToPortfolio : ForYouBottomSheetConfig + + data class ManageFunds( + val rawCurrencyId: CryptoCurrency.RawID, + ) : ForYouBottomSheetConfig +} \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouEarnOpportunitiesType.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouEarnOpportunitiesType.kt new file mode 100644 index 0000000000..cea8dd473d --- /dev/null +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouEarnOpportunitiesType.kt @@ -0,0 +1,31 @@ +package com.tangem.features.foryou.impl.entity + +import com.tangem.domain.staking.model.StakingIntegrationID + +/** + * The earn product behind an earn-opportunities row. Resolved once per token when the row is built + * and carried through the click callback, so the model knows which earn screen to open + * ([com.tangem.common.routing.AppRoute.YieldSupplyEntry] or [com.tangem.common.routing.AppRoute.Staking]) + * without re-resolving availability. + */ +internal sealed interface ForYouEarnOpportunitiesType { + + /** + * Yield supply (yield module) opportunity. + * + * @property apy rate in percent as received from the backend (e.g. "5.5" = 5.5%), passed as-is + * to the yield-supply entry route + */ + data class YieldSupply( + val apy: String, + ) : ForYouEarnOpportunitiesType + + /** + * Staking opportunity. + * + * @property integrationID staking integration to open the staking screen with + */ + data class Staking( + val integrationID: StakingIntegrationID, + ) : ForYouEarnOpportunitiesType +} \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouUM.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouUM.kt index 2291181eaf..de0075f610 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouUM.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/entity/ForYouUM.kt @@ -62,6 +62,7 @@ internal sealed interface EarnOpportunitiesUM { @param:StringRes val subtitleRes: Int, val potentialReward: TextReference?, val potentialRewardType: TextReference?, + val onAllEarnTokensClick: () -> Unit, ) : EarnOpportunitiesUM } diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouModel.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouModel.kt index 561d48003a..9c920731d9 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouModel.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/ForYouModel.kt @@ -4,6 +4,12 @@ import androidx.compose.runtime.Stable import arrow.core.getOrElse import arrow.core.left import arrow.core.right +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.arkivanov.decompose.router.slot.dismiss +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -24,6 +30,7 @@ import com.tangem.domain.models.earn.EarnTopToken import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager import com.tangem.features.foryou.ForYouComponent import com.tangem.features.foryou.impl.components.state.MarketChartUM import com.tangem.features.foryou.impl.entity.* @@ -38,7 +45,11 @@ import com.tangem.utils.coroutines.combine6 import com.tangem.utils.transformer.update import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.* +import kotlinx.coroutines.job import javax.inject.Inject @Stable @@ -49,12 +60,14 @@ internal class ForYouModel @Inject constructor( userWalletsListRepository: UserWalletsListRepository, multiAccountStatusListSupplier: MultiAccountStatusListSupplier, yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, + private val router: AppRouter, override val dispatchers: CoroutineDispatcherProvider, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getEarnTokensBatchFlowUseCase: GetEarnTokensBatchFlowUseCase, private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase, private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, private val earnErrorResolver: EarnErrorResolver, + private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory, ) : Model() { private val params = paramsContainer.require() @@ -63,6 +76,13 @@ internal class ForYouModel @Inject constructor( private val expandedEarnOpportunitiesAssetIds = MutableStateFlow>(value = emptySet()) private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() + val bottomSheetNavigation: SlotNavigation = SlotNavigation() + + var addToPortfolioManager: AddToPortfolioManager? = null + private set + + private var addToPortfolioManagerScope: CoroutineScope? = null + val uiState: StateFlow field = MutableStateFlow( ForYouUM( @@ -144,6 +164,8 @@ internal class ForYouModel @Inject constructor( topEarnTokens = topEarnTokens, expandedAssetIds = expandedEarnOpportunities, expandClick = ::onExpandEarnOpportunitiesClick, + onTokenClick = ::onEarnOpportunitiesTokenClick, + onAllEarnTokensClick = params.callbacks::onAllEarnTokensClick, ).convert(accountStatusList) uiState.update( @@ -207,6 +229,46 @@ internal class ForYouModel @Inject constructor( params.callbacks.onTokenClick(walletId, currency) } + private fun onEarnOpportunitiesTokenClick( + selectedWalletId: UserWalletId?, + currency: CryptoCurrency, + type: ForYouEarnOpportunitiesType, + ) { + when { + selectedWalletId != null -> openEarnScreen( + userWalletId = selectedWalletId, + currency = currency, + type = type, + ) + else -> { + // TODO For you make logic if not added add token, otherwise manage funds + // val token = RawMarketToken( + // id = currency.id.rawCurrencyId ?: return, + // name = currency.name, + // symbol = currency.symbol, + // ) + // val network = TokenMarketInfo.Network( + // networkId = currency.network.rawId, + // isExchangeable = false, + // contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress, + // decimalCount = currency.decimals, + // ) + // val manager = createAddToPortfolioManager().apply { + // setTokenParams(token) + // setTokenNetworks(listOf(network)) + // } + // addToPortfolioManager = manager + // Drop the slot through null so the same-source repeat click still recreates the child. + bottomSheetNavigation.dismiss() + bottomSheetNavigation.activate( + ForYouBottomSheetConfig.ManageFunds( + currency.id.rawCurrencyId ?: return, + ), + ) + } + } + } + private fun onExpandPortfolioReviewClick(assetId: String) { expandedPortfolioReviewAssetIds.update { ids -> if (assetId in ids) ids - assetId else ids + assetId @@ -228,4 +290,77 @@ internal class ForYouModel @Inject constructor( ) } } + + // TODO For you make logic if not added add token, otherwise manage funds + @Suppress("UnusedPrivateMember") + private fun createAddToPortfolioManager(): AddToPortfolioManager { + addToPortfolioManagerScope?.cancel() + val managerScope = CoroutineScope( + modelScope.coroutineContext + SupervisorJob(modelScope.coroutineContext.job), + ) + addToPortfolioManagerScope = managerScope + + val manager = addToPortfolioManagerFactory.create( + scope = managerScope, + settings = AddToPortfolioManager.Settings.Earn, + analyticsParams = AddToPortfolioManager.AnalyticsParams( + source = AnalyticsParam.ScreensSources.Markets.value, + ), + ).apply { + updateLaunchMode(AddToPortfolioManager.LaunchMode.ViaUserPortfolio) + } + + manager.onDismiss.receiveAsFlow() + .onEach { bottomSheetNavigation.dismiss() } + .launchIn(managerScope) + manager.onSuccessAdded.receiveAsFlow() + .onEach { bottomSheetNavigation.dismiss() } + .onEach { result -> + router.push( + AppRoute.CurrencyDetails( + userWalletId = result.wallet.walletId, + currency = result.addedCurrency.currency, + ), + ) + } + .launchIn(managerScope) + manager.onAddedTokenClick.receiveAsFlow() + .onEach { bottomSheetNavigation.dismiss() } + .onEach { result -> + router.push( + AppRoute.CurrencyDetails( + userWalletId = result.wallet.walletId, + currency = result.addedCurrency.currency, + ), + ) + } + .launchIn(managerScope) + + return manager + } + + private fun openEarnScreen( + userWalletId: UserWalletId, + currency: CryptoCurrency, + type: ForYouEarnOpportunitiesType, + ) { + router.push( + when (type) { + is ForYouEarnOpportunitiesType.Staking -> { + AppRoute.Staking( + userWalletId = userWalletId, + cryptoCurrency = currency, + integrationId = type.integrationID, + ) + } + is ForYouEarnOpportunitiesType.YieldSupply -> { + AppRoute.YieldSupplyEntry( + userWalletId = userWalletId, + cryptoCurrency = currency, + apy = type.apy, + ) + } + }, + ) + } } \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouUtils.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouUtils.kt index d667614973..a5afe20680 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouUtils.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/ForYouUtils.kt @@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.models.account.Account import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.features.foryou.impl.entity.ForYouEarnOpportunitiesType import com.tangem.utils.extensions.isZero import java.math.BigDecimal import java.math.RoundingMode @@ -62,11 +63,14 @@ internal fun forYouPlaceholderBadge(): TangemBadgeUM = TangemBadgeUM( * @property isActive whether the user already earns on the token (active yield supply or stake) * @property apy rate as a fraction (0.05 = 5%) * @property potentialRewards projected yearly reward in fiat (`fiatAmount * apy`), `null` when unknown + * @property type which earn product the rate belongs to; passed to the click callback so the model + * can open the matching earn screen */ internal data class EarnApyInfo( val isActive: Boolean, val apy: BigDecimal?, val potentialRewards: BigDecimal?, + val type: ForYouEarnOpportunitiesType, ) /** diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesConverter.kt index 8f4a348934..ebd873738c 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesConverter.kt @@ -8,12 +8,15 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.yieldSupplyKey import com.tangem.domain.models.earn.EarnTopToken import com.tangem.domain.models.staking.StakingBalance +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.staking.model.StakingAvailability +import com.tangem.domain.staking.model.StakingIntegrationID import com.tangem.domain.staking.model.StakingOption import com.tangem.domain.staking.model.common.RewardInfo import com.tangem.domain.staking.model.common.RewardType import com.tangem.domain.staking.model.optionOrNull import com.tangem.features.foryou.impl.entity.EarnOpportunitiesUM +import com.tangem.features.foryou.impl.entity.ForYouEarnOpportunitiesType import com.tangem.features.foryou.impl.model.converter.EarnApyInfo import com.tangem.features.foryou.impl.model.converter.EarnOpportunities import com.tangem.features.foryou.impl.model.converter.PERCENT_BASE @@ -45,6 +48,8 @@ internal class ForYouEarnOpportunitiesConverter( private val yieldSupplyAvailability: Map, private val yieldStakingAvailability: Map, private val topEarnTokens: EarnTopToken?, + private val onTokenClick: (UserWalletId?, CryptoCurrency, ForYouEarnOpportunitiesType) -> Unit, + private val onAllEarnTokensClick: () -> Unit, ) : Converter { override fun convert(value: AccountStatusList?): EarnOpportunitiesUM { @@ -86,19 +91,26 @@ internal class ForYouEarnOpportunitiesConverter( data.isEmpty() -> { ForYouEarnOpportunitiesNoTokensConverter( topEarnTokens = topEarnTokens, + onTokenClick = onTokenClick, + onAllEarnTokensClick = onAllEarnTokensClick, ).convert(data) } data.all { earn -> earn.earnCurrencies.all { entry -> entry.value.isActive } } -> { ForYouEarnOpportunitiesTokensActiveConverter( topEarnTokens = topEarnTokens, + onTokenClick = onTokenClick, + onAllEarnTokensClick = onAllEarnTokensClick, ).convert(data) } else -> { ForYouEarnOpportunitiesPotentialRewardsConverter( appCurrency = appCurrency, + userWalletId = value?.userWalletId, isAccountsModeEnabled = isAccountsModeEnabled, expandedAssetIds = expandedAssetIds, expandClick = expandClick, + onTokenClick = onTokenClick, + onAllEarnTokensClick = onAllEarnTokensClick, ).convert(data) } } @@ -129,6 +141,7 @@ internal class ForYouEarnOpportunitiesConverter( isActive = isActive, potentialRewards = cryptoCurrencyStatus.value.fiatAmount?.multiply(apy), apy = apy, + type = ForYouEarnOpportunitiesType.YieldSupply(yieldSupplyApy.toPlainString()), ) } } @@ -143,6 +156,7 @@ internal class ForYouEarnOpportunitiesConverter( isActive = stakingInfo.isActive, apy = stakingInfo.rate, potentialRewards = cryptoCurrencyStatus.value.fiatAmount?.multiply(stakingInfo.rate), + type = ForYouEarnOpportunitiesType.Staking(integrationID = stakingInfo.integrationId), ) } } @@ -205,6 +219,7 @@ internal class ForYouEarnOpportunitiesConverter( rate = rateInfo.rate, isActive = isActive, rewardType = rateInfo.type, + integrationId = option.integrationId, ) } @@ -212,5 +227,6 @@ internal class ForYouEarnOpportunitiesConverter( val rate: BigDecimal, val isActive: Boolean, val rewardType: RewardType, + val integrationId: StakingIntegrationID, ) } \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesNoTokensConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesNoTokensConverter.kt index 2f363e16d9..06f1d35a1f 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesNoTokensConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesNoTokensConverter.kt @@ -5,7 +5,10 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.utils.parseBigDecimalOrNull +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.earn.EarnTopToken +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.features.foryou.impl.entity.ForYouEarnOpportunitiesType import com.tangem.features.foryou.impl.entity.EarnOpportunitiesUM import com.tangem.features.foryou.impl.model.converter.EarnOpportunities import com.tangem.features.foryou.impl.model.converter.FOR_YOU_TOP_EARN_TOKENS_COUNT @@ -20,6 +23,8 @@ import kotlinx.collections.immutable.toPersistentList */ internal class ForYouEarnOpportunitiesNoTokensConverter( private val topEarnTokens: EarnTopToken?, + private val onTokenClick: (UserWalletId?, CryptoCurrency, ForYouEarnOpportunitiesType) -> Unit, + private val onAllEarnTokensClick: () -> Unit, ) : Converter, EarnOpportunitiesUM> { override fun convert(value: List): EarnOpportunitiesUM { @@ -29,7 +34,7 @@ internal class ForYouEarnOpportunitiesNoTokensConverter( val topEarnToken = topEarnTokenList?.firstOrNull()?.earnToken val topEarnApy = topEarnToken?.apy?.parseBigDecimalOrNull() - val rowConverter = ForYouEarnOpportunitiesTopTokenRowConverter() + val rowConverter = ForYouEarnOpportunitiesTopTokenRowConverter(onTokenClick = onTokenClick) return EarnOpportunitiesUM.Content( tokenList = topEarnTokenList @@ -39,6 +44,7 @@ internal class ForYouEarnOpportunitiesNoTokensConverter( subtitleRes = R.string.for_you_earn_opportunities_no_available_tokens, potentialReward = stringReference(topEarnApy.format { percent() }), potentialRewardType = topEarnToken?.rewardType?.name?.let(::stringReference), + onAllEarnTokensClick = onAllEarnTokensClick, ) } } \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesPotentialRewardsConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesPotentialRewardsConverter.kt index b93413e988..2af52de079 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesPotentialRewardsConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesPotentialRewardsConverter.kt @@ -11,7 +11,10 @@ import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.account.Account +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.features.foryou.impl.entity.EarnOpportunitiesUM +import com.tangem.features.foryou.impl.entity.ForYouEarnOpportunitiesType import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM import com.tangem.features.foryou.impl.model.converter.EarnOpportunities import com.tangem.utils.StringsSigns @@ -29,14 +32,22 @@ import java.math.BigDecimal * [ForYouEarnOpportunitiesTokenRowConverter], expansion keyed by account id); with it off, the * tokens are rendered as flat non-expandable rows. */ +@Suppress("LongParameterList") internal class ForYouEarnOpportunitiesPotentialRewardsConverter( private val appCurrency: AppCurrency, + private val userWalletId: UserWalletId?, private val isAccountsModeEnabled: Boolean, private val expandedAssetIds: Set, private val expandClick: (assetId: String) -> Unit, + private val onTokenClick: (UserWalletId?, CryptoCurrency, ForYouEarnOpportunitiesType) -> Unit, + private val onAllEarnTokensClick: () -> Unit, ) : Converter, EarnOpportunitiesUM> { - private val rowConverter = ForYouEarnOpportunitiesTokenRowConverter(appCurrency = appCurrency) + private val rowConverter = ForYouEarnOpportunitiesTokenRowConverter( + appCurrency = appCurrency, + userWalletId = userWalletId, + onTokenClick = onTokenClick, + ) override fun convert(value: List): EarnOpportunitiesUM { val totalPotentialReward = value.sumOf { it.accountPotentialReward } @@ -82,6 +93,7 @@ internal class ForYouEarnOpportunitiesPotentialRewardsConverter( subtitleRes = R.string.for_you_earn_opportunities_tokens_rewards, potentialReward = totalPotentialRewardText, potentialRewardType = null, + onAllEarnTokensClick = onAllEarnTokensClick, ) } diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokenRowConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokenRowConverter.kt index cb60890b9d..55f47be1bd 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokenRowConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokenRowConverter.kt @@ -14,7 +14,10 @@ import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.features.foryou.impl.entity.ForYouEarnOpportunitiesType import com.tangem.features.foryou.impl.model.converter.EarnApyInfo import com.tangem.utils.StringsSigns import com.tangem.utils.converter.Converter @@ -31,6 +34,8 @@ import java.math.BigDecimal */ internal class ForYouEarnOpportunitiesTokenRowConverter( private val appCurrency: AppCurrency, + private val userWalletId: UserWalletId?, + private val onTokenClick: (UserWalletId?, CryptoCurrency, ForYouEarnOpportunitiesType) -> Unit, ) : Converter, TangemTokenRowUM> { private val iconConverter = CryptoCurrencyToIconStateConverter() @@ -55,7 +60,13 @@ internal class ForYouEarnOpportunitiesTokenRowConverter( ), topEndContentUM = toRowTopEnd(cryptoCurrencyStatus, possibleEarnAmount), bottomEndContentUM = toRowBottomEnd(cryptoCurrencyStatus, earnApyInfo.apy.orZero()), - onItemClick = null, + onItemClick = { + onTokenClick( + userWalletId, + cryptoCurrencyStatus.currency, + earnApyInfo.type, + ) + }, onItemLongClick = null, ) } diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokensActiveConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokensActiveConverter.kt index 2c0aaadc55..bd4b3e743d 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokensActiveConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTokensActiveConverter.kt @@ -1,8 +1,11 @@ package com.tangem.features.foryou.impl.model.converter.earnOpportunities import com.tangem.common.ui.R +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.earn.EarnTopToken +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.features.foryou.impl.entity.EarnOpportunitiesUM +import com.tangem.features.foryou.impl.entity.ForYouEarnOpportunitiesType import com.tangem.features.foryou.impl.model.converter.EarnOpportunities import com.tangem.features.foryou.impl.model.converter.FOR_YOU_TOP_EARN_TOKENS_COUNT import com.tangem.features.foryou.impl.model.converter.forYouEarnAssetKey @@ -20,6 +23,8 @@ import kotlinx.collections.immutable.toPersistentList */ internal class ForYouEarnOpportunitiesTokensActiveConverter( private val topEarnTokens: EarnTopToken?, + private val onTokenClick: (UserWalletId?, CryptoCurrency, ForYouEarnOpportunitiesType) -> Unit, + private val onAllEarnTokensClick: () -> Unit, ) : Converter, EarnOpportunitiesUM> { override fun convert(value: List): EarnOpportunitiesUM { @@ -28,7 +33,7 @@ internal class ForYouEarnOpportunitiesTokensActiveConverter( .map { status -> status.currency.forYouEarnAssetKey() } .toSet() - val rowConverter = ForYouEarnOpportunitiesTopTokenRowConverter() + val rowConverter = ForYouEarnOpportunitiesTopTokenRowConverter(onTokenClick) return EarnOpportunitiesUM.Content( tokenList = topEarnTokens?.getOrNull() @@ -40,6 +45,7 @@ internal class ForYouEarnOpportunitiesTokensActiveConverter( subtitleRes = R.string.for_you_earn_opportunities_all_tokens_active, potentialReward = null, potentialRewardType = null, + onAllEarnTokensClick = onAllEarnTokensClick, ) } } \ No newline at end of file diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTopTokenRowConverter.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTopTokenRowConverter.kt index c8db01a057..47359ddc9c 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTopTokenRowConverter.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/model/converter/earnOpportunities/ForYouEarnOpportunitiesTopTokenRowConverter.kt @@ -10,8 +10,12 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.percent +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.earn.EarnTokenWithCurrency import com.tangem.domain.models.earn.EarnType +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.staking.model.StakingIntegrationID +import com.tangem.features.foryou.impl.entity.ForYouEarnOpportunitiesType import com.tangem.features.foryou.impl.entity.ForYouTokenListItemUM import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.persistentListOf @@ -23,7 +27,9 @@ import java.math.BigDecimal * converters that surface suggestions ([ForYouEarnOpportunitiesNoTokensConverter], * [ForYouEarnOpportunitiesTokensActiveConverter]). */ -internal class ForYouEarnOpportunitiesTopTokenRowConverter : Converter { +internal class ForYouEarnOpportunitiesTopTokenRowConverter( + private val onTokenClick: (UserWalletId?, CryptoCurrency, ForYouEarnOpportunitiesType) -> Unit, +) : Converter { private val iconConverter = CryptoCurrencyToIconStateConverter() @@ -57,7 +63,22 @@ internal class ForYouEarnOpportunitiesTopTokenRowConverter : Converter { + val integrationID = StakingIntegrationID.create(currencyId = value.cryptoCurrency.id) + ?: return@Content + + ForYouEarnOpportunitiesType.Staking(integrationID = integrationID) + } + EarnType.YIELD -> ForYouEarnOpportunitiesType.YieldSupply(apy = earnToken.apy) + } + onTokenClick( + null, + value.cryptoCurrency, + type, + ) + }, onItemLongClick = { _, _ -> }, ), tokenList = persistentListOf(), diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouEarnOpportunities.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouEarnOpportunities.kt index cb513e9ac9..0edc0676d6 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouEarnOpportunities.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/ForYouEarnOpportunities.kt @@ -3,6 +3,7 @@ package com.tangem.features.foryou.impl.ui import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.InlineTextContent @@ -19,6 +20,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp +import com.tangem.core.ui.ds2.button.TangemButton import com.tangem.core.ui.ds2.shimmers.TangemShimmer import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme @@ -84,6 +86,20 @@ internal fun ForYouEarnOpportunities(earnOpportunitiesUM: EarnOpportunitiesUM, m tokenList = earnOpportunitiesUM.tokenList, modifier = Modifier.padding(top = 8.dp), ) + + if (earnOpportunitiesUM is EarnOpportunitiesUM.Content) { + TangemButton( + text = stringReference("Explore all tokens"), // todo FOR YOU lokalize + variant = TangemButton.Variant.Secondary, + size = TangemButton.Size.X9, + isEnabled = true, + contentDescription = "Explore all tokens", // todo FOR YOU lokalize + onClick = earnOpportunitiesUM.onAllEarnTokensClick, + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + ) + } } } diff --git a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/preview/ForYouEarnOpportunitiesPreviewData.kt b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/preview/ForYouEarnOpportunitiesPreviewData.kt index 400a57c04b..f8f20dc8f4 100644 --- a/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/preview/ForYouEarnOpportunitiesPreviewData.kt +++ b/features/for-you/impl/src/main/java/com/tangem/features/foryou/impl/ui/preview/ForYouEarnOpportunitiesPreviewData.kt @@ -36,6 +36,7 @@ internal object ForYouEarnOpportunitiesPreviewData { bottomEnd = "6,2%", ), ), + onAllEarnTokensClick = {}, ) /** No earnable tokens in the portfolio: top earn tokens teaser. */ @@ -52,6 +53,7 @@ internal object ForYouEarnOpportunitiesPreviewData { bottomEnd = "Staking", ), ), + onAllEarnTokensClick = {}, ) /** Every earnable token is already earning: plain subtitle, no reward badge, no rows. */ @@ -60,6 +62,7 @@ internal object ForYouEarnOpportunitiesPreviewData { potentialReward = null, potentialRewardType = null, tokenList = persistentListOf(), + onAllEarnTokensClick = {}, ) val loading = EarnOpportunitiesUM.Loading(