Updated on 2026-08-14
This commit is contained in:
parent
571bf358bd
commit
afbdca53fe
16 changed files with 332 additions and 6 deletions
|
|
@ -13,6 +13,7 @@ interface ForYouComponent : ComposableModularBottomSheetContentComponent {
|
|||
|
||||
interface ForYouModelCallbacks {
|
||||
fun onTokenClick(userWalletId: UserWalletId, currency: CryptoCurrency)
|
||||
fun onAllEarnTokensClick()
|
||||
}
|
||||
|
||||
interface Factory : ComponentFactory<Params, ForYouComponent>
|
||||
|
|
|
|||
|
|
@ -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<BottomSheetState>) {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -62,6 +62,7 @@ internal sealed interface EarnOpportunitiesUM {
|
|||
@param:StringRes val subtitleRes: Int,
|
||||
val potentialReward: TextReference?,
|
||||
val potentialRewardType: TextReference?,
|
||||
val onAllEarnTokensClick: () -> Unit,
|
||||
) : EarnOpportunitiesUM
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ForYouComponent.Params>()
|
||||
|
|
@ -63,6 +76,13 @@ internal class ForYouModel @Inject constructor(
|
|||
private val expandedEarnOpportunitiesAssetIds = MutableStateFlow<Set<String>>(value = emptySet())
|
||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<ForYouBottomSheetConfig> = SlotNavigation()
|
||||
|
||||
var addToPortfolioManager: AddToPortfolioManager? = null
|
||||
private set
|
||||
|
||||
private var addToPortfolioManagerScope: CoroutineScope? = null
|
||||
|
||||
val uiState: StateFlow<ForYouUM>
|
||||
field = MutableStateFlow<ForYouUM>(
|
||||
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,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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<String, BigDecimal>,
|
||||
private val yieldStakingAvailability: Map<CryptoCurrency, StakingAvailability>,
|
||||
private val topEarnTokens: EarnTopToken?,
|
||||
private val onTokenClick: (UserWalletId?, CryptoCurrency, ForYouEarnOpportunitiesType) -> Unit,
|
||||
private val onAllEarnTokensClick: () -> Unit,
|
||||
) : Converter<AccountStatusList?, EarnOpportunitiesUM> {
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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<List<EarnOpportunities>, EarnOpportunitiesUM> {
|
||||
|
||||
override fun convert(value: List<EarnOpportunities>): 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String>,
|
||||
private val expandClick: (assetId: String) -> Unit,
|
||||
private val onTokenClick: (UserWalletId?, CryptoCurrency, ForYouEarnOpportunitiesType) -> Unit,
|
||||
private val onAllEarnTokensClick: () -> Unit,
|
||||
) : Converter<List<EarnOpportunities>, EarnOpportunitiesUM> {
|
||||
|
||||
private val rowConverter = ForYouEarnOpportunitiesTokenRowConverter(appCurrency = appCurrency)
|
||||
private val rowConverter = ForYouEarnOpportunitiesTokenRowConverter(
|
||||
appCurrency = appCurrency,
|
||||
userWalletId = userWalletId,
|
||||
onTokenClick = onTokenClick,
|
||||
)
|
||||
|
||||
override fun convert(value: List<EarnOpportunities>): 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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Pair<CryptoCurrencyStatus, EarnApyInfo>, 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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<List<EarnOpportunities>, EarnOpportunitiesUM> {
|
||||
|
||||
override fun convert(value: List<EarnOpportunities>): 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<EarnTokenWithCurrency, ForYouTokenListItemUM> {
|
||||
internal class ForYouEarnOpportunitiesTopTokenRowConverter(
|
||||
private val onTokenClick: (UserWalletId?, CryptoCurrency, ForYouEarnOpportunitiesType) -> Unit,
|
||||
) : Converter<EarnTokenWithCurrency, ForYouTokenListItemUM> {
|
||||
|
||||
private val iconConverter = CryptoCurrencyToIconStateConverter()
|
||||
|
||||
|
|
@ -57,7 +63,22 @@ internal class ForYouEarnOpportunitiesTopTokenRowConverter : Converter<EarnToken
|
|||
},
|
||||
),
|
||||
),
|
||||
onItemClick = {},
|
||||
onItemClick = {
|
||||
val type = when (earnToken.type) {
|
||||
EarnType.STAKING -> {
|
||||
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(),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue