diff --git a/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyRewardBalance.kt b/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyRewardBalance.kt new file mode 100644 index 0000000000..d3086742cb --- /dev/null +++ b/domain/yield-supply/models/src/main/java/com/tangem/domain/yield/supply/models/YieldSupplyRewardBalance.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.yield.supply.models + +data class YieldSupplyRewardBalance( + val fiatBalance: String?, + val cryptoBalance: String?, +) { + companion object { + fun empty() = YieldSupplyRewardBalance(null, null) + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt index 52c1434981..93e18928bf 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt @@ -1,11 +1,13 @@ package com.tangem.domain.yield.supply.usecase import com.tangem.core.ui.format.bigdecimal.anyDecimals +import com.tangem.core.ui.format.bigdecimal.crypto 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.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance import com.tangem.domain.yield.supply.YieldSupplyRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.delay @@ -22,7 +24,7 @@ class YieldSupplyGetRewardsBalanceUseCase( private val dispatcherProvider: CoroutineDispatcherProvider, ) { - operator fun invoke(status: CryptoCurrencyStatus, appCurrency: AppCurrency): Flow = flow { + operator fun invoke(status: CryptoCurrencyStatus, appCurrency: AppCurrency): Flow = flow { val cryptoAmount = status.value.amount val fiatRate = status.value.fiatRate @@ -30,11 +32,7 @@ class YieldSupplyGetRewardsBalanceUseCase( return@flow } - val amount = if (cryptoAmount != null && fiatRate != null) { - cryptoAmount.multiply(fiatRate) - } else { - return@flow - } + if (cryptoAmount == null) return@flow val tokenAddress = (status.currency as? CryptoCurrency.Token)?.contractAddress ?: return@flow val apy = try { @@ -50,37 +48,57 @@ class YieldSupplyGetRewardsBalanceUseCase( return@flow } - val initialPerTickDelta = amount - .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) - .abs() + val initialPerTickDeltaCrypto = perTickDelta(cryptoAmount, apyFraction).abs() - val minVisibleDecimals = calculateMinVisibleDecimals(initialPerTickDelta) + val minVisibleDecimalsCrypto = calculateMinVisibleDecimals( + perTickDeltaAbs = initialPerTickDeltaCrypto, + maxDecimals = status.currency.decimals, + ) - var currentBalance: BigDecimal = amount + val fiatAmountStart = fiatRate?.let { cryptoAmount.multiply(it) } + val minVisibleDecimalsFiat = fiatAmountStart?.let { amount -> + val initialPerTickDeltaFiat = perTickDelta(amount, apyFraction).abs() + calculateMinVisibleDecimals( + perTickDeltaAbs = initialPerTickDeltaFiat, + maxDecimals = FIAT_MAX_DECIMALS, + ) + } + + var currentCryptoBalance: BigDecimal = cryptoAmount + var currentFiatBalance: BigDecimal? = fiatAmountStart while (true) { + val fiatBalanceFormatted: String? = currentFiatBalance?.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ).anyDecimals(decimals = minVisibleDecimalsFiat ?: FIAT_MIN_DECIMALS) + } + + val cryptoBalanceFormatted: String = currentCryptoBalance.format { + crypto(status.currency).anyDecimals( + maxDecimals = minVisibleDecimalsCrypto, + minDecimals = minVisibleDecimalsCrypto, + ) + } + emit( - currentBalance.format { - fiat( - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ).anyDecimals(decimals = minVisibleDecimals) - }, + YieldSupplyRewardBalance(fiatBalance = fiatBalanceFormatted, cryptoBalance = cryptoBalanceFormatted), ) - val perTickDelta = currentBalance - .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + val perTickDeltaCrypto = perTickDelta(currentCryptoBalance, apyFraction) - currentBalance = currentBalance.add(perTickDelta) + currentCryptoBalance = currentCryptoBalance.add(perTickDeltaCrypto) + + currentFiatBalance = currentFiatBalance?.let { current -> + val perTickDeltaFiat = perTickDelta(current, apyFraction) + current.add(perTickDeltaFiat) + } delay(TICK_MILLIS) } }.flowOn(dispatcherProvider.default) - private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal): Int { + private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal, maxDecimals: Int): Int { if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS val perTickAsDouble = perTickDeltaAbs.toDouble() @@ -88,20 +106,28 @@ class YieldSupplyGetRewardsBalanceUseCase( val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble val raw = ceil(-ln(safe) / LN_10) - return raw.toInt().coerceIn(MIN_DECIMALS, MAX_DECIMALS) + return raw.toInt().coerceIn(MIN_DECIMALS, maxDecimals) } - private companion object { - const val TICK_MILLIS: Long = 800 - private val TICK_SECONDS_BD = BigDecimal("0.8") - private val SECONDS_PER_YEAR_BD = BigDecimal("31536000") // 365 * 24 * 60 * 60 - private val HUNDRED_BD = BigDecimal("100") - private const val SCALE = 18 + private fun perTickDelta(amount: BigDecimal, apyFraction: BigDecimal): BigDecimal { + return amount + .multiply(apyFraction) + .multiply(TICK_SECONDS_BD) + .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + } - private const val MIN_DECIMALS = 3 - private const val MAX_DECIMALS = 12 + companion object { + internal const val TICK_MILLIS: Long = 800 + internal val TICK_SECONDS_BD: BigDecimal = BigDecimal("0.8") + internal val SECONDS_PER_YEAR_BD: BigDecimal = BigDecimal("31536000") // 365 * 24 * 60 * 60 + internal val HUNDRED_BD: BigDecimal = BigDecimal("100") + internal const val SCALE: Int = 18 - private val LN_10 = ln(10.0) - private const val EPSILON = 1e-18 + internal const val MIN_DECIMALS: Int = 3 + internal const val FIAT_MIN_DECIMALS: Int = 2 + internal const val FIAT_MAX_DECIMALS: Int = 12 + + internal val LN_10: Double = ln(10.0) + internal const val EPSILON: Double = 1e-18 } } \ No newline at end of file diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt index 3bdd2f446e..c086813f73 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt @@ -4,6 +4,7 @@ import com.google.common.truth.Truth.assertThat import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.anyDecimals +import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -11,6 +12,7 @@ import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.yield.supply.YieldSupplyRepository import com.tangem.domain.yield.supply.models.YieldMarketToken +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetRewardsBalanceUseCase.Companion.TICK_MILLIS import com.tangem.utils.coroutines.CoroutineDispatcherProvider import io.mockk.coEvery import io.mockk.mockk @@ -26,7 +28,6 @@ import org.junit.jupiter.api.Test import java.math.BigDecimal import java.math.RoundingMode import kotlin.math.ceil -import kotlin.math.ln class YieldSupplyGetRewardsBalanceUseCaseTest { @@ -165,9 +166,9 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { val deferred = async { useCase(status, appCurrency).take(3).toList() } testScheduler.advanceUntilIdle() - advanceTimeBy(300) + advanceTimeBy(TICK_MILLIS) testScheduler.advanceUntilIdle() - advanceTimeBy(300) + advanceTimeBy(TICK_MILLIS) testScheduler.advanceUntilIdle() val collected = deferred.await() @@ -175,25 +176,147 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { val expectedDecimals = calculateMinVisibleDecimalsForTest(initialPerTickDelta(amount, apy)) - val firstExpected = amount.format { fiat( - appCurrency.code, - appCurrency.symbol, - ).anyDecimals(decimals = expectedDecimals) } - assertThat(collected[0]).isEqualTo(firstExpected) + val firstExpected = amount.format { + fiat( + appCurrency.code, + appCurrency.symbol, + ).anyDecimals(decimals = expectedDecimals) + } + assertThat(collected[0].fiatBalance).isEqualTo(firstExpected) val firstNext = nextBalance(amount, apy) - val secondExpected = firstNext.format { fiat( - appCurrency.code, - appCurrency.symbol, - ).anyDecimals(decimals = expectedDecimals) } - assertThat(collected[1]).isEqualTo(secondExpected) + val secondExpected = firstNext.format { + fiat( + appCurrency.code, + appCurrency.symbol, + ).anyDecimals(decimals = expectedDecimals) + } + assertThat(collected[1].fiatBalance).isEqualTo(secondExpected) val secondNext = nextBalance(firstNext, apy) - val thirdExpected = secondNext.format { fiat( - appCurrency.code, - appCurrency.symbol, - ).anyDecimals(decimals = expectedDecimals) } - assertThat(collected[2]).isEqualTo(thirdExpected) + val thirdExpected = secondNext.format { + fiat( + appCurrency.code, + appCurrency.symbol, + ).anyDecimals(decimals = expectedDecimals) + } + assertThat(collected[2].fiatBalance).isEqualTo(thirdExpected) + } + + @Test + fun `GIVEN polygon USDT0 Loaded status WHEN invoke THEN emit formatted balances`() = runTest { + val network = Network( + id = Network.ID(Network.RawID("POLYGON"), Network.DerivationPath.Card("m/44'/60'/0'/0/0")), + backendId = "polygon-pos", + name = "Polygon", + currencySymbol = "POL", + derivationPath = Network.DerivationPath.Card("m/44'/60'/0'/0/0"), + isTestnet = false, + standardType = Network.StandardType.Unspecified("Polygon"), + hasFiatFeeRate = true, + canHandleTokens = true, + transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, + ) + val tokenId = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId(network.rawId), + suffix = CryptoCurrency.ID.Suffix.RawID("usdt0", "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"), + ) + val currency = CryptoCurrency.Token( + id = tokenId, + network = network, + name = "USDT0", + symbol = "USDT0", + decimals = 6, + iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/usdt0.png", + isCustom = false, + contractAddress = "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", + ) + + val amount = BigDecimal("9.241136") + val fiatRate = BigDecimal("0.9999761277273864") + val status = CryptoCurrencyStatus( + currency = currency, + value = CryptoCurrencyStatus.Loaded( + amount = amount, + fiatAmount = amount.multiply(fiatRate), + fiatRate = fiatRate, + priceChange = BigDecimal("-0.000058200000000008245"), + yieldBalance = null, + yieldSupplyStatus = null, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = NetworkAddress.Single( + NetworkAddress.Address( + value = "0xb71fa0E20ba8579B3ec51cC79aaa84Bf5982BB49", + type = NetworkAddress.Address.Type.Primary, + ), + ), + sources = CryptoCurrencyStatus.Sources(), + ), + ) + + val apy = BigDecimal("5.0") + coEvery { repository.getCachedMarkets() } returns listOf( + YieldMarketToken( + tokenAddress = currency.contractAddress, + chainId = 137, + apy = apy, + isActive = true, + maxFeeNative = BigDecimal.ZERO, + maxFeeUSD = BigDecimal.ZERO, + backendId = "polygon-pos", + ), + ) + + val dispatcherProvider = testDispatcherProvider(this) + val useCase = YieldSupplyGetRewardsBalanceUseCase(repository, dispatcherProvider) + val appCurrency = AppCurrency.Default + + val deferred = async { useCase(status, appCurrency).take(2).toList() } + + testScheduler.advanceUntilIdle() + advanceTimeBy(TICK_MILLIS) + testScheduler.advanceUntilIdle() + + val emissions = deferred.await() + assertThat(emissions).hasSize(2) + + val apyFraction = apy.divide(BigDecimal("100"), 18, RoundingMode.HALF_UP) + val perTickCrypto = amount.multiply(apyFraction) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) + .abs() + val minCryptoDecimals = calculateMinVisibleDecimalsForTest(perTickCrypto).coerceAtMost(currency.decimals) + + val fiatAmountStart = amount.multiply(fiatRate) + val perTickFiat = fiatAmountStart.multiply(apyFraction) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) + .abs() + val minFiatDecimals = calculateMinVisibleDecimalsForTest(perTickFiat) + + val expectedCrypto0 = amount.format { + crypto(currency).anyDecimals( + maxDecimals = minCryptoDecimals, + minDecimals = minCryptoDecimals, + ) + } + val expectedFiat0 = fiatAmountStart.format { + fiat(appCurrency.code, appCurrency.symbol).anyDecimals(decimals = minFiatDecimals) + } + + assertThat(emissions[0].cryptoBalance).isEqualTo(expectedCrypto0) + assertThat(emissions[0].fiatBalance).isEqualTo(expectedFiat0) } private fun testDispatcherProvider(scope: TestScope): CoroutineDispatcherProvider { @@ -260,42 +383,48 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { } private fun initialPerTickDelta(amount: BigDecimal, apy: BigDecimal): BigDecimal { - val apyFraction = apy.divide(HUNDRED_BD, SCALE, RoundingMode.HALF_UP) + val apyFraction = apy.divide( + YieldSupplyGetRewardsBalanceUseCase.HUNDRED_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) return amount .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) .abs() } private fun nextBalance(current: BigDecimal, apy: BigDecimal): BigDecimal { - val apyFraction = apy.divide(HUNDRED_BD, SCALE, RoundingMode.HALF_UP) + val apyFraction = apy.divide( + YieldSupplyGetRewardsBalanceUseCase.HUNDRED_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) val perTickDelta = current .multiply(apyFraction) - .multiply(TICK_SECONDS_BD) - .divide(SECONDS_PER_YEAR_BD, SCALE, RoundingMode.HALF_UP) + .multiply(YieldSupplyGetRewardsBalanceUseCase.TICK_SECONDS_BD) + .divide( + YieldSupplyGetRewardsBalanceUseCase.SECONDS_PER_YEAR_BD, + YieldSupplyGetRewardsBalanceUseCase.SCALE, + RoundingMode.HALF_UP, + ) return current.add(perTickDelta) } private fun calculateMinVisibleDecimalsForTest(perTickDeltaAbs: BigDecimal): Int { - if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS + if (perTickDeltaAbs <= BigDecimal.ZERO) return YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS val perTickAsDouble = perTickDeltaAbs.toDouble() - if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return MIN_DECIMALS - val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble - val raw = ceil(-ln(safe) / LN_10) - return raw.toInt().coerceIn(MIN_DECIMALS, MAX_DECIMALS) - } - - private companion object { - private const val SCALE = 18 - private val TICK_SECONDS_BD = BigDecimal("0.8") - private val SECONDS_PER_YEAR_BD = BigDecimal("31536000") - private val HUNDRED_BD = BigDecimal("100") - - private const val MIN_DECIMALS = 3 - private const val MAX_DECIMALS = 12 - - private val LN_10 = ln(10.0) - private const val EPSILON = 1e-18 + if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS + val safe = if (perTickAsDouble <= 0.0) YieldSupplyGetRewardsBalanceUseCase.EPSILON else perTickAsDouble + val raw = ceil(-kotlin.math.ln(safe) / YieldSupplyGetRewardsBalanceUseCase.LN_10) + return raw.toInt().coerceIn( + YieldSupplyGetRewardsBalanceUseCase.MIN_DECIMALS, + YieldSupplyGetRewardsBalanceUseCase.FIAT_MAX_DECIMALS, + ) } } \ No newline at end of file diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 2d9665f967..05ec40ad40 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -88,6 +88,7 @@ dependencies { implementation(projects.domain.wallets) implementation(projects.domain.wallets.models) implementation(projects.domain.yieldSupply) + implementation(projects.domain.yieldSupply.models) /** Temp dependency to swap domain */ implementation(projects.features.swap.domain) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 4e70a2faa4..a221f254ec 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -121,7 +121,7 @@ internal object TokenDetailsPreviewData { selectedBalanceType = BalanceType.ALL, onBalanceSelect = {}, displayCryptoBalance = "966,96 XLM", - displayYeildSupplyCryptoBalance = null, + displayYieldSupplyFiatBalance = null, displayFiatBalance = "91,50$", isBalanceSelectorEnabled = true, isBalanceFlickering = false, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 3ef6e97629..16ce0aecda 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -75,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.models.YieldSupplyRewardBalance import com.tangem.domain.yield.supply.usecase.YieldSupplyGetRewardsBalanceUseCase import com.tangem.feature.tokendetails.deeplink.TokenDetailsDeepLinkActionListener import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter @@ -416,7 +417,9 @@ internal class TokenDetailsModel @Inject constructor( .saveIn(yieldSupplyBalanceJobHolder) } else { yieldSupplyBalanceJobHolder.cancel() - internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance(null) + internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance( + YieldSupplyRewardBalance.empty(), + ) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt index a74d80f6b8..9e17b95c95 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt @@ -28,7 +28,8 @@ internal sealed class TokenDetailsBalanceBlockState { val isBalanceSelectorEnabled: Boolean, val isBalanceFlickering: Boolean, val yieldSupplyState: TokenDetailsYieldSupplyState = TokenDetailsYieldSupplyState.Empty, - val displayYeildSupplyCryptoBalance: String? = null, + val displayYieldSupplyFiatBalance: String? = null, + val displayYieldSupplyCryptoBalance: String? = null, ) : TokenDetailsBalanceBlockState() data class Error( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 3243636297..306315e766 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -98,8 +98,8 @@ internal class TokenDetailsLoadedBalanceConverter( stakingCryptoAmount, currentState.selectedBalanceType, ), - displayYeildSupplyCryptoBalance = (currentState as? TokenDetailsBalanceBlockState.Content) - ?.displayYeildSupplyCryptoBalance, + displayYieldSupplyFiatBalance = (currentState as? TokenDetailsBalanceBlockState.Content) + ?.displayYieldSupplyFiatBalance, balanceSegmentedButtonConfig = currentState.balanceSegmentedButtonConfig, onBalanceSelect = clickIntents::onBalanceSelect, selectedBalanceType = currentState.selectedBalanceType, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index e19dda0395..da13ce39ba 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -26,6 +26,7 @@ import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase +import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance 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 @@ -315,13 +316,18 @@ internal class TokenDetailsStateFactory( return balanceSelectStateConverter.convert(buttonConfig) } - fun getStateWithUpdatedYieldSupplyDisplayBalance(displayBalance: String?): TokenDetailsState { + fun getStateWithUpdatedYieldSupplyDisplayBalance( + yieldSupplyRewardBalance: YieldSupplyRewardBalance, + ): TokenDetailsState { val state = currentStateProvider() val balanceState = state.tokenBalanceBlockState return state.copy( tokenBalanceBlockState = when (balanceState) { is TokenDetailsBalanceBlockState.Content -> - balanceState.copy(displayYeildSupplyCryptoBalance = displayBalance) + balanceState.copy( + displayYieldSupplyFiatBalance = yieldSupplyRewardBalance.fiatBalance, + displayYieldSupplyCryptoBalance = yieldSupplyRewardBalance.cryptoBalance, + ) is TokenDetailsBalanceBlockState.Error -> balanceState is TokenDetailsBalanceBlockState.Loading -> balanceState }, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt index 9eaa517709..293e54545c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt @@ -122,12 +122,12 @@ private fun FiatBalance( height = TangemTheme.dimens.size32, ), ) - is TokenDetailsBalanceBlockState.Content -> if (state.displayYeildSupplyCryptoBalance != null && + is TokenDetailsBalanceBlockState.Content -> if (state.displayYieldSupplyFiatBalance != null && !isBalanceHidden ) { TextAnimatedCounter( modifier = modifier, - text = state.displayYeildSupplyCryptoBalance, + text = state.displayYieldSupplyFiatBalance, style = TangemTheme.typography.h2.applyBladeBrush( isEnabled = state.isBalanceFlickering, textColor = TangemTheme.colors.text.primary1, @@ -136,7 +136,7 @@ private fun FiatBalance( } else { Text( modifier = modifier, - text = (state.displayYeildSupplyCryptoBalance ?: state.displayFiatBalance).orMaskWithStars( + text = (state.displayYieldSupplyFiatBalance ?: state.displayFiatBalance).orMaskWithStars( isBalanceHidden, ), style = TangemTheme.typography.h2.applyBladeBrush( @@ -184,8 +184,9 @@ private fun CryptoBalance( tint = TangemTheme.colors.icon.inactive, contentDescription = null, ) - Text( - text = state.displayCryptoBalance.orMaskWithStars(isBalanceHidden), + TextAnimatedCounter( + text = (state.displayYieldSupplyCryptoBalance ?: state.displayCryptoBalance) + .orMaskWithStars(isBalanceHidden), style = TangemTheme.typography.caption2.applyBladeBrush( isEnabled = state.isBalanceFlickering, textColor = TangemTheme.colors.text.tertiary,