Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-10 12:28:38 +05:00
parent d30713a914
commit cfd8ae2957
13 changed files with 209 additions and 140 deletions

View file

@ -38,9 +38,8 @@ internal class DefaultYieldSupplyComponent @AssistedInject constructor(
override fun Content(modifier: Modifier) {
val yieldSupplyUM by model.uiState.collectAsStateWithLifecycle()
val bottomSheet by bottomSheetSlot.subscribeAsState()
val isBalanceHidden by model.isBalanceHiddenFlow.collectAsStateWithLifecycle()
YieldSupplyBlockContent(yieldSupplyUM = yieldSupplyUM, isBalanceHidden = isBalanceHidden, modifier = modifier)
YieldSupplyBlockContent(yieldSupplyUM = yieldSupplyUM, modifier = modifier)
bottomSheet.child?.instance?.BottomSheet()
}

View file

@ -18,7 +18,8 @@ internal sealed class YieldSupplyUM {
data object Unavailable : YieldSupplyUM()
data class Content(
val rewardsBalance: TextReference,
val title: TextReference,
val subtitle: TextReference,
val rewardsApy: TextReference,
val onClick: () -> Unit,
val isAllowedToSpend: Boolean,

View file

@ -7,12 +7,15 @@ import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
@ -20,6 +23,7 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyIsAvailableUseCase
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
import com.tangem.features.yield.supply.impl.main.model.transformers.YieldSupplyTokenStatusSuccessTransformer
@ -72,6 +76,8 @@ internal class YieldSupplyModel @Inject constructor(
val isBalanceHiddenFlow: StateFlow<Boolean>
field = MutableStateFlow(false)
private var lastYieldSupplyStatus: YieldSupplyStatus? = null
init {
checkIfYieldSupplyIsAvailable()
}
@ -100,7 +106,7 @@ internal class YieldSupplyModel @Inject constructor(
maybeCryptoCurrency.fold(
ifRight = { cryptoCurrencyStatus ->
cryptoCurrencyStatusFlow.update { cryptoCurrencyStatus }
onDataLoaded(cryptoCurrencyStatus)
onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus)
},
ifLeft = {
Timber.w(it.toString())
@ -128,6 +134,7 @@ internal class YieldSupplyModel @Inject constructor(
),
)
}.onLeft {
Timber.e(it)
uiState.update { YieldSupplyUM.Unavailable }
}
}
@ -157,50 +164,76 @@ internal class YieldSupplyModel @Inject constructor(
.launchIn(modelScope)
}
private fun onDataLoaded(cryptoCurrencyStatus: CryptoCurrencyStatus) {
@Suppress("MaximumLineLength")
private fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) {
val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
val hasActiveTransaction = cryptoCurrencyStatus.value.hasCurrentNetworkTransactions
val yieldTransaction = cryptoCurrencyStatus.value.pendingTransactions.firstOrNull {
it.type is TxInfo.TransactionType.YieldSupply
}?.type as? TxInfo.TransactionType.YieldSupply
sendInfoAboutProtocolStatus(yieldSupplyStatus?.isActive == true)
sendInfoAboutProtocolStatus(cryptoCurrencyStatus)
val yieldSupplyUM = when {
when {
hasActiveTransaction && yieldTransaction != null -> {
coroutineScope.launch(dispatchers.io) {
delay(PROCESSING_UPDATE_DELAY)
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id)
}
when (yieldTransaction) {
TxInfo.TransactionType.YieldSupply.Enter -> YieldSupplyUM.Processing.Enter
TxInfo.TransactionType.YieldSupply.Exit -> YieldSupplyUM.Processing.Exit
uiState.update {
when (yieldTransaction) {
TxInfo.TransactionType.YieldSupply.Enter -> YieldSupplyUM.Processing.Enter
TxInfo.TransactionType.YieldSupply.Exit -> YieldSupplyUM.Processing.Exit
}
}
}
yieldSupplyStatus?.isActive == true -> {
val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
modelScope.launch(dispatchers.default) {
yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken)
.onRight { tokenStatus ->
uiState.update {
YieldSupplyUM.Content(
title = resourceReference(
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
),
subtitle = resourceReference(
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_subtitle,
),
rewardsApy = combinedReference(
resourceReference(
R.string.yield_module_token_details_earn_notification_apy,
),
stringReference(tokenStatus.apy.toString() + "%"),
),
onClick = ::onActiveClick,
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
)
}
}.onLeft {
Timber.e(it)
uiState.update { YieldSupplyUM.Loading }
}
}
}
yieldSupplyStatus?.isActive == true ->
YieldSupplyUM.Content(
rewardsBalance = TextReference.EMPTY,
rewardsApy = TextReference.EMPTY,
onClick = ::onActiveClick,
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
)
else -> YieldSupplyUM.Initial
}
uiState.update { yieldSupplyUM }
when (yieldSupplyUM) {
is YieldSupplyUM.Initial -> loadTokenStatus()
else -> Unit
else -> {
loadTokenStatus()
}
}
}
private fun sendInfoAboutProtocolStatus(isActivated: Boolean) {
private fun sendInfoAboutProtocolStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) {
if (lastYieldSupplyStatus == cryptoCurrencyStatus.value.yieldSupplyStatus) return
val token = cryptoCurrency as? CryptoCurrency.Token ?: return
modelScope.launch(dispatchers.default) {
if (isActivated) {
yieldSupplyActivateUseCase(token)
if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == true) {
yieldSupplyActivateUseCase(token).onRight {
lastYieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
}
} else {
yieldSupplyDeactivateUseCase(token)
yieldSupplyDeactivateUseCase(token).onRight {
lastYieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus
}
}
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.yield.supply.impl.main.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@ -17,12 +18,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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.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.components.SecondaryButton
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
@ -34,11 +37,7 @@ import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM
import com.tangem.utils.StringsSigns
@Composable
internal fun YieldSupplyBlockContent(
yieldSupplyUM: YieldSupplyUM,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
internal fun YieldSupplyBlockContent(yieldSupplyUM: YieldSupplyUM, modifier: Modifier = Modifier) {
AnimatedContent(
targetState = yieldSupplyUM,
modifier = modifier,
@ -46,7 +45,7 @@ internal fun YieldSupplyBlockContent(
when (supplyUM) {
is YieldSupplyUM.Available -> SupplyAvailable(supplyUM)
YieldSupplyUM.Loading -> SupplyLoading()
is YieldSupplyUM.Content -> SupplyContent(supplyUM, isBalanceHidden)
is YieldSupplyUM.Content -> SupplyContent(supplyUM)
YieldSupplyUM.Processing.Enter -> SupplyProcessing(
resourceReference(R.string.yield_module_token_details_earn_notification_processing),
)
@ -87,7 +86,7 @@ private fun SupplyUnavailable() {
}
@Composable
private fun SupplyContent(supplyUM: YieldSupplyUM.Content, isBalanceHidden: Boolean) {
private fun SupplyContent(supplyUM: YieldSupplyUM.Content) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
@ -96,22 +95,21 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, isBalanceHidden: Bool
.clickable(onClick = supplyUM.onClick)
.padding(12.dp),
) {
Image(
painter = painterResource(R.drawable.img_aave_22),
modifier = Modifier.size(36.dp),
contentDescription = null,
)
SpacerW12()
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.weight(1f),
) {
Text(
text = stringResourceSafe(
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = supplyUM.rewardsBalance.orMaskWithStars(isBalanceHidden).resolveReference(),
text = supplyUM.title.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
@ -123,9 +121,15 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, isBalanceHidden: Bool
Text(
text = supplyUM.rewardsApy.resolveReference(),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
color = TangemTheme.colors.text.accent,
)
}
Text(
text = supplyUM.subtitle.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
)
}
SpacerW8()
AnimatedVisibility(supplyUM.isAllowedToSpend.not()) {
@ -146,36 +150,42 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, isBalanceHidden: Bool
@Composable
private fun SupplyProcessing(text: TextReference) {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.background.primary)
.padding(12.dp),
) {
Text(
text = stringResourceSafe(
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
Image(
painter = painterResource(R.drawable.img_aave_22),
modifier = Modifier.size(36.dp),
contentDescription = null,
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
SpacerW12()
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.weight(1f),
) {
Text(
text = text.resolveReference(),
style = TangemTheme.typography.body1,
text = stringResourceSafe(
R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title,
),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
color = TangemTheme.colors.icon.accent,
strokeWidth = 2.dp,
Text(
text = text.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
)
}
SpacerW8()
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = TangemTheme.colors.icon.accent,
strokeWidth = 2.dp,
)
}
}
@ -286,7 +296,7 @@ private fun SupplyInfo(
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun YieldSupplyBlockContent_Preview(@PreviewParameter(PreviewProvider::class) params: YieldSupplyUM) {
TangemThemePreview {
YieldSupplyBlockContent(params, true)
YieldSupplyBlockContent(yieldSupplyUM = params, modifier = Modifier)
}
}
@ -301,13 +311,15 @@ private class PreviewProvider : PreviewParameterProvider<YieldSupplyUM> {
onClick = {},
),
YieldSupplyUM.Content(
rewardsBalance = stringReference("1 USDT"),
title = stringReference("Aave lending is active"),
subtitle = stringReference("Interest accrues automatically"),
rewardsApy = stringReference("5.1 % APY"),
onClick = {},
isAllowedToSpend = false,
),
YieldSupplyUM.Content(
rewardsBalance = stringReference("1 USDT"),
title = stringReference("Aave lending is active"),
subtitle = stringReference("Interest accrues automatically"),
rewardsApy = stringReference("5.1 % APY"),
onClick = {},
isAllowedToSpend = true,

View file

@ -56,17 +56,6 @@ internal fun YieldSupplyActiveContent(
.fillMaxWidth()
.padding(12.dp),
) {
Text(
text = stringResourceSafe(R.string.yield_module_earn_sheet_total_earnings_title),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
ResizableText(
text = state.totalEarnings.orMaskWithStars(isBalanceHidden).resolveReference(),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
CurrentApy(state.apy)
chartComponent.Content(Modifier.padding(bottom = 12.dp))
}
@ -85,22 +74,23 @@ internal fun YieldSupplyActiveContent(
@Composable
private fun CurrentApy(apy: TextReference?, modifier: Modifier = Modifier) {
Row(modifier = modifier.padding(vertical = 12.dp), verticalAlignment = Alignment.CenterVertically) {
Column(modifier = modifier.padding(vertical = 12.dp)) {
Text(
modifier = modifier.weight(1.0f),
modifier = Modifier,
text = stringResourceSafe(R.string.yield_module_earn_sheet_current_apy_title),
style = TangemTheme.typography.body1,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
AnimatedContent(
modifier = Modifier.height(32.dp),
targetState = apy?.resolveReference(),
label = "CurrentApy",
) { apyText ->
if (apyText == null) {
TextShimmer(
modifier = modifier.width(56.dp),
modifier = modifier.width(94.dp),
text = "",
style = TangemTheme.typography.body1,
style = TangemTheme.typography.head,
)
} else {
Row(verticalAlignment = Alignment.CenterVertically) {
@ -108,7 +98,7 @@ private fun CurrentApy(apy: TextReference?, modifier: Modifier = Modifier) {
painterResource(R.drawable.ic_arrow_up_8),
tint = TangemTheme.colors.text.accent,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp),
modifier = Modifier.padding(end = 6.dp).size(12.dp),
)
Text(
modifier = modifier,
@ -260,6 +250,7 @@ private class YieldSupplyActiveBottomSheetPreviewProvider : PreviewParameterProv
),
subtitleLink = resourceReference(R.string.common_read_more),
notificationUM = NotificationUM.Error.InvalidAmount,
apy = stringReference("5,14%"),
),
)
}