Updated on 2026-08-14
This commit is contained in:
parent
c10f747de8
commit
52ea08c979
10 changed files with 393 additions and 2 deletions
|
|
@ -436,4 +436,10 @@ internal object TokensDomainModule {
|
|||
fun provideCheckHasLinkedTokensUseCase(currenciesRepository: CurrenciesRepository): CheckHasLinkedTokensUseCase {
|
||||
return CheckHasLinkedTokensUseCase(currenciesRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCurrencyCheckUseCase(currencyChecksRepository: CurrencyChecksRepository): GetCurrencyCheckUseCase {
|
||||
return GetCurrencyCheckUseCase(currencyChecksRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.tangem.common.ui.feeScreen
|
||||
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
/**
|
||||
* Check and calculates subtracted amount
|
||||
*/
|
||||
fun checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable: Boolean,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
amountValue: BigDecimal,
|
||||
feeValue: BigDecimal,
|
||||
reduceAmountBy: BigDecimal,
|
||||
): BigDecimal {
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: return amountValue
|
||||
val isFeeCoverage = checkFeeCoverage(
|
||||
isSubtractAvailable = isAmountSubtractAvailable,
|
||||
balance = balance,
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
)
|
||||
return if (isFeeCoverage) {
|
||||
balance.minus(reduceAmountBy).minus(feeValue)
|
||||
} else {
|
||||
amountValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if sending amount with fee is greater than balance
|
||||
*/
|
||||
fun checkFeeCoverage(
|
||||
isSubtractAvailable: Boolean,
|
||||
balance: BigDecimal,
|
||||
amountValue: BigDecimal,
|
||||
feeValue: BigDecimal,
|
||||
reduceAmountBy: BigDecimal?,
|
||||
): Boolean {
|
||||
if (!isSubtractAvailable) return false
|
||||
val reducedBy = balance - (reduceAmountBy ?: BigDecimal.ZERO)
|
||||
return reducedBy < amountValue + feeValue && reducedBy > feeValue && reducedBy >= amountValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if custom fee is too low
|
||||
*/
|
||||
fun checkIfFeeTooLow(fee: TransactionFee, customValue: BigDecimal, isCustomSelected: Boolean): Boolean {
|
||||
val multipleFees = fee as? TransactionFee.Choosable ?: return false
|
||||
val minimumValue = multipleFees.minimum.amount.value ?: return false
|
||||
|
||||
return isCustomSelected && minimumValue > customValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if custom fee is too high
|
||||
*/
|
||||
fun checkIfFeeTooHigh(
|
||||
fee: TransactionFee,
|
||||
customValue: BigDecimal,
|
||||
isCustomSelected: Boolean,
|
||||
onShow: (String) -> Unit,
|
||||
): Boolean {
|
||||
val multipleFees = fee as? TransactionFee.Choosable ?: return false
|
||||
val highValue = multipleFees.priority.amount.value ?: return false
|
||||
|
||||
val diff = customValue / highValue
|
||||
val isShow = isCustomSelected && diff > FEE_MAX_DIFF
|
||||
if (isShow) onShow(diff.parseBigDecimal(ZERO_DECIMALS, RoundingMode.HALF_UP))
|
||||
return isShow
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if fee exceeds fee paid currency balance
|
||||
*/
|
||||
fun checkExceedBalance(feeBalance: BigDecimal?, feeAmount: BigDecimal?): Boolean {
|
||||
return feeAmount == null || feeBalance == null || feeAmount.isZero() || feeAmount > feeBalance
|
||||
}
|
||||
|
||||
private val FEE_MAX_DIFF = BigDecimal("5")
|
||||
private const val ZERO_DECIMALS = 0
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.tokens.model.warnings
|
||||
|
||||
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class CryptoCurrencyCheck(
|
||||
val dustValue: BigDecimal?,
|
||||
val reserveAmount: BigDecimal?,
|
||||
val existentialDeposit: BigDecimal?,
|
||||
val utxoAmountLimit: UtxoAmountLimit?,
|
||||
)
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import java.math.BigDecimal
|
||||
|
||||
class GetCurrencyCheckUseCase(
|
||||
private val currencyChecksRepository: CurrencyChecksRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
amount: BigDecimal?,
|
||||
fee: BigDecimal?,
|
||||
): CryptoCurrencyCheck {
|
||||
val network = currencyStatus.currency.network
|
||||
val dustValue = currencyChecksRepository.getDustValue(userWalletId, network)
|
||||
val reserveAmount = currencyChecksRepository.getReserveAmount(userWalletId, network)
|
||||
val existentialDeposit = currencyChecksRepository.getExistentialDeposit(userWalletId, network)
|
||||
val utxoAmountLimit = if (amount != null && fee != null) {
|
||||
currencyChecksRepository.checkUtxoAmountLimit(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
amount = amount,
|
||||
fee = fee,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return CryptoCurrencyCheck(
|
||||
dustValue = dustValue,
|
||||
reserveAmount = reserveAmount,
|
||||
existentialDeposit = existentialDeposit,
|
||||
utxoAmountLimit = utxoAmountLimit,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package com.tangem.features.staking.impl.di
|
||||
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.features.staking.impl.navigation.DefaultStakingRouter
|
||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||
import com.tangem.features.staking.impl.navigation.DefaultStakingRouter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -18,9 +19,10 @@ internal object StakingRouterModule {
|
|||
|
||||
@Provides
|
||||
@ActivityScoped
|
||||
fun provideStakingRouter(urlOpener: UrlOpener): StakingRouter {
|
||||
fun provideStakingRouter(urlOpener: UrlOpener, router: AppRouter): StakingRouter {
|
||||
return DefaultStakingRouter(
|
||||
urlOpener = urlOpener,
|
||||
router = router,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,33 @@
|
|||
package com.tangem.features.staking.impl.navigation
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.staking.impl.presentation.StakingFragment
|
||||
|
||||
internal class DefaultStakingRouter(
|
||||
private val urlOpener: UrlOpener,
|
||||
private val router: AppRouter,
|
||||
) : InnerStakingRouter {
|
||||
override fun getEntryFragment(): Fragment = StakingFragment.create()
|
||||
|
||||
override fun openUrl(url: String) {
|
||||
urlOpener.openUrl(url)
|
||||
}
|
||||
|
||||
override fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
||||
router.pop { isSuccess ->
|
||||
if (isSuccess) {
|
||||
router.push(
|
||||
AppRoute.CurrencyDetails(
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
package com.tangem.features.staking.impl.navigation
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||
|
||||
interface InnerStakingRouter : StakingRouter {
|
||||
|
||||
fun openUrl(url: String)
|
||||
|
||||
fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency)
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.feeScreen.checkAndCalculateSubtractedAmount
|
||||
import com.tangem.common.ui.feeScreen.checkFeeCoverage
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addDustWarningNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExceedBalanceNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExceedsBalanceNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addExistentialWarningNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addFeeCoverageNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addFeeUnreachableNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addReserveAmountErrorNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addTransactionLimitErrorNotification
|
||||
import com.tangem.common.ui.notifications.NotificationsFactory.addValidateTransactionNotifications
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class AddStakingNotificationsTransformer(
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
private val currencyWarning: CryptoCurrencyWarning?,
|
||||
private val validatorError: Throwable?,
|
||||
private val feeError: GetFeeError?,
|
||||
private val currencyCheck: CryptoCurrencyCheck,
|
||||
private val isSubtractAvailable: Boolean,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val appCurrency = appCurrencyProvider()
|
||||
val balance = cryptoCurrencyStatus.value.amount.orZero()
|
||||
|
||||
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
|
||||
val amountState = prevState.amountState as? AmountState.Data ?: return prevState
|
||||
val feeState = confirmationState.feeState as? FeeState.Content ?: return prevState
|
||||
|
||||
val amountValue = amountState.amountTextField.cryptoAmount.value.orZero()
|
||||
val feeValue = feeState.fee?.amount?.value.orZero()
|
||||
val reduceAmountBy = confirmationState.reduceAmountBy.orZero()
|
||||
|
||||
val isFeeCoverage = checkFeeCoverage(
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
balance = balance,
|
||||
isSubtractAvailable = isSubtractAvailable,
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
)
|
||||
val sendingAmount = checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isSubtractAvailable,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatusProvider(),
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
)
|
||||
|
||||
val notifications = buildList {
|
||||
// errors
|
||||
addErrorNotifications(
|
||||
prevState = prevState,
|
||||
feeError = feeError,
|
||||
sendingAmount = sendingAmount,
|
||||
onReload = { prevState.clickIntents.loadFee(confirmationState.pendingActions) },
|
||||
feeValue = feeValue,
|
||||
)
|
||||
// warnings
|
||||
addWarningNotifications(
|
||||
prevState = prevState,
|
||||
amountState = amountState,
|
||||
feeState = feeState,
|
||||
sendingAmount = sendingAmount,
|
||||
isFeeCoverage = isFeeCoverage,
|
||||
)
|
||||
|
||||
addAll(confirmationState.notifications)
|
||||
}.toImmutableList()
|
||||
|
||||
return prevState.copy(
|
||||
confirmationState = confirmationState.copy(
|
||||
notifications = notifications.toImmutableList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addErrorNotifications(
|
||||
prevState: StakingUiState,
|
||||
onReload: () -> Unit,
|
||||
feeError: GetFeeError?,
|
||||
sendingAmount: BigDecimal,
|
||||
feeValue: BigDecimal,
|
||||
) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val network = cryptoCurrency.network
|
||||
|
||||
if (feeError != null) {
|
||||
addFeeUnreachableNotification(
|
||||
feeError = feeError,
|
||||
tokenName = cryptoCurrencyStatusProvider().currency.name,
|
||||
onReload = onReload,
|
||||
)
|
||||
}
|
||||
addExceedBalanceNotification(
|
||||
feeAmount = feeValue,
|
||||
sendingAmount = sendingAmount,
|
||||
isSubtractionAvailable = isSubtractAvailable,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
)
|
||||
addExceedsBalanceNotification(
|
||||
cryptoCurrencyWarning = currencyWarning,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
shouldMergeFeeNetworkName = BlockchainUtils.isArbitrum(network.backendId),
|
||||
onClick = prevState.clickIntents::openTokenDetails,
|
||||
onAnalyticsEvent = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
)
|
||||
if (!BlockchainUtils.isCardano(network.id.value)) {
|
||||
addDustWarningNotification(
|
||||
dustValue = currencyCheck.dustValue,
|
||||
feeValue = feeValue,
|
||||
sendingAmount = sendingAmount,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
feeCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
)
|
||||
}
|
||||
addTransactionLimitErrorNotification(
|
||||
utxoLimit = currencyCheck.utxoAmountLimit,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
onReduceClick = prevState.clickIntents::onAmountReduceToClick,
|
||||
)
|
||||
addReserveAmountErrorNotification(
|
||||
reserveAmount = currencyCheck.reserveAmount,
|
||||
sendingAmount = sendingAmount,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
isAccountFunded = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addWarningNotifications(
|
||||
prevState: StakingUiState,
|
||||
amountState: AmountState.Data,
|
||||
feeState: FeeState.Content,
|
||||
sendingAmount: BigDecimal,
|
||||
isFeeCoverage: Boolean,
|
||||
) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val appCurrency = appCurrencyProvider()
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
|
||||
addExistentialWarningNotification(
|
||||
existentialDeposit = currencyCheck.existentialDeposit,
|
||||
feeAmount = feeState.fee?.amount?.value.orZero(),
|
||||
receivedAmount = sendingAmount,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
onReduceClick = prevState.clickIntents::onAmountReduceByClick,
|
||||
)
|
||||
addFeeCoverageNotification(
|
||||
isFeeCoverage = isFeeCoverage,
|
||||
amountField = amountState.amountTextField,
|
||||
sendingValue = sendingAmount,
|
||||
appCurrency = appCurrency,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
)
|
||||
|
||||
// blockchain specific
|
||||
addValidateTransactionNotifications(
|
||||
dustValue = currencyCheck.dustValue.orZero(),
|
||||
fee = feeState.fee,
|
||||
validationError = validatorError,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
onReduceClick = prevState.clickIntents::onAmountReduceToClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.amount
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class AmountReduceByStateTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val value: ReduceByData,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
return prevState.copy(
|
||||
amountState = AmountReduceByTransformer(cryptoCurrencyStatus, value).transform(prevState.amountState),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.amount
|
||||
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceToTransformer
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class AmountReduceToStateTransformer(
|
||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val value: BigDecimal,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
return prevState.copy(
|
||||
amountState = AmountReduceToTransformer(cryptoCurrencyStatus, value).transform(prevState.amountState),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue