Updated on 2026-08-14
This commit is contained in:
parent
25c73300fe
commit
0ddd171bb2
7 changed files with 155 additions and 85 deletions
|
|
@ -277,11 +277,9 @@ internal object TokensDomainModule {
|
|||
@Singleton
|
||||
fun provideGetMinimumTransactionAmountSyncUseCase(
|
||||
currencyChecksRepository: CurrencyChecksRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetMinimumTransactionAmountSyncUseCase {
|
||||
return GetMinimumTransactionAmountSyncUseCase(
|
||||
currencyChecksRepository = currencyChecksRepository,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.CRYPTO_FEE
|
|||
import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.CURRENCY_SPACE
|
||||
import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.FORMAT_THRESHOLD
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.utils.StringsSigns.NON_BREAKING_SPACE
|
||||
import com.tangem.utils.extensions.isNotWhitespace
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
|
@ -16,6 +17,7 @@ open class BigDecimalCryptoFormat(
|
|||
val symbol: String,
|
||||
val decimals: Int,
|
||||
val locale: Locale = Locale.getDefault(),
|
||||
val ignoreSymbolPosition: Boolean = false,
|
||||
) : BigDecimalFormat {
|
||||
|
||||
override fun invoke(value: BigDecimal): String = defaultAmount()(value)
|
||||
|
|
@ -48,11 +50,13 @@ fun BigDecimalFormatScope.crypto(
|
|||
|
||||
fun BigDecimalFormatScope.crypto(
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
ignoreSymbolPosition: Boolean = false,
|
||||
locale: Locale = Locale.getDefault(),
|
||||
): BigDecimalCryptoFormat {
|
||||
return BigDecimalCryptoFormat(
|
||||
symbol = cryptoCurrency.symbol,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
ignoreSymbolPosition = ignoreSymbolPosition,
|
||||
locale = locale,
|
||||
)
|
||||
}
|
||||
|
|
@ -60,19 +64,28 @@ fun BigDecimalFormatScope.crypto(
|
|||
// == Formatters ==
|
||||
|
||||
fun BigDecimalCryptoFormat.defaultAmount() = BigDecimalFormat { value ->
|
||||
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
|
||||
currency = usdCurrency
|
||||
maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8)
|
||||
minimumFractionDigits = 2
|
||||
isGroupingUsed = true
|
||||
roundingMode = RoundingMode.HALF_UP
|
||||
if (ignoreSymbolPosition) {
|
||||
val formatter = NumberFormat.getInstance(locale).apply {
|
||||
maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8)
|
||||
minimumFractionDigits = 2
|
||||
isGroupingUsed = true
|
||||
roundingMode = RoundingMode.HALF_UP
|
||||
}
|
||||
formatter.format(value) + NON_BREAKING_SPACE + symbol
|
||||
} else {
|
||||
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
|
||||
currency = usdCurrency
|
||||
maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8)
|
||||
minimumFractionDigits = 2
|
||||
isGroupingUsed = true
|
||||
roundingMode = RoundingMode.HALF_UP
|
||||
}
|
||||
formatter.format(value)
|
||||
.replaceFiatSymbolWithCrypto(
|
||||
fiatCurrencySymbol = usdCurrency.getSymbol(locale),
|
||||
cryptoCurrencySymbol = symbol,
|
||||
)
|
||||
}
|
||||
|
||||
formatter.format(value)
|
||||
.replaceFiatSymbolWithCrypto(
|
||||
fiatCurrencySymbol = usdCurrency.getSymbol(locale),
|
||||
cryptoCurrencySymbol = symbol,
|
||||
)
|
||||
}
|
||||
|
||||
fun BigDecimalCryptoFormat.shorted() = BigDecimalFormat { value ->
|
||||
|
|
|
|||
|
|
@ -95,8 +95,14 @@ internal object TokensDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCurrencyChecksRepository(walletManagersFacade: WalletManagersFacade): CurrencyChecksRepository {
|
||||
return DefaultCurrencyChecksRepository(walletManagersFacade = walletManagersFacade)
|
||||
fun provideCurrencyChecksRepository(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
): CurrencyChecksRepository {
|
||||
return DefaultCurrencyChecksRepository(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
coroutineDispatchers = coroutineDispatcherProvider,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -12,10 +12,13 @@ import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
|
|||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class DefaultCurrencyChecksRepository(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val coroutineDispatchers: CoroutineDispatcherProvider,
|
||||
) : CurrencyChecksRepository {
|
||||
override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? {
|
||||
val manager = walletManagersFacade.getOrCreateWalletManager(
|
||||
|
|
@ -45,12 +48,14 @@ internal class DefaultCurrencyChecksRepository(
|
|||
}
|
||||
|
||||
override suspend fun getMinimumSendAmount(userWalletId: UserWalletId, network: Network): BigDecimal? {
|
||||
val manager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
)
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
val manager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
)
|
||||
|
||||
return if (manager is MinimumSendAmountProvider) manager.getMinimumSendAmount() else null
|
||||
if (manager is MinimumSendAmountProvider) manager.getMinimumSendAmount() else null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getFeeResourceAmount(userWalletId: UserWalletId, network: Network): CurrencyAmount? {
|
||||
|
|
|
|||
|
|
@ -5,22 +5,17 @@ import arrow.core.raise.either
|
|||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
class GetMinimumTransactionAmountSyncUseCase(
|
||||
private val currencyChecksRepository: CurrencyChecksRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): Either<Throwable, BigDecimal?> = withContext(dispatchers.io) {
|
||||
either {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
currencyChecksRepository.getMinimumSendAmount(userWalletId, cryptoCurrency.network)
|
||||
}
|
||||
): Either<Throwable, BigDecimal?> = either {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
currencyChecksRepository.getMinimumSendAmount(userWalletId, cryptoCurrency.network)
|
||||
}
|
||||
}
|
||||
|
|
@ -172,10 +172,15 @@ internal class StateBuilder(
|
|||
val canSelectReceiveToken = mainTokenId != toToken.id.value
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
val sendInput = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable).copy(
|
||||
isError = false,
|
||||
header = TextReference.Res(R.string.swapping_from_title),
|
||||
)
|
||||
val sendInputType = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable)
|
||||
val sendInput = if (sendInputType.isError) {
|
||||
sendInputType
|
||||
} else {
|
||||
sendInputType.copy(
|
||||
isError = false,
|
||||
header = TextReference.Res(R.string.swapping_from_title),
|
||||
)
|
||||
}
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = sendInput,
|
||||
|
|
@ -252,10 +257,16 @@ internal class StateBuilder(
|
|||
} else {
|
||||
TextReference.Res(R.string.swapping_from_title)
|
||||
}
|
||||
val sendInput = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable).copy(
|
||||
isError = isInsufficientFunds,
|
||||
header = insufficientFundsHeader,
|
||||
)
|
||||
val sendCardType = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable)
|
||||
val sendInput = if (sendCardType.isError && !isInsufficientFunds) {
|
||||
// if any error in inputField and funds enough -> show that error else show fund is not enough error
|
||||
sendCardType
|
||||
} else {
|
||||
sendCardType.copy(
|
||||
isError = isInsufficientFunds,
|
||||
header = insufficientFundsHeader,
|
||||
)
|
||||
}
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = sendInput,
|
||||
|
|
@ -863,14 +874,36 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
|
||||
fun updateSwapAmount(uiState: SwapStateHolder, amount: String): SwapStateHolder {
|
||||
fun updateSwapAmount(
|
||||
uiState: SwapStateHolder,
|
||||
amountFormatted: String,
|
||||
amountRaw: String,
|
||||
fromToken: CryptoCurrency,
|
||||
minTxAmount: BigDecimal?,
|
||||
): SwapStateHolder {
|
||||
if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState
|
||||
val amountToSend = amountRaw.toBigDecimalOrNull()
|
||||
val sendInput = if (minTxAmount != null && amountToSend != null && amountToSend < minTxAmount) {
|
||||
val minAmountFormatted = minTxAmount.format {
|
||||
crypto(cryptoCurrency = fromToken, ignoreSymbolPosition = true)
|
||||
}
|
||||
(uiState.sendCardData.type as? TransactionCardType.Inputtable)?.copy(
|
||||
isError = true,
|
||||
header = resourceReference(R.string.transfer_min_amount_error, wrappedList(minAmountFormatted)),
|
||||
) ?: uiState.sendCardData.type
|
||||
} else {
|
||||
(uiState.sendCardData.type as? TransactionCardType.Inputtable)?.copy(
|
||||
isError = false,
|
||||
header = TextReference.Res(R.string.swapping_from_title),
|
||||
) ?: uiState.sendCardData.type
|
||||
}
|
||||
return uiState.copy(
|
||||
sendCardData = uiState.sendCardData.copy(
|
||||
amountTextFieldValue = TextFieldValue(
|
||||
text = amount,
|
||||
selection = TextRange(amount.length),
|
||||
text = amountFormatted,
|
||||
selection = TextRange(amountFormatted.length),
|
||||
),
|
||||
type = sendInput,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
|||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -83,6 +84,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
private val getCardInfoUseCase: GetCardInfoUseCase,
|
||||
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase,
|
||||
swapInteractorFactory: SwapInteractor.Factory,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
|
|
@ -842,60 +844,78 @@ internal class SwapViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onChangeCardsClicked() {
|
||||
val newFromToken = dataState.toCryptoCurrency
|
||||
val newToToken = dataState.fromCryptoCurrency
|
||||
viewModelScope.launch {
|
||||
val newFromToken = dataState.toCryptoCurrency
|
||||
val newToToken = dataState.fromCryptoCurrency
|
||||
|
||||
if (newFromToken != null && newToToken != null) {
|
||||
isAmountChangedByUser = true
|
||||
if (newFromToken != null && newToToken != null) {
|
||||
isAmountChangedByUser = true
|
||||
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = newFromToken,
|
||||
toCryptoCurrency = newToToken,
|
||||
)
|
||||
isOrderReversed = !isOrderReversed
|
||||
dataState.tokensDataState?.let {
|
||||
updateTokensState(it)
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = newFromToken,
|
||||
toCryptoCurrency = newToToken,
|
||||
)
|
||||
isOrderReversed = !isOrderReversed
|
||||
dataState.tokensDataState?.let {
|
||||
updateTokensState(it)
|
||||
}
|
||||
|
||||
val minTxAmount = getMinimumTransactionAmountSyncUseCase(
|
||||
userWalletId,
|
||||
newFromToken,
|
||||
).getOrNull()
|
||||
val decimals = newFromToken.currency.decimals
|
||||
lastAmount.value = cutAmountWithDecimals(decimals, lastAmount.value)
|
||||
uiState = stateBuilder.updateSwapAmount(
|
||||
uiState = uiState,
|
||||
amountFormatted = inputNumberFormatter.formatWithThousands(lastAmount.value, decimals),
|
||||
amountRaw = lastAmount.value,
|
||||
fromToken = newFromToken.currency,
|
||||
minTxAmount = minTxAmount,
|
||||
)
|
||||
startLoadingQuotes(
|
||||
fromToken = newFromToken,
|
||||
toToken = newToToken,
|
||||
amount = lastAmount.value,
|
||||
toProvidersList = findSwapProviders(newFromToken, newToToken),
|
||||
)
|
||||
}
|
||||
|
||||
val decimals = newFromToken.currency.decimals
|
||||
lastAmount.value = cutAmountWithDecimals(decimals, lastAmount.value)
|
||||
uiState = stateBuilder.updateSwapAmount(
|
||||
uiState,
|
||||
inputNumberFormatter.formatWithThousands(lastAmount.value, decimals),
|
||||
)
|
||||
startLoadingQuotes(
|
||||
fromToken = newFromToken,
|
||||
toToken = newToToken,
|
||||
amount = lastAmount.value,
|
||||
toProvidersList = findSwapProviders(newFromToken, newToToken),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAmountChanged(value: String) {
|
||||
val fromToken = dataState.fromCryptoCurrency
|
||||
val toToken = dataState.toCryptoCurrency
|
||||
if (fromToken != null) {
|
||||
val decimals = fromToken.currency.decimals
|
||||
val cutValue = cutAmountWithDecimals(decimals, value)
|
||||
lastAmount.value = cutValue
|
||||
uiState = stateBuilder.updateSwapAmount(
|
||||
uiState = uiState,
|
||||
amount = inputNumberFormatter.formatWithThousands(cutValue, decimals),
|
||||
)
|
||||
viewModelScope.launch {
|
||||
val fromToken = dataState.fromCryptoCurrency
|
||||
val toToken = dataState.toCryptoCurrency
|
||||
if (fromToken != null) {
|
||||
val decimals = fromToken.currency.decimals
|
||||
val cutValue = cutAmountWithDecimals(decimals, value)
|
||||
val minTxAmount = getMinimumTransactionAmountSyncUseCase(
|
||||
userWalletId,
|
||||
fromToken,
|
||||
).getOrNull()
|
||||
lastAmount.value = cutValue
|
||||
uiState = stateBuilder.updateSwapAmount(
|
||||
uiState = uiState,
|
||||
amountFormatted = inputNumberFormatter.formatWithThousands(cutValue, decimals),
|
||||
amountRaw = lastAmount.value,
|
||||
fromToken = fromToken.currency,
|
||||
minTxAmount = minTxAmount,
|
||||
)
|
||||
|
||||
if (toToken != null) {
|
||||
if (toToken.value.amount != null) {
|
||||
isAmountChangedByUser = true
|
||||
}
|
||||
if (toToken != null) {
|
||||
if (toToken.value.amount != null) {
|
||||
isAmountChangedByUser = true
|
||||
}
|
||||
|
||||
amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) {
|
||||
startLoadingQuotes(
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
amount = lastAmount.value,
|
||||
toProvidersList = findSwapProviders(fromToken, toToken),
|
||||
)
|
||||
amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) {
|
||||
startLoadingQuotes(
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
amount = lastAmount.value,
|
||||
toProvidersList = findSwapProviders(fromToken, toToken),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue