Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-29 23:05:18 +03:00
parent 3b04938e54
commit d2bef0bf1a
13 changed files with 557 additions and 8 deletions

View file

@ -87,6 +87,7 @@ dependencies {
implementation(projects.domain.txhistory.models)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
implementation(projects.domain.yieldSupply)
/** Temp dependency to swap domain */
implementation(projects.features.swap.domain)

View file

@ -124,6 +124,7 @@ internal object TokenDetailsPreviewData {
selectedBalanceType = BalanceType.ALL,
onBalanceSelect = {},
displayCryptoBalance = "966,96 XLM",
displayYeildSupplyCryptoBalance = null,
displayFiatBalance = "91,50$",
isBalanceSelectorEnabled = true,
isBalanceFlickering = false,

View file

@ -40,6 +40,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TokenReceiveNotification
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
@ -74,6 +75,7 @@ import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetRewardsBalanceUseCase
import com.tangem.feature.tokendetails.deeplink.TokenDetailsDeepLinkActionListener
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
import com.tangem.feature.tokendetails.presentation.tokendetails.analytics.TokenDetailsCurrencyStatusAnalyticsSender
@ -151,6 +153,7 @@ internal class TokenDetailsModel @Inject constructor(
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getAccountCryptoCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase,
private val yieldSupplyGetRewardsBalanceUseCase: YieldSupplyGetRewardsBalanceUseCase,
) : Model(), TokenDetailsClickIntents, YieldSupplyDepositedWarningComponent.ModelCallback {
private val params = paramsContainer.require<TokenDetailsComponent.Params>()
@ -165,6 +168,7 @@ internal class TokenDetailsModel @Inject constructor(
private val expressTxJobHolder = JobHolder()
private val buttonsJobHolder = JobHolder()
private val stakingJobHolder = JobHolder()
private val yieldSupplyBalanceJobHolder = JobHolder()
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
@ -352,6 +356,7 @@ internal class TokenDetailsModel @Inject constructor(
updateButtons(currencyStatus = status)
updateWarnings(status)
subscribeOnUpdateStakingInfo(status)
subscribeOnYieldSupplyBalanceIfActive(status)
}
currencyStatusAnalyticsSender.send(maybeCurrencyStatus)
}
@ -395,6 +400,26 @@ internal class TokenDetailsModel @Inject constructor(
.saveIn(expressTxJobHolder)
}
private fun subscribeOnYieldSupplyBalanceIfActive(status: CryptoCurrencyStatus) {
if (yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled &&
status.value.yieldSupplyStatus?.isActive == true
) {
if (yieldSupplyBalanceJobHolder.isActive && status.value.sources.networkSource != StatusSource.ACTUAL) {
return
}
yieldSupplyGetRewardsBalanceUseCase(status = status, appCurrency = selectedAppCurrencyFlow.value)
.onEach { formatted ->
internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance(formatted)
}
.flowOn(dispatchers.main)
.launchIn(modelScope)
.saveIn(yieldSupplyBalanceJobHolder)
} else {
yieldSupplyBalanceJobHolder.cancel()
internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance(null)
}
}
private fun updateNetworkToSwapBalance(toCryptoCurrency: CryptoCurrency) {
modelScope.launch {
updateDelayedCurrencyStatusUseCase(

View file

@ -28,6 +28,7 @@ internal sealed class TokenDetailsBalanceBlockState {
val isBalanceSelectorEnabled: Boolean,
val isBalanceFlickering: Boolean,
val yieldSupplyState: TokenDetailsYieldSupplyState = TokenDetailsYieldSupplyState.Empty,
val displayYeildSupplyCryptoBalance: String? = null,
) : TokenDetailsBalanceBlockState()
data class Error(

View file

@ -98,6 +98,8 @@ internal class TokenDetailsLoadedBalanceConverter(
stakingCryptoAmount,
currentState.selectedBalanceType,
),
displayYeildSupplyCryptoBalance = (currentState as? TokenDetailsBalanceBlockState.Content)
?.displayYeildSupplyCryptoBalance,
balanceSegmentedButtonConfig = currentState.balanceSegmentedButtonConfig,
onBalanceSelect = clickIntents::onBalanceSelect,
selectedBalanceType = currentState.selectedBalanceType,

View file

@ -28,6 +28,7 @@ import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsAppBarMenuConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsDialogConfig
import com.tangem.features.tokendetails.impl.R
@ -313,6 +314,19 @@ internal class TokenDetailsStateFactory(
return balanceSelectStateConverter.convert(buttonConfig)
}
fun getStateWithUpdatedYieldSupplyDisplayBalance(displayBalance: String?): TokenDetailsState {
val state = currentStateProvider()
val balanceState = state.tokenBalanceBlockState
return state.copy(
tokenBalanceBlockState = when (balanceState) {
is TokenDetailsBalanceBlockState.Content ->
balanceState.copy(displayYeildSupplyCryptoBalance = displayBalance)
is TokenDetailsBalanceBlockState.Error -> balanceState
is TokenDetailsBalanceBlockState.Loading -> balanceState
},
)
}
fun getStateWithConfirmHideExpressStatus(): TokenDetailsState {
return currentStateProvider().copy(
dialogConfig = TokenDetailsDialogConfig(

View file

@ -22,6 +22,7 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.buttons.HorizontalActionChips
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
import com.tangem.core.ui.components.text.TextAnimatedCounter
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resolveReference
@ -121,14 +122,29 @@ private fun FiatBalance(
height = TangemTheme.dimens.size32,
),
)
is TokenDetailsBalanceBlockState.Content -> Text(
modifier = modifier,
text = state.displayFiatBalance.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.h2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.primary1,
),
)
is TokenDetailsBalanceBlockState.Content -> if (state.displayYeildSupplyCryptoBalance != null &&
!isBalanceHidden
) {
TextAnimatedCounter(
modifier = modifier,
text = state.displayYeildSupplyCryptoBalance,
style = TangemTheme.typography.h2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.primary1,
),
)
} else {
Text(
modifier = modifier,
text = (state.displayYeildSupplyCryptoBalance ?: state.displayFiatBalance).orMaskWithStars(
isBalanceHidden,
),
style = TangemTheme.typography.h2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.primary1,
),
)
}
is TokenDetailsBalanceBlockState.Error -> Text(
modifier = modifier,
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),