Updated on 2026-08-14
This commit is contained in:
parent
e2cc040d68
commit
ff3f1a26a5
19 changed files with 420 additions and 121 deletions
|
|
@ -19,6 +19,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component
|
|||
import com.tangem.features.tokendetails.impl.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LargeClass")
|
||||
internal object TokenDetailsPreviewData {
|
||||
|
|
@ -121,12 +122,13 @@ internal object TokenDetailsPreviewData {
|
|||
)
|
||||
val balanceContent = TokenDetailsBalanceBlockState.Content(
|
||||
actionButtons = actionButtons,
|
||||
fiatBalance = "91,50$",
|
||||
cryptoBalance = "966,96 XLM",
|
||||
isStakingEnabled = true,
|
||||
fiatBalance = BigDecimal.ZERO,
|
||||
cryptoBalance = BigDecimal.ZERO,
|
||||
balanceSegmentedButtonConfig = balanceSegmentedButtonConfig,
|
||||
selectedBalanceType = BalanceType.ALL,
|
||||
onBalanceSelect = {},
|
||||
displayCryptoBalance = "966,96 XLM",
|
||||
displayFiatBalance = "91,50$",
|
||||
)
|
||||
val balanceError = TokenDetailsBalanceBlockState.Error(
|
||||
actionButtons = actionButtons,
|
||||
|
|
@ -139,9 +141,12 @@ internal object TokenDetailsPreviewData {
|
|||
private val stakingLoading = StakingBlocksState(
|
||||
stakingAvailable = StakingAvailable.Loading(iconState),
|
||||
stakingBalance = StakingBalance.Content(
|
||||
cryptoAmount = stringReference("5 SOL"),
|
||||
fiatAmount = stringReference("456.34 $"),
|
||||
rewardAmount = resourceReference(R.string.staking_details_no_rewards_to_claim, wrappedList("0.43 $")),
|
||||
cryptoValue = stringReference("5 SOL"),
|
||||
fiatValue = stringReference("456.34 $"),
|
||||
rewardValue = resourceReference(R.string.staking_details_no_rewards_to_claim, wrappedList("0.43 $")),
|
||||
cryptoAmount = BigDecimal.ZERO,
|
||||
fiatAmount = BigDecimal.ZERO,
|
||||
onStakeClicked = {},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -320,9 +325,12 @@ internal object TokenDetailsPreviewData {
|
|||
onStakeClicked = {},
|
||||
),
|
||||
stakingBalance = StakingBalance.Content(
|
||||
cryptoAmount = stringReference("5 SOL"),
|
||||
fiatAmount = stringReference("456.34 $"),
|
||||
rewardAmount = resourceReference(R.string.staking_details_rewards_to_claim, wrappedList("0.43 $")),
|
||||
cryptoValue = stringReference("5 SOL"),
|
||||
fiatValue = stringReference("456.34 $"),
|
||||
rewardValue = resourceReference(R.string.staking_details_no_rewards_to_claim, wrappedList("0.43 $")),
|
||||
cryptoAmount = BigDecimal.ZERO,
|
||||
fiatAmount = BigDecimal.ZERO,
|
||||
onStakeClicked = {},
|
||||
),
|
||||
),
|
||||
notifications = persistentListOf(),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal data class StakingBlocksState(
|
||||
val stakingAvailable: StakingAvailable,
|
||||
|
|
@ -30,8 +31,11 @@ sealed class StakingBalance {
|
|||
data object Empty : StakingBalance()
|
||||
|
||||
data class Content(
|
||||
val cryptoAmount: TextReference,
|
||||
val fiatAmount: TextReference,
|
||||
val rewardAmount: TextReference,
|
||||
val cryptoValue: TextReference,
|
||||
val fiatValue: TextReference,
|
||||
val rewardValue: TextReference,
|
||||
val cryptoAmount: BigDecimal?,
|
||||
val fiatAmount: BigDecimal?,
|
||||
val onStakeClicked: () -> Unit,
|
||||
) : StakingBalance()
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state
|
|||
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal sealed class TokenDetailsBalanceBlockState {
|
||||
|
||||
|
|
@ -19,10 +20,11 @@ internal sealed class TokenDetailsBalanceBlockState {
|
|||
override val actionButtons: ImmutableList<TokenDetailsActionButton>,
|
||||
override val balanceSegmentedButtonConfig: ImmutableList<TokenBalanceSegmentedButtonConfig>,
|
||||
override val selectedBalanceType: BalanceType,
|
||||
val fiatBalance: String,
|
||||
val cryptoBalance: String,
|
||||
val isStakingEnabled: Boolean,
|
||||
val fiatBalance: BigDecimal?,
|
||||
val cryptoBalance: BigDecimal?,
|
||||
val onBalanceSelect: (TokenBalanceSegmentedButtonConfig) -> Unit,
|
||||
val displayCryptoBalance: String,
|
||||
val displayFiatBalance: String,
|
||||
) : TokenDetailsBalanceBlockState()
|
||||
|
||||
data class Error(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
||||
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getBalance
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class TokenDetailsBalanceSelectStateConverter(
|
||||
private val currentStateProvider: Provider<TokenDetailsState>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
|
||||
) : Converter<TokenBalanceSegmentedButtonConfig, TokenDetailsState> {
|
||||
|
||||
override fun convert(value: TokenBalanceSegmentedButtonConfig): TokenDetailsState {
|
||||
return with(currentStateProvider()) {
|
||||
if (stakingBlocksState.stakingBalance !is StakingBalance.Content) return this
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() ?: return this
|
||||
|
||||
val stakingCryptoAmount = stakingBlocksState.stakingBalance.cryptoAmount
|
||||
val stakingFiatAmount = stakingBlocksState.stakingBalance.fiatAmount
|
||||
|
||||
copy(
|
||||
tokenBalanceBlockState = if (tokenBalanceBlockState is TokenDetailsBalanceBlockState.Content) {
|
||||
tokenBalanceBlockState.copy(
|
||||
selectedBalanceType = value.type,
|
||||
displayFiatBalance = formatFiatAmount(
|
||||
status = cryptoCurrencyStatus.value,
|
||||
stakingFiatAmount = stakingFiatAmount,
|
||||
selectedBalanceType = value.type,
|
||||
appCurrency = appCurrencyProvider(),
|
||||
),
|
||||
displayCryptoBalance = formatCryptoAmount(
|
||||
status = cryptoCurrencyStatus,
|
||||
stakingCryptoAmount = stakingCryptoAmount,
|
||||
selectedBalanceType = value.type,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
tokenBalanceBlockState
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatFiatAmount(
|
||||
status: CryptoCurrencyStatus.Value,
|
||||
stakingFiatAmount: BigDecimal?,
|
||||
selectedBalanceType: BalanceType,
|
||||
appCurrency: AppCurrency,
|
||||
): String {
|
||||
val fiatAmount = status.fiatAmount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
val totalAmount = fiatAmount.getBalance(selectedBalanceType, stakingFiatAmount)
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = totalAmount,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatCryptoAmount(
|
||||
status: CryptoCurrencyStatus,
|
||||
stakingCryptoAmount: BigDecimal?,
|
||||
selectedBalanceType: BalanceType,
|
||||
): String {
|
||||
val amount = status.value.amount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
val totalAmount = amount.getBalance(selectedBalanceType, stakingCryptoAmount)
|
||||
|
||||
return BigDecimalFormatter.formatCryptoAmount(totalAmount, status.currency.symbol, status.currency.decimals)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,41 +6,54 @@ import com.tangem.core.ui.components.marketprice.PriceChangeState
|
|||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.YieldBalance
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.BalanceType
|
||||
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.*
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsTxHistoryTransactionStateConverter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getBalance
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class TokenDetailsLoadedBalanceConverter(
|
||||
private val currentStateProvider: Provider<TokenDetailsState>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val isStakingEnabled: Boolean,
|
||||
private val symbol: String,
|
||||
private val decimals: Int,
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
) : Converter<Either<CurrencyStatusError, CryptoCurrencyStatus>, TokenDetailsState> {
|
||||
) : Converter<TokenDetailsLoadedBalanceConverter.Data, TokenDetailsState> {
|
||||
|
||||
private val txHistoryItemConverter by lazy {
|
||||
TokenDetailsTxHistoryTransactionStateConverter(symbol, decimals, clickIntents)
|
||||
}
|
||||
|
||||
override fun convert(value: Either<CurrencyStatusError, CryptoCurrencyStatus>): TokenDetailsState {
|
||||
return value.fold(ifLeft = { convertError() }, ifRight = ::convert)
|
||||
data class Data(
|
||||
val maybeCryptoCurrencyStatus: Either<CurrencyStatusError, CryptoCurrencyStatus>,
|
||||
val maybeYieldBalance: Either<Throwable, YieldBalance>?,
|
||||
)
|
||||
|
||||
override fun convert(value: Data): TokenDetailsState {
|
||||
return value.maybeCryptoCurrencyStatus.fold(
|
||||
ifLeft = { convertError() },
|
||||
ifRight = { convert(it, value.maybeYieldBalance?.getOrNull() ?: YieldBalance.Empty) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertError(): TokenDetailsState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
isStakingBlockShown = false,
|
||||
tokenBalanceBlockState = TokenDetailsBalanceBlockState.Error(
|
||||
actionButtons = state.tokenBalanceBlockState.actionButtons,
|
||||
balanceSegmentedButtonConfig = state.tokenBalanceBlockState.balanceSegmentedButtonConfig,
|
||||
|
|
@ -51,12 +64,23 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun convert(status: CryptoCurrencyStatus): TokenDetailsState {
|
||||
private fun convert(status: CryptoCurrencyStatus, yieldBalance: YieldBalance): TokenDetailsState {
|
||||
val state = currentStateProvider()
|
||||
val currencyName = state.marketPriceBlockState.currencySymbol
|
||||
val pendingTxs = status.value.pendingTransactions.map(txHistoryItemConverter::convert).toPersistentList()
|
||||
val stakingCryptoAmount = (yieldBalance as? YieldBalance.Data)?.let {
|
||||
yieldBalance.balance.items.sumOf { it.amount }
|
||||
}
|
||||
val stakingFiatAmount = stakingCryptoAmount?.let { status.value.fiatRate?.multiply(it) }
|
||||
|
||||
return state.copy(
|
||||
tokenBalanceBlockState = getBalanceState(state.tokenBalanceBlockState, status),
|
||||
tokenBalanceBlockState = getBalanceState(
|
||||
currentState = state.tokenBalanceBlockState,
|
||||
status = status,
|
||||
stakingCryptoAmount = stakingCryptoAmount,
|
||||
stakingFiatAmount = stakingFiatAmount,
|
||||
),
|
||||
stakingBlocksState = getYieldBalance(status, yieldBalance, state),
|
||||
marketPriceBlockState = getMarketPriceState(status = status.value, currencySymbol = currencyName),
|
||||
pendingTxs = pendingTxs,
|
||||
txHistoryState = if (state.txHistoryState is TxHistoryState.NotSupported) {
|
||||
|
|
@ -70,6 +94,8 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
private fun getBalanceState(
|
||||
currentState: TokenDetailsBalanceBlockState,
|
||||
status: CryptoCurrencyStatus,
|
||||
stakingCryptoAmount: BigDecimal?,
|
||||
stakingFiatAmount: BigDecimal?,
|
||||
): TokenDetailsBalanceBlockState {
|
||||
return when (status.value) {
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
|
|
@ -78,9 +104,19 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
is CryptoCurrencyStatus.Custom,
|
||||
-> TokenDetailsBalanceBlockState.Content(
|
||||
actionButtons = currentState.actionButtons,
|
||||
fiatBalance = formatFiatAmount(status.value, appCurrencyProvider()),
|
||||
cryptoBalance = formatCryptoAmount(status),
|
||||
isStakingEnabled = isStakingEnabled,
|
||||
cryptoBalance = status.value.amount,
|
||||
fiatBalance = status.value.fiatAmount,
|
||||
displayFiatBalance = formatFiatAmount(
|
||||
status.value,
|
||||
stakingFiatAmount,
|
||||
currentState.selectedBalanceType,
|
||||
appCurrencyProvider(),
|
||||
),
|
||||
displayCryptoBalance = formatCryptoAmount(
|
||||
status,
|
||||
stakingCryptoAmount,
|
||||
currentState.selectedBalanceType,
|
||||
),
|
||||
balanceSegmentedButtonConfig = currentState.balanceSegmentedButtonConfig,
|
||||
onBalanceSelect = clickIntents::onBalanceSelect,
|
||||
selectedBalanceType = currentState.selectedBalanceType,
|
||||
|
|
@ -88,7 +124,7 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
is CryptoCurrencyStatus.Loading -> TokenDetailsBalanceBlockState.Loading(
|
||||
actionButtons = currentState.actionButtons,
|
||||
balanceSegmentedButtonConfig = currentState.balanceSegmentedButtonConfig,
|
||||
selectedBalanceType = BalanceType.ALL,
|
||||
selectedBalanceType = currentState.selectedBalanceType,
|
||||
)
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
|
|
@ -96,11 +132,58 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
-> TokenDetailsBalanceBlockState.Error(
|
||||
actionButtons = currentState.actionButtons,
|
||||
balanceSegmentedButtonConfig = currentState.balanceSegmentedButtonConfig,
|
||||
selectedBalanceType = BalanceType.ALL,
|
||||
selectedBalanceType = currentState.selectedBalanceType,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getYieldBalance(
|
||||
status: CryptoCurrencyStatus,
|
||||
yieldBalance: YieldBalance,
|
||||
state: TokenDetailsState,
|
||||
): StakingBlocksState {
|
||||
val stakingCryptoAmount = (yieldBalance as? YieldBalance.Data)?.let {
|
||||
yieldBalance.balance.items.sumOf { it.amount }
|
||||
}
|
||||
val stakingFiatAmount = stakingCryptoAmount?.let { status.value.fiatRate?.multiply(it) }
|
||||
val stakingRewardAmount = (yieldBalance as? YieldBalance.Data)?.let {
|
||||
yieldBalance.balance.items.sumOf { it.amount.multiply(it.pricePerShare) }
|
||||
}
|
||||
val stakingBalance = if (stakingCryptoAmount == null) {
|
||||
StakingBalance.Empty
|
||||
} else {
|
||||
StakingBalance.Content(
|
||||
cryptoAmount = stakingCryptoAmount,
|
||||
fiatAmount = stakingFiatAmount,
|
||||
cryptoValue = stringReference(
|
||||
BigDecimalFormatter.formatCryptoAmount(stakingCryptoAmount, symbol, decimals),
|
||||
),
|
||||
fiatValue = stringReference(
|
||||
BigDecimalFormatter.formatFiatAmount(
|
||||
stakingFiatAmount,
|
||||
appCurrencyProvider().code,
|
||||
appCurrencyProvider().symbol,
|
||||
),
|
||||
),
|
||||
rewardValue = resourceReference(
|
||||
R.string.staking_details_rewards_to_claim,
|
||||
wrappedList(
|
||||
BigDecimalFormatter.formatFiatAmount(
|
||||
stakingRewardAmount,
|
||||
appCurrencyProvider().code,
|
||||
appCurrencyProvider().symbol,
|
||||
),
|
||||
),
|
||||
),
|
||||
onStakeClicked = clickIntents::onStakeBannerClick,
|
||||
)
|
||||
}
|
||||
return state.stakingBlocksState.copy(
|
||||
stakingAvailable = state.stakingBlocksState.stakingAvailable,
|
||||
stakingBalance = stakingBalance,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getMarketPriceState(
|
||||
status: CryptoCurrencyStatus.Value,
|
||||
currencySymbol: String,
|
||||
|
|
@ -158,19 +241,30 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun formatFiatAmount(status: CryptoCurrencyStatus.Value, appCurrency: AppCurrency): String {
|
||||
private fun formatFiatAmount(
|
||||
status: CryptoCurrencyStatus.Value,
|
||||
stakingFiatAmount: BigDecimal?,
|
||||
selectedBalanceType: BalanceType,
|
||||
appCurrency: AppCurrency,
|
||||
): String {
|
||||
val fiatAmount = status.fiatAmount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
val totalAmount = fiatAmount.getBalance(selectedBalanceType, stakingFiatAmount)
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = fiatAmount,
|
||||
fiatAmount = totalAmount,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatCryptoAmount(status: CryptoCurrencyStatus): String {
|
||||
private fun formatCryptoAmount(
|
||||
status: CryptoCurrencyStatus,
|
||||
stakingCryptoAmount: BigDecimal?,
|
||||
selectedBalanceType: BalanceType,
|
||||
): String {
|
||||
val amount = status.value.amount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
val totalAmount = amount.getBalance(selectedBalanceType, stakingCryptoAmount)
|
||||
|
||||
return BigDecimalFormatter.formatCryptoAmount(amount, status.currency.symbol, status.currency.decimals)
|
||||
return BigDecimalFormatter.formatCryptoAmount(totalAmount, status.currency.symbol, status.currency.decimals)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.staking.model.YieldBalance
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
|
|
@ -42,9 +43,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
internal class TokenDetailsStateFactory(
|
||||
private val currentStateProvider: Provider<TokenDetailsState>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
private val featureToggles: TokenDetailsFeatureToggles,
|
||||
private val isStakingEnabled: Boolean,
|
||||
symbol: String,
|
||||
decimals: Int,
|
||||
) {
|
||||
|
|
@ -64,7 +65,6 @@ internal class TokenDetailsStateFactory(
|
|||
TokenDetailsLoadedBalanceConverter(
|
||||
currentStateProvider = currentStateProvider,
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
isStakingEnabled = isStakingEnabled,
|
||||
symbol = symbol,
|
||||
decimals = decimals,
|
||||
clickIntents = clickIntents,
|
||||
|
|
@ -107,14 +107,25 @@ internal class TokenDetailsStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
private val balanceSelectStateConverter by lazy {
|
||||
TokenDetailsBalanceSelectStateConverter(
|
||||
currentStateProvider = currentStateProvider,
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
)
|
||||
}
|
||||
|
||||
fun getInitialState(screenArgument: CryptoCurrency): TokenDetailsState {
|
||||
return skeletonStateConverter.convert(value = screenArgument)
|
||||
}
|
||||
|
||||
fun getCurrencyLoadedBalanceState(
|
||||
cryptoCurrencyEither: Either<CurrencyStatusError, CryptoCurrencyStatus>,
|
||||
yieldBalanceEither: Either<Throwable, YieldBalance>?,
|
||||
): TokenDetailsState {
|
||||
return tokenDetailsLoadedBalanceConverter.convert(cryptoCurrencyEither)
|
||||
return tokenDetailsLoadedBalanceConverter.convert(
|
||||
TokenDetailsLoadedBalanceConverter.Data(cryptoCurrencyEither, yieldBalanceEither),
|
||||
)
|
||||
}
|
||||
|
||||
fun getManageButtonsState(actions: List<TokenActionsState.ActionState>): TokenDetailsState {
|
||||
|
|
@ -352,12 +363,7 @@ internal class TokenDetailsStateFactory(
|
|||
fun getStateWithUpdatedBalanceSegmentedButtonConfig(
|
||||
buttonConfig: TokenBalanceSegmentedButtonConfig,
|
||||
): TokenDetailsState {
|
||||
return with(currentStateProvider()) {
|
||||
val updatedState = (tokenBalanceBlockState as? TokenDetailsBalanceBlockState.Content)
|
||||
?.copy(selectedBalanceType = buttonConfig.type)
|
||||
?: tokenBalanceBlockState
|
||||
copy(tokenBalanceBlockState = updatedState)
|
||||
}
|
||||
return balanceSelectStateConverter.convert(buttonConfig)
|
||||
}
|
||||
|
||||
private fun TokenDetailsAppBarMenuConfig.updateMenu(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.utils
|
||||
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.BalanceType
|
||||
import java.math.BigDecimal
|
||||
|
||||
fun BigDecimal.getBalance(selectedBalanceType: BalanceType, stakingAmount: BigDecimal?): BigDecimal {
|
||||
return if (selectedBalanceType == BalanceType.ALL && stakingAmount != null) {
|
||||
this.plus(stakingAmount)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
|
@ -108,6 +108,7 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
|
|||
modifier = itemModifier,
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
state = state.tokenBalanceBlockState,
|
||||
isStakingAvailable = state.isStakingBlockShown,
|
||||
)
|
||||
}
|
||||
items(
|
||||
|
|
@ -141,16 +142,6 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
|
|||
}
|
||||
|
||||
if (state.isStakingBlockShown) {
|
||||
item(
|
||||
key = StakingAvailable::class.java,
|
||||
contentType = StakingAvailable::class.java,
|
||||
content = {
|
||||
TokenStakingBlock(
|
||||
modifier = itemModifier,
|
||||
state = state.stakingBlocksState.stakingAvailable,
|
||||
)
|
||||
},
|
||||
)
|
||||
if (state.stakingBlocksState.stakingBalance is StakingBalance.Content) {
|
||||
item(
|
||||
key = StakingBalance::class.java,
|
||||
|
|
@ -163,6 +154,16 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
|
|||
},
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = StakingAvailable::class.java,
|
||||
contentType = StakingAvailable::class.java,
|
||||
content = {
|
||||
TokenStakingBlock(
|
||||
modifier = itemModifier,
|
||||
state = state.stakingBlocksState.stakingAvailable,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
swapTransactionsItems(
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
internal fun TokenDetailsBalanceBlock(
|
||||
state: TokenDetailsBalanceBlockState,
|
||||
isBalanceHidden: Boolean,
|
||||
isStakingAvailable: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Surface(
|
||||
|
|
@ -53,7 +54,7 @@ internal fun TokenDetailsBalanceBlock(
|
|||
.weight(1f)
|
||||
.padding(top = TangemTheme.dimens.spacing12),
|
||||
)
|
||||
BalanceButtons(state)
|
||||
if (isStakingAvailable) BalanceButtons(state)
|
||||
}
|
||||
FiatBalance(
|
||||
state = state,
|
||||
|
|
@ -94,7 +95,7 @@ private fun FiatBalance(
|
|||
)
|
||||
is TokenDetailsBalanceBlockState.Content -> Text(
|
||||
modifier = modifier,
|
||||
text = if (isBalanceHidden) STARS else state.fiatBalance,
|
||||
text = if (isBalanceHidden) STARS else state.displayFiatBalance,
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
|
@ -122,7 +123,7 @@ private fun CryptoBalance(
|
|||
)
|
||||
is TokenDetailsBalanceBlockState.Content -> Text(
|
||||
modifier = modifier,
|
||||
text = if (isBalanceHidden) STARS else state.cryptoBalance,
|
||||
text = if (isBalanceHidden) STARS else state.displayCryptoBalance,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
|
@ -171,7 +172,7 @@ private fun Preview_TokenDetailsBalanceBlock(
|
|||
@PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
TokenDetailsBalanceBlock(state = state, isBalanceHidden = false)
|
||||
TokenDetailsBalanceBlock(state = state, isBalanceHidden = false, isStakingAvailable = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -30,6 +34,11 @@ fun StakingBalanceBlock(state: StakingBalance.Content, modifier: Modifier = Modi
|
|||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(),
|
||||
onClick = state.onStakeClicked,
|
||||
)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -44,7 +53,7 @@ fun StakingBalanceBlock(state: StakingBalance.Content, modifier: Modifier = Modi
|
|||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
|
||||
Text(
|
||||
text = state.fiatAmount.resolveReference(),
|
||||
text = state.fiatValue.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
|
@ -54,13 +63,13 @@ fun StakingBalanceBlock(state: StakingBalance.Content, modifier: Modifier = Modi
|
|||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
text = state.cryptoAmount.resolveReference(),
|
||||
text = state.cryptoValue.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = state.rewardAmount.resolveReference(),
|
||||
text = state.rewardValue.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.tangem.domain.redux.ReduxStateHolder
|
|||
import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
|
||||
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
|
||||
import com.tangem.domain.staking.GetStakingEntryInfoUseCase
|
||||
import com.tangem.domain.staking.GetStakingYieldBalanceUseCase
|
||||
import com.tangem.domain.staking.GetYieldUseCase
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.tokens.*
|
||||
|
|
@ -107,6 +108,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||
private val getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase,
|
||||
private val getYieldUseCase: GetYieldUseCase,
|
||||
private val getStakingYieldBalanceUseCase: GetStakingYieldBalanceUseCase,
|
||||
private val swapRepository: SwapRepository,
|
||||
private val swapTransactionRepository: SwapTransactionRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
|
|
@ -133,6 +135,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
?: error("This screen can't open without `CryptoCurrency`")
|
||||
|
||||
private val userWallet: UserWallet
|
||||
private var stakingIntegrationId: String? = null
|
||||
|
||||
lateinit var router: InnerTokenDetailsRouter
|
||||
|
||||
|
|
@ -149,11 +152,11 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val stateFactory = TokenDetailsStateFactory(
|
||||
currentStateProvider = Provider { uiState.value },
|
||||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
clickIntents = this,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
featureToggles = tokenDetailsFeatureToggles,
|
||||
isStakingEnabled = stakingFeatureToggles.isStakingEnabled,
|
||||
)
|
||||
|
||||
private val exchangeStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
|
|
@ -217,7 +220,6 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun updateContent() {
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
subscribeOnExchangeTransactionsUpdates()
|
||||
updateTxHistory(refresh = false, showItemsLoading = true)
|
||||
|
||||
|
|
@ -279,12 +281,36 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
)
|
||||
.distinctUntilChanged()
|
||||
.onEach { maybeCurrencyStatus ->
|
||||
internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus)
|
||||
maybeCurrencyStatus.onRight { status ->
|
||||
cryptoCurrencyStatus = status
|
||||
updateButtons(currencyStatus = status)
|
||||
updateWarnings(status)
|
||||
}
|
||||
maybeCurrencyStatus.fold(
|
||||
ifLeft = {
|
||||
internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(
|
||||
maybeCurrencyStatus,
|
||||
null,
|
||||
)
|
||||
},
|
||||
ifRight = { status ->
|
||||
cryptoCurrencyStatus = status
|
||||
|
||||
if (stakingIntegrationId != null) {
|
||||
getStakingYieldBalanceUseCase(
|
||||
userWalletId = userWalletId,
|
||||
address = status.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
integrationId = stakingIntegrationId.orEmpty(),
|
||||
).onEach { maybeYieldBalance ->
|
||||
internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(
|
||||
maybeCurrencyStatus,
|
||||
maybeYieldBalance,
|
||||
)
|
||||
}.launchIn(viewModelScope)
|
||||
} else {
|
||||
internalUiState.value =
|
||||
stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus, null)
|
||||
}
|
||||
|
||||
updateButtons(currencyStatus = status)
|
||||
updateWarnings(status)
|
||||
},
|
||||
)
|
||||
currencyStatusAnalyticsSender.send(maybeCurrencyStatus)
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
|
|
@ -385,8 +411,12 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
)
|
||||
internalUiState.value = stateFactory.getStateWithUpdatedStakingAvailability(stakingAvailability)
|
||||
if (stakingAvailability is StakingAvailability.Available) {
|
||||
stakingIntegrationId = stakingAvailability.integrationId
|
||||
val stakingInfo = getStakingEntryInfoUseCase(stakingAvailability.integrationId)
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
internalUiState.value = stateFactory.getStateWithStaking(stakingInfo)
|
||||
} else {
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue