Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-04 23:59:26 +05:00
parent 80ca0095f1
commit 7edea4da00
16 changed files with 467 additions and 160 deletions

View file

@ -5,7 +5,6 @@ import com.tangem.blockchain.common.TransactionData
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal
@Immutable
internal sealed class YieldSupplyFeeUM {
@ -31,5 +30,4 @@ internal data class YieldSupplyActionUM(
val yieldSupplyFeeUM: YieldSupplyFeeUM,
val isPrimaryButtonEnabled: Boolean,
val isTransactionSending: Boolean,
val maxFee: BigDecimal = BigDecimal.ZERO,
)

View file

@ -20,6 +20,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -91,6 +92,7 @@ private fun SupplyUnavailable(modifier: Modifier = Modifier) {
)
}
@Suppress("LongMethod")
@Composable
private fun SupplyContent(supplyUM: YieldSupplyUM.Content, modifier: Modifier = Modifier) {
Row(
@ -115,8 +117,11 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, modifier: Modifier =
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier.weight(1.0f, fill = false),
text = supplyUM.title.resolveReference(),
style = TangemTheme.typography.subtitle1,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors.text.primary1,
)
AnimatedVisibility(supplyUM.rewardsApy != TextReference.EMPTY) {
@ -344,17 +349,17 @@ private class PreviewProvider : PreviewParameterProvider<YieldSupplyUM> {
onClick = {},
),
YieldSupplyUM.Content(
title = stringReference("Aave lending is active"),
title = stringReference("Aave l"),
subtitle = stringReference("Interest accrues automatically"),
rewardsApy = stringReference("5.1 % APY"),
rewardsApy = stringReference("APY 5.1%"),
onClick = {},
apy = "5.1",
showWarningIcon = false,
),
YieldSupplyUM.Content(
title = stringReference("Aave lending is active"),
title = stringReference("Aave lending is active "),
subtitle = stringReference("Interest accrues automatically"),
rewardsApy = stringReference("5.1 % APY"),
rewardsApy = stringReference("APY 5.1%"),
onClick = {},
apy = "5.1",
showWarningIcon = true,

View file

@ -6,6 +6,8 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -30,7 +32,6 @@ import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.promo.entity.YieldSupplyPromoUM
import com.tangem.features.yield.supply.impl.promo.model.YieldSupplyPromoClickIntents
@Suppress("MagicNumber")
@Composable
internal fun YieldSupplyPromoContent(
yieldSupplyPromoUM: YieldSupplyPromoUM,
@ -38,50 +39,18 @@ internal fun YieldSupplyPromoContent(
modifier: Modifier = Modifier,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.background(color = TangemTheme.colors.background.tertiary)
.fillMaxWidth()
.fillMaxSize()
.imePadding()
.systemBarsPadding()
.padding(horizontal = 16.dp),
.systemBarsPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
YieldStatusAppBar(
onBackClick = clickIntents::onBackClick,
onHowItWorksClick = clickIntents::onHowItWorksClick,
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(horizontal = 20.dp),
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_analytics_up_24),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
modifier = Modifier
.background(TangemTheme.colors.icon.accent.copy(0.1f), CircleShape)
.padding(12.dp)
.size(32.dp),
)
SpacerH(20.dp)
Text(
text = yieldSupplyPromoUM.title.resolveReference(),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
SpacerH8()
Label(
state = LabelUM(
text = yieldSupplyPromoUM.subtitle,
style = LabelStyle.REGULAR,
icon = R.drawable.ic_information_24,
onIconClick = clickIntents::onApyInfoClick,
),
)
SpacerH32()
PromoItems()
}
SpacerHMax()
Content(yieldSupplyPromoUM = yieldSupplyPromoUM, clickIntents = clickIntents)
YieldSupplyTosText(
tosLink = yieldSupplyPromoUM.tosLink,
policyLink = yieldSupplyPromoUM.policyLink,
@ -90,16 +59,72 @@ internal fun YieldSupplyPromoContent(
PrimaryButton(
text = stringResourceSafe(R.string.yield_module_start_earning),
onClick = clickIntents::onStartEarningClick,
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.padding(
start = 16.dp,
end = 16.dp,
bottom = 8.dp,
)
.fillMaxWidth(),
)
}
}
@Suppress("MagicNumber")
@Composable
private fun ColumnScope.Content(yieldSupplyPromoUM: YieldSupplyPromoUM, clickIntents: YieldSupplyPromoClickIntents) {
Box(modifier = Modifier.weight(1f)) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(horizontal = 20.dp),
) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_analytics_up_24),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
modifier = Modifier
.background(TangemTheme.colors.icon.accent.copy(0.1f), CircleShape)
.padding(12.dp)
.size(32.dp),
)
SpacerH(20.dp)
Text(
text = yieldSupplyPromoUM.title.resolveReference(),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
SpacerH8()
Label(
state = LabelUM(
text = yieldSupplyPromoUM.subtitle,
style = LabelStyle.REGULAR,
icon = R.drawable.ic_information_24,
onIconClick = clickIntents::onApyInfoClick,
),
)
SpacerH32()
PromoItems()
}
SpacerH32()
}
Fade(
backgroundColor = TangemTheme.colors.background.tertiary,
modifier = Modifier.align(Alignment.BottomCenter),
)
SpacerH8()
}
}
@Composable
private fun YieldStatusAppBar(onBackClick: () -> Unit, onHowItWorksClick: () -> Unit) {
Row(
modifier = Modifier.padding(vertical = 16.dp),
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
@ -165,7 +190,12 @@ private fun PromoItem(@DrawableRes icon: Int, title: TextReference, subtitle: Te
}
@Composable
private fun YieldSupplyTosText(tosLink: String, policyLink: String, onClick: (String) -> Unit) {
private fun YieldSupplyTosText(
tosLink: String,
policyLink: String,
onClick: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val tosTitle = stringResourceSafe(R.string.common_terms_of_use)
val policyTitle = stringResourceSafe(R.string.common_privacy_policy)
val fullString = stringResourceSafe(id = R.string.yield_module_promo_screen_terms_disclaimer, tosTitle, policyTitle)
@ -204,7 +234,7 @@ private fun YieldSupplyTosText(tosLink: String, policyLink: String, onClick: (St
textAlign = TextAlign.Center,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.padding(16.dp),
)

View file

@ -248,7 +248,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
cryptoCurrencyStatus = cryptoStatus,
appCurrency = appCurrency,
feeValue = currentFee,
maxNetworkFee = maxFee,
maxNetworkFee = maxFee.second,
analyticsHandler = analyticsHandler,
),
)

View file

@ -13,7 +13,6 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
@ -62,8 +61,8 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger,
private val yieldSupplyAlertFactory: YieldSupplyAlertFactory,
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
private val yieldSupplyGetMaxFeeUseCase: YieldSupplyGetMaxFeeUseCase,
private val yieldSupplyRepository: YieldSupplyRepository,
) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
@ -131,11 +130,8 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
}
}
private suspend fun getMaxFee(): BigDecimal? {
if (uiState.value.maxFee != BigDecimal.ZERO) return uiState.value.maxFee
val yieldTokenStatus = yieldSupplyGetTokenStatusUseCase(cryptoCurrency as CryptoCurrency.Token)
.getOrNull()
return yieldTokenStatus?.maxFeeNative
private suspend fun getMaxFeePair(): Pair<BigDecimal, BigDecimal>? {
return yieldSupplyGetMaxFeeUseCase(userWallet, cryptoCurrencyStatus).getOrNull()
}
private suspend fun onLoadFee() {
@ -145,11 +141,10 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
}
val maxFee = if (uiState.value.maxFee == BigDecimal.ZERO) {
getMaxFee()
} else {
uiState.value.maxFee
} ?: return
val maxFeePair = getMaxFeePair() ?: return
val maxFee = maxFeePair.first
val maxFeeFiat = maxFeePair.second
val transactionListData = yieldSupplyStartEarningUseCase(
userWalletId = userWallet.walletId,
@ -190,6 +185,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
val feeSum = updatedTransactionList.sumOf {
it.fee?.amount?.value.orZero()
}
uiState.update(
YieldSupplyStartEarningFeeContentTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value,
@ -198,6 +194,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
updatedTransactionList = updatedTransactionList,
feeValue = feeSum,
maxNetworkFee = maxFee,
maxNetworkFeeFiat = maxFeeFiat,
minAmount = minAmount,
),
)

View file

@ -25,6 +25,7 @@ internal class YieldSupplyStartEarningFeeContentTransformer(
private val updatedTransactionList: List<TransactionData.Uncompiled>,
private val feeValue: BigDecimal,
private val maxNetworkFee: BigDecimal,
private val maxNetworkFeeFiat: BigDecimal,
private val minAmount: BigDecimal,
) : Transformer<YieldSupplyActionUM> {
override fun transform(prevState: YieldSupplyActionUM): YieldSupplyActionUM {
@ -42,14 +43,8 @@ internal class YieldSupplyStartEarningFeeContentTransformer(
val tokenFiatFee = feeFiat // same fiat amount
val tokenFiatFeeValueText = tokenFiatFee.format { fiat(appCurrency.code, appCurrency.symbol) }
val maxFiatFee = feeFiatRate?.let { rate ->
maxNetworkFee.multiply(rate)
}
val maxFeeCryptoValueText = tokenFiatRate?.let { rate ->
maxFiatFee?.divide(rate, cryptoCurrency.decimals, RoundingMode.HALF_UP)
}.format { crypto(cryptoCurrency) }
val maxFiatFeeValueText = maxFiatFee.format { fiat(appCurrency.code, appCurrency.symbol) }
val maxFeeCryptoValueText = maxNetworkFee.format { crypto(cryptoCurrency) }
val maxFiatFeeValueText = maxNetworkFeeFiat.format { fiat(appCurrency.code, appCurrency.symbol) }
val minAmountCryptoText = minAmount.format { crypto(cryptoCurrency) }
val minAmountFiat = tokenFiatRate?.let(minAmount::multiply)

View file

@ -86,7 +86,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
val uiState: StateFlow<YieldSupplyActionUM>
field: MutableStateFlow<YieldSupplyActionUM> = MutableStateFlow(
YieldSupplyActionUM(
title = resourceReference(R.string.yield_module_stop_earning),
title = resourceReference(R.string.yield_module_stop_earning_sheet_title),
subtitle = resourceReference(
id = R.string.yield_module_stop_earning_sheet_description,
formatArgs = wrappedList(cryptoCurrency.symbol),