Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-15 15:20:29 +02:00
parent 626d0914a0
commit 88ab462aa7
26 changed files with 365 additions and 428 deletions

View file

@ -1,7 +1,7 @@
==========================================
Detekt Baseline Updater & Issue Counter
==========================================
Date: 2025-12-12 13:17:20
Date: 2025-12-13 00:34:09
Step 1: Running detekt to check for new issues...
@ -17,13 +17,13 @@ Counting issues in baseline files...
==========================================
Summary:
Total Issues: 1378
Modules with Issues: 72
Average Issues per Module: 19
Total Issues: 1271
Modules with Issues: 69
Average Issues per Module: 18
Progress:
Fixed: 555 out of 1933 (28%)
Remaining: 1378
Fixed: 662 out of 1933 (34%)
Remaining: 1271
==========================================
All Modules with Issues (sorted by count)
@ -34,7 +34,6 @@ Module Issues
features/markets/impl 147
features/wallet/impl 144
features/onboarding-v2/impl 128
features/swap/impl 67
data/wallet-connect 54
features/hot-wallet/impl 51
features/walletconnect/impl 47
@ -45,7 +44,6 @@ domain/wallets 37
features/nft/impl 34
features/tester/impl 28
core/ui 26
features/swap/domain 25
domain/tokens 25
common/ui 24
features/yield-supply/impl 21
@ -54,7 +52,6 @@ domain/models 21
data/visa 21
data/nft 19
core/pagination 16
features/swap/data 15
domain/staking/models 13
data/wallets 13
features/token-recieve/impl 11

View file

@ -1,21 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>BooleanPropertyNaming:DefaultSwapTransactionRepository.kt$DefaultSwapTransactionRepository$val fromCurrency = it.fromCryptoCurrencyId == cryptoCurrencyId.value</ID>
<ID>BooleanPropertyNaming:DefaultSwapTransactionRepository.kt$DefaultSwapTransactionRepository$val toCurrency = it.toCryptoCurrencyId == cryptoCurrencyId.value</ID>
<ID>MultilineLambdaItParameter:DefaultSwapRepository.kt$DefaultSwapRepository${ Timber.e("getExchangeStatus error: $it") raise(UnknownError(it.message)) }</ID>
<ID>MultilineLambdaItParameter:DefaultSwapRepository.kt$DefaultSwapRepository${ val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, it) val isAvailableForSwap = rampStateManager.checkAssetRequirements(requirements) isAvailableForSwap }</ID>
<ID>MultilineLambdaItParameter:DefaultSwapTransactionRepository.kt$DefaultSwapTransactionRepository${ converter.convertBack( value = it, userWallet = userWallet, accountList = accountList, txStatuses = txStatuses, ) }</ID>
<ID>MultilineLambdaItParameter:DefaultSwapTransactionRepository.kt$DefaultSwapTransactionRepository${ converter.convertBack( value = it, userWallet = userWallet, accountList = accountList, txStatuses = txStatuses, onFilter = { it.swapTxTypeDTO == SwapTxTypeDTO.Swap }, ) }</ID>
<ID>MultilineLambdaItParameter:DefaultSwapTransactionRepository.kt$DefaultSwapTransactionRepository${ it.checkId( checkUserWalletId = userWalletId, fromCurrencyId = fromCryptoCurrency.id, toCurrencyId = toCryptoCurrency.id, ) }</ID>
<ID>MultilineLambdaItParameter:DefaultSwapTransactionRepository.kt$DefaultSwapTransactionRepository${ storeTransactionState( txId = transaction.txId, status = it, accountWithCurrency = null, ) }</ID>
<ID>MultilineLambdaItParameter:DefaultSwapTransactionRepository.kt$DefaultSwapTransactionRepository${ val isUserWallet = it.userWalletId == userWallet.walletId.stringValue val fromCurrency = it.fromCryptoCurrencyId == cryptoCurrencyId.value isUserWallet &amp;&amp; fromCurrency }</ID>
<ID>MultilineLambdaItParameter:DefaultSwapTransactionRepository.kt$DefaultSwapTransactionRepository${ val isUserWallet = it.userWalletId == userWallet.walletId.stringValue val toCurrency = it.toCryptoCurrencyId == cryptoCurrencyId.value isUserWallet &amp;&amp; toCurrency }</ID>
<ID>MultilineLambdaItParameter:ExpressDataConverter.kt$ExpressDataConverter${ if (it == "0") { BigDecimal.ZERO } else { requireNotNull(it.toBigDecimalOrNull()) { "wrong amount format, use only digits" } } }</ID>
<ID>NoNameShadowing:DefaultSwapTransactionRepository.kt$DefaultSwapTransactionRepository${ it.swapTxTypeDTO == SwapTxTypeDTO.Swap }</ID>
<ID>NoNameShadowing:DefaultSwapTransactionRepository.kt$DefaultSwapTransactionRepository${ it.txId == txId }</ID>
<ID>UseOrEmpty:DefaultSwapRepository.kt$DefaultSwapRepository$ex.errorBody ?: ""</ID>
<ID>UseOrEmpty:DefaultSwapRepository.kt$DefaultSwapRepository$exception.errorBody ?: ""</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -118,7 +118,7 @@ internal class DefaultSwapRepository(
)
} catch (exception: Exception) {
if (exception is ApiResponseError.HttpException) {
throw ExpressException(errorsDataConverter.convert(exception.errorBody ?: ""))
throw ExpressException(errorsDataConverter.convert(exception.errorBody.orEmpty()))
} else {
throw exception
}
@ -138,12 +138,12 @@ internal class DefaultSwapRepository(
network = initialCurrency.network,
)
val currenciesList = currencyList
.filter {
val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, it)
.filter { currency ->
val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, currency)
val isAvailableForSwap = rampStateManager.checkAssetRequirements(requirements)
isAvailableForSwap
}
.map { leastTokenInfoConverter.convert(it) }
.map { currency -> leastTokenInfoConverter.convert(currency) }
val pairsDeferred = async {
getPairsInternal(
@ -174,7 +174,7 @@ internal class DefaultSwapRepository(
)
} catch (exception: Exception) {
if (exception is ApiResponseError.HttpException) {
throw ExpressException(errorsDataConverter.convert(exception.errorBody ?: ""))
throw ExpressException(errorsDataConverter.convert(exception.errorBody.orEmpty()))
} else {
throw exception
}
@ -221,9 +221,9 @@ internal class DefaultSwapRepository(
.getOrThrow(),
)
},
catch = {
Timber.e("getExchangeStatus error: $it")
raise(UnknownError(it.message))
catch = { exception ->
Timber.e("getExchangeStatus error: $exception")
raise(UnknownError(exception.message))
},
)
}
@ -420,7 +420,7 @@ internal class DefaultSwapRepository(
private fun getDataError(ex: Exception): ExpressDataError {
return if (ex is ApiResponseError.HttpException) {
errorsDataConverter.convert(ex.errorBody ?: "")
errorsDataConverter.convert(ex.errorBody.orEmpty())
} else {
ExpressDataError.UnknownError
}

View file

@ -51,10 +51,10 @@ internal class DefaultSwapTransactionRepository(
toAccount: Account.CryptoPortfolio?,
transaction: SavedSwapTransactionModel,
) {
transaction.status?.let {
transaction.status?.let { status ->
storeTransactionState(
txId = transaction.txId,
status = it,
status = status,
accountWithCurrency = null,
)
}
@ -63,8 +63,8 @@ internal class DefaultSwapTransactionRepository(
key = PreferencesKeys.SWAP_TRANSACTIONS_KEY,
)
val tokenTransactions = savedTransactions
?.firstOrNull {
it.checkId(
?.firstOrNull { savedTx ->
savedTx.checkId(
checkUserWalletId = userWalletId,
fromCurrencyId = fromCryptoCurrency.id,
toCurrencyId = toCryptoCurrency.id,
@ -73,7 +73,7 @@ internal class DefaultSwapTransactionRepository(
?.transactions
?.addOrReplace(
item = transaction,
predicate = { it.txId == transaction.txId },
predicate = { tx -> tx.txId == transaction.txId },
) ?: listOf(transaction)
mutablePreferences.setObject(
@ -117,31 +117,31 @@ internal class DefaultSwapTransactionRepository(
},
) { savedTransactions, txStatuses, accountList ->
val currencyToTxs = savedTransactions?.filter {
val isUserWallet = it.userWalletId == userWallet.walletId.stringValue
val toCurrency = it.toCryptoCurrencyId == cryptoCurrencyId.value
isUserWallet && toCurrency
val currencyToTxs = savedTransactions?.filter { savedTx ->
val isUserWallet = savedTx.userWalletId == userWallet.walletId.stringValue
val isToCurrency = savedTx.toCryptoCurrencyId == cryptoCurrencyId.value
isUserWallet && isToCurrency
}
val currencyFromTxs = savedTransactions?.filter {
val isUserWallet = it.userWalletId == userWallet.walletId.stringValue
val fromCurrency = it.fromCryptoCurrencyId == cryptoCurrencyId.value
isUserWallet && fromCurrency
val currencyFromTxs = savedTransactions?.filter { savedTx ->
val isUserWallet = savedTx.userWalletId == userWallet.walletId.stringValue
val isFromCurrency = savedTx.fromCryptoCurrencyId == cryptoCurrencyId.value
isUserWallet && isFromCurrency
}
val toTxs = currencyToTxs?.mapNotNull {
val toTxs = currencyToTxs?.mapNotNull { savedTx ->
converter.convertBack(
value = it,
value = savedTx,
userWallet = userWallet,
accountList = accountList,
txStatuses = txStatuses,
onFilter = { it.swapTxTypeDTO == SwapTxTypeDTO.Swap },
onFilter = { tx -> tx.swapTxTypeDTO == SwapTxTypeDTO.Swap },
)
}.orEmpty()
val fromTxs = currencyFromTxs?.mapNotNull {
val fromTxs = currencyFromTxs?.mapNotNull { savedTx ->
converter.convertBack(
value = it,
value = savedTx,
userWallet = userWallet,
accountList = accountList,
txStatuses = txStatuses,
@ -161,9 +161,9 @@ internal class DefaultSwapTransactionRepository(
)
val tokenTransactions = savedList
?.asSequence()
?.map {
it.copy(transactions = it.transactions.filterNot { it.txId == txId })
}?.filterNot { it.transactions.isEmpty() }
?.map { savedTx ->
savedTx.copy(transactions = savedTx.transactions.filterNot { tx -> tx.txId == txId })
}?.filterNot { savedTx -> savedTx.transactions.isEmpty() }
?.toList()
if (tokenTransactions.isNullOrEmpty()) {
@ -261,8 +261,8 @@ internal class DefaultSwapTransactionRepository(
toAccount = toAccount,
tokenTransactions = transactions,
),
predicate = {
it.checkId(
predicate = { savedTx ->
savedTx.checkId(
checkUserWalletId = userWalletId,
fromCurrencyId = fromCryptoCurrency.id,
toCurrencyId = toCryptoCurrency.id,

View file

@ -25,11 +25,11 @@ internal class ExpressDataConverter : Converter<ExchangeDataResponseWithTxDetail
dataResponse: ExchangeDataResponse,
): ExpressTransactionModel {
return if (transactionDto.txType == TxType.SWAP) {
val otherNativeFeeWei = transactionDto.otherNativeFee?.let {
if (it == "0") {
val otherNativeFeeWei = transactionDto.otherNativeFee?.let { feeValue ->
if (feeValue == "0") {
BigDecimal.ZERO
} else {
requireNotNull(it.toBigDecimalOrNull()) { "wrong amount format, use only digits" }
requireNotNull(feeValue.toBigDecimalOrNull()) { "wrong amount format, use only digits" }
}
}
ExpressTransactionModel.DEX(

View file

@ -1,31 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>BooleanPropertyNaming:SwapInteractorImpl.kt$SwapInteractorImpl$val allQuotesFound = cachedQuotes?.all { it.value !is QuoteStatus.Empty } == true</ID>
<ID>BooleanPropertyNaming:SwapInteractorImpl.kt$SwapInteractorImpl$val currencyFilter = it.currency.network.backendId != currency.network.backendId || it.currency.getContractAddress() != currency.getContractAddress()</ID>
<ID>BooleanPropertyNaming:SwapInteractorImpl.kt$SwapInteractorImpl$val notCustomTokenFilter = !it.currency.isCustom</ID>
<ID>BooleanPropertyNaming:SwapInteractorImpl.kt$SwapInteractorImpl$val statusFilter = it.value is CryptoCurrencyStatus.Loaded || it.value is CryptoCurrencyStatus.NoAccount</ID>
<ID>CanBeNonNullable:SwapInteractorImpl.kt$SwapInteractorImpl$spenderAddress: String?</ID>
<ID>MaxChainedCallsOnSameLine:SwapInteractorImpl.kt$SwapInteractorImpl$currencyToGet.value.networkAddress?.defaultAddress?.value.orEmpty()</ID>
<ID>MaxChainedCallsOnSameLine:SwapInteractorImpl.kt$SwapInteractorImpl$currencyToSend.value.networkAddress?.defaultAddress?.value.orEmpty()</ID>
<ID>MaxChainedCallsOnSameLine:SwapInteractorImpl.kt$SwapInteractorImpl$currencyToSendStatus.value.networkAddress?.defaultAddress?.value.orEmpty()</ID>
<ID>MaxChainedCallsOnSameLine:SwapInteractorImpl.kt$SwapInteractorImpl$fromToken.value.networkAddress?.defaultAddress?.value.orEmpty()</ID>
<ID>MaxChainedCallsOnSameLine:SwapInteractorImpl.kt$SwapInteractorImpl$toToken.value.networkAddress?.defaultAddress?.value.orEmpty()</ID>
<ID>MultilineLambdaItParameter:SwapInteractorImpl.kt$SwapInteractorImpl${ Timber.e(it, "Failed to create approveTransaction") return SwapTransactionState.Error.UnknownError }</ID>
<ID>MultilineLambdaItParameter:SwapInteractorImpl.kt$SwapInteractorImpl${ Timber.e(it, "Failed to create swap CEX tx data") return SwapTransactionState.Error.UnknownError }</ID>
<ID>MultilineLambdaItParameter:SwapInteractorImpl.kt$SwapInteractorImpl${ Timber.e(it, "Failed to create swap dex tx data") return SwapTransactionState.Error.UnknownError }</ID>
<ID>MultilineLambdaItParameter:SwapInteractorImpl.kt$SwapInteractorImpl${ it.contractAddress.equals(feePaidCurrency.contractAddress, ignoreCase = true) &amp;&amp; it.network.derivationPath == fromTokenStatus.currency.network.derivationPath }</ID>
<ID>MultilineLambdaItParameter:SwapInteractorImpl.kt$SwapInteractorImpl${ tokenInfoForFilter(it).contractAddress == currency.getContractAddress() &amp;&amp; tokenInfoForFilter(it).network == currency.network.backendId }</ID>
<ID>MultilineLambdaItParameter:SwapInteractorImpl.kt$SwapInteractorImpl${ val currencyFilter = it.currency.network.backendId != currency.network.backendId || it.currency.getContractAddress() != currency.getContractAddress() val statusFilter = it.value is CryptoCurrencyStatus.Loaded || it.value is CryptoCurrencyStatus.NoAccount val notCustomTokenFilter = !it.currency.isCustom statusFilter &amp;&amp; currencyFilter &amp;&amp; notCustomTokenFilter }</ID>
<ID>MultilineLambdaItParameter:SwapInteractorImpl.kt$SwapInteractorImpl${ val listTokenInfo = tokenInfoForAvailable(it) if (cryptoCurrencyStatuses.currency.network.backendId == listTokenInfo.network &amp;&amp; cryptoCurrencyStatuses.currency.getContractAddress() == listTokenInfo.contractAddress &amp;&amp; isAvailableForSwap ) { it.providers } else { null } }</ID>
<ID>NamedArguments:SwapInteractorImpl.kt$SwapInteractorImpl$getCoinBalanceAfterTransaction(fromTokenStatus, amount, includeFeeInAmount, fee)</ID>
<ID>NamedArguments:SwapInteractorImpl.kt$SwapInteractorImpl$isAllowedToSpend(networkId, fromToken.currency, amount, it)</ID>
<ID>NamedArguments:SwapInteractorImpl.kt$SwapInteractorImpl$tryGetFromCache(userWallet, initialCryptoCurrency, state, isReverseFromTo)</ID>
<ID>NamedArguments:SwapInteractorImpl.kt$SwapInteractorImpl$tryGetFromCacheV2(userWallet, initialCryptoCurrency, state, isReverseFromTo)</ID>
<ID>NoNameShadowing:SwapInteractorImpl.kt$SwapInteractorImpl$account</ID>
<ID>NoNameShadowing:SwapInteractorImpl.kt$SwapInteractorImpl${ it.isAvailable }</ID>
<ID>NullableToStringCall:SwapInteractorImpl.kt$SwapInteractorImpl$${e.message}</ID>
<ID>SuspendFunSwallowedCancellation:SwapInteractorImpl.kt$SwapInteractorImpl$runCatching</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -43,6 +43,7 @@ import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.*
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.utils.coroutines.runSuspendCatching
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.models.ExpressDataError
import com.tangem.feature.swap.domain.models.SwapAmount
@ -122,12 +123,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
.getOrElse { emptyList() }
val walletCurrencyStatusesExceptInitial = walletCurrencyStatuses
.filter {
val currencyFilter = it.currency.network.backendId != currency.network.backendId ||
it.currency.getContractAddress() != currency.getContractAddress()
val statusFilter = it.value is CryptoCurrencyStatus.Loaded || it.value is CryptoCurrencyStatus.NoAccount
val notCustomTokenFilter = !it.currency.isCustom
statusFilter && currencyFilter && notCustomTokenFilter
.filter { status ->
val isDifferentCurrency = status.currency.network.backendId != currency.network.backendId ||
status.currency.getContractAddress() != currency.getContractAddress()
val hasValidStatus =
status.value is CryptoCurrencyStatus.Loaded || status.value is CryptoCurrencyStatus.NoAccount
val isNotCustomToken = !status.currency.isCustom
hasValidStatus && isDifferentCurrency && isNotCustomToken
}
if (walletCurrencyStatusesExceptInitial.isEmpty()) {
@ -169,15 +171,15 @@ internal class SwapInteractorImpl @AssistedInject constructor(
val walletAccountCurrencyStatusesExceptInitial = walletAccountCurrencyStatuses
.mapNotNull { accountStatus ->
val filteredCurrencies = accountStatus.flattenCurrencies().filter {
val currencyFilter = it.currency.network.backendId != currency.network.backendId ||
it.currency.getContractAddress() != currency.getContractAddress()
val filteredCurrencies = accountStatus.flattenCurrencies().filter { status ->
val isDifferentCurrency = status.currency.network.backendId != currency.network.backendId ||
status.currency.getContractAddress() != currency.getContractAddress()
val statusFilter =
it.value is CryptoCurrencyStatus.Loaded || it.value is CryptoCurrencyStatus.NoAccount
val notCustomTokenFilter = !it.currency.isCustom
val hasValidStatus =
status.value is CryptoCurrencyStatus.Loaded || status.value is CryptoCurrencyStatus.NoAccount
val isNotCustomToken = !status.currency.isCustom
statusFilter && currencyFilter && notCustomTokenFilter
hasValidStatus && isDifferentCurrency && isNotCustomToken
}
if (filteredCurrencies.isNotEmpty()) {
@ -228,9 +230,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
tokenInfoForFilter: (SwapPairLeast) -> LeastTokenInfo,
tokenInfoForAvailable: (SwapPairLeast) -> LeastTokenInfo,
): CurrenciesGroup {
val filteredPairs = leastPairs.filter {
tokenInfoForFilter(it).contractAddress == currency.getContractAddress() &&
tokenInfoForFilter(it).network == currency.network.backendId
val filteredPairs = leastPairs.filter { pair ->
tokenInfoForFilter(pair).contractAddress == currency.getContractAddress() &&
tokenInfoForFilter(pair).network == currency.network.backendId
}
val availableCryptoCurrencies = cryptoCurrenciesList.mapNotNull { cryptoCurrencyStatus ->
@ -261,16 +263,16 @@ internal class SwapInteractorImpl @AssistedInject constructor(
tokenInfoForFilter: (SwapPairLeast) -> LeastTokenInfo,
tokenInfoForAvailable: (SwapPairLeast) -> LeastTokenInfo,
): CurrenciesGroup {
val filteredPairs = leastPairs.filter {
tokenInfoForFilter(it).contractAddress == currency.getContractAddress() &&
tokenInfoForFilter(it).network == currency.network.backendId
val filteredPairs = leastPairs.filter { pair ->
tokenInfoForFilter(pair).contractAddress == currency.getContractAddress() &&
tokenInfoForFilter(pair).network == currency.network.backendId
}
val accountCurrencyList = cryptoCurrenciesList.mapNotNull { (account, currencyStatusList) ->
val account = account as? Account.CryptoPortfolio ?: return@mapNotNull null
val accountCurrencyList = cryptoCurrenciesList.mapNotNull { (accountEntry, currencyStatusList) ->
val cryptoPortfolio = accountEntry as? Account.CryptoPortfolio ?: return@mapNotNull null
AccountSwapAvailability(
account = account,
account = cryptoPortfolio,
currencyList = currencyStatusList.map { currencyStatus ->
val providers = findProvidersForPair(
cryptoCurrencyStatuses = currencyStatus,
@ -280,7 +282,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
val isUnavailable = providers.isNullOrEmpty()
AccountSwapCurrency(
isAvailable = !isUnavailable,
account = account,
account = cryptoPortfolio,
cryptoCurrencyStatus = currencyStatus,
providers = providers.orEmpty(),
)
@ -304,13 +306,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
val requirements = getAssetRequirementsUseCase.invoke(userWalletId, cryptoCurrencyStatuses.currency).getOrNull()
val isAvailableForSwap = rampStateManager.checkAssetRequirements(requirements)
return swapPairsLeastList.firstNotNullOfOrNull {
val listTokenInfo = tokenInfoForAvailable(it)
return swapPairsLeastList.firstNotNullOfOrNull { pair ->
val listTokenInfo = tokenInfoForAvailable(pair)
if (cryptoCurrencyStatuses.currency.network.backendId == listTokenInfo.network &&
cryptoCurrencyStatuses.currency.getContractAddress() == listTokenInfo.contractAddress &&
isAvailableForSwap
) {
it.providers
pair.providers
} else {
null
}
@ -351,8 +353,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
amount = amount?.value,
contractAddress = permissionOptions.forTokenContractAddress,
spenderAddress = permissionOptions.spenderAddress,
).getOrElse {
Timber.e(it, "Failed to create approveTransaction")
).getOrElse { error ->
Timber.e(error, "Failed to create approveTransaction")
return SwapTransactionState.Error.UnknownError
}
@ -450,6 +452,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
}.toMap()
}
@Suppress("LongMethod")
private suspend fun manageDex(
networkId: String,
fromToken: CryptoCurrencyStatus,
@ -487,8 +490,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
val fromTokenAddress = getTokenAddress(fromToken.currency)
val isAllowedToSpend = maybeQuotes.fold(
ifRight = { quotes ->
quotes.allowanceContract?.let {
isAllowedToSpend(networkId, fromToken.currency, amount, it)
quotes.allowanceContract?.let { allowanceContract ->
isAllowedToSpend(
networkId = networkId,
fromToken = fromToken.currency,
amount = amount,
spenderAddress = allowanceContract,
)
} != false
},
ifLeft = { false },
@ -626,7 +634,12 @@ internal class SwapInteractorImpl @AssistedInject constructor(
is TxFeeState.MultipleFeeState -> feeState.getFeeByType(selectedFee).feeValue
is TxFeeState.SingleFeeState -> feeState.fee.feeValue
}
val balanceAfterTransaction = getCoinBalanceAfterTransaction(fromTokenStatus, amount, includeFeeInAmount, fee)
val balanceAfterTransaction = getCoinBalanceAfterTransaction(
fromTokenStatus = fromTokenStatus,
amount = amount,
includeFeeInAmount = includeFeeInAmount,
fee = fee,
)
val amountToRequest = if (includeFeeInAmount is IncludeFeeInAmount.Included) {
includeFeeInAmount.amountSubtractFee
} else {
@ -866,8 +879,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
userWalletId = userWalletId,
network = currencyToSendStatus.currency.network,
txExtras = createDexTxExtras(dataToSign, currencyToSendStatus.currency.network, txFee.fee.getGasLimit()),
).getOrElse {
Timber.e(it, "Failed to create swap dex tx data")
).getOrElse { error ->
Timber.e(error, "Failed to create swap dex tx data")
return SwapTransactionState.Error.UnknownError
}
return handleSwapResult(
@ -938,11 +951,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
)
return result.fold(
ifRight = { txHash ->
val networkAddress = currencyToSendStatus.value.networkAddress
val fromAddress = networkAddress?.defaultAddress?.value.orEmpty()
repository.exchangeSent(
userWallet = userWallet,
txId = swapData.transaction.txId,
fromNetwork = currencyToSendStatus.currency.network.backendId,
fromAddress = currencyToSendStatus.value.networkAddress?.defaultAddress?.value.orEmpty(),
fromAddress = fromAddress,
payInAddress = payInAddress,
txHash = txHash,
payInExtraId = swapData.transaction.txExtraId,
@ -1000,12 +1015,16 @@ internal class SwapInteractorImpl @AssistedInject constructor(
expressOperationType: ExpressOperationType,
isTangemPayWithdrawal: Boolean,
): SwapTransactionState {
val fromNetworkAddress = currencyToSend.value.networkAddress
val fromAddress = fromNetworkAddress?.defaultAddress?.value.orEmpty()
val toNetworkAddress = currencyToGet.value.networkAddress
val toAddress = toNetworkAddress?.defaultAddress?.value.orEmpty()
val exchangeData = repository.getExchangeData(
userWallet = userWallet,
fromContractAddress = currencyToSend.currency.getContractAddress(),
fromNetwork = currencyToSend.currency.network.backendId,
toContractAddress = currencyToGet.currency.getContractAddress(),
fromAddress = currencyToSend.value.networkAddress?.defaultAddress?.value.orEmpty(),
fromAddress = fromAddress,
toNetwork = currencyToGet.currency.network.backendId,
fromAmount = amount.toStringWithRightOffset(),
fromDecimals = amount.decimals,
@ -1013,10 +1032,10 @@ internal class SwapInteractorImpl @AssistedInject constructor(
providerId = swapProvider.providerId,
rateType = RateType.FLOAT,
expressOperationType = expressOperationType,
toAddress = currencyToGet.value.networkAddress?.defaultAddress?.value.orEmpty(),
toAddress = toAddress,
refundAddress = currencyToSend.value.networkAddress?.defaultAddress?.value,
refundExtraId = null, // currently always null,
).getOrElse { return SwapTransactionState.Error.ExpressError(it) }
).getOrElse { error -> return SwapTransactionState.Error.ExpressError(error) }
val exchangeDataCex =
exchangeData.transaction as? ExpressTransactionModel.CEX ?: return SwapTransactionState.Error.UnknownError
@ -1063,8 +1082,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
destination = exchangeDataCex.txTo,
userWalletId = userWalletId,
network = currencyToSend.currency.network,
).getOrElse {
Timber.e(it, "Failed to create swap CEX tx data")
).getOrElse { error ->
Timber.e(error, "Failed to create swap CEX tx data")
return SwapTransactionState.Error.UnknownError
}
@ -1079,14 +1098,16 @@ internal class SwapInteractorImpl @AssistedInject constructor(
)
val derivationPath = currencyToSend.currency.network.derivationPath.value
val cexNetworkAddress = currencyToSend.value.networkAddress
val cexFromAddress = cexNetworkAddress?.defaultAddress?.value.orEmpty()
return result.fold(
ifLeft = { SwapTransactionState.Error.TransactionError(it) },
ifLeft = { error -> SwapTransactionState.Error.TransactionError(error) },
ifRight = { txHash ->
repository.exchangeSent(
userWallet = userWallet,
txId = exchangeDataCex.txId,
fromNetwork = currencyToSend.currency.network.backendId,
fromAddress = currencyToSend.value.networkAddress?.defaultAddress?.value.orEmpty(),
fromAddress = cexFromAddress,
payInAddress = txData.destinationAddress,
txHash = txHash,
payInExtraId = exchangeDataCex.txExtraId,
@ -1182,8 +1203,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
isReverseFromTo: Boolean,
): CryptoCurrencyStatus? {
val group = state.getGroupWithReverse(isReverseFromTo)
return initialToCurrencyResolver.tryGetFromCache(userWallet, initialCryptoCurrency, state, isReverseFromTo)
?: initialToCurrencyResolver.tryGetWithMaxAmount(state, isReverseFromTo)
return initialToCurrencyResolver.tryGetFromCache(
userWallet = userWallet,
initialCryptoCurrency = initialCryptoCurrency,
state = state,
isReverseFromTo = isReverseFromTo,
)
?: initialToCurrencyResolver.tryGetWithMaxAmount(state = state, isReverseFromTo = isReverseFromTo)
?: group.available.firstOrNull()?.currencyStatus
}
@ -1193,9 +1219,18 @@ internal class SwapInteractorImpl @AssistedInject constructor(
isReverseFromTo: Boolean,
): AccountSwapCurrency? {
val group = state.getGroupWithReverse(isReverseFromTo)
return initialToCurrencyResolver.tryGetFromCacheV2(userWallet, initialCryptoCurrency, state, isReverseFromTo)
?: initialToCurrencyResolver.tryGetWithMaxAmountV2(state, isReverseFromTo)
?: group.accountCurrencyList.firstNotNullOfOrNull { it.currencyList.firstOrNull { it.isAvailable } }
return initialToCurrencyResolver.tryGetFromCacheV2(
userWallet = userWallet,
initialCryptoCurrency = initialCryptoCurrency,
state = state,
isReverseFromTo = isReverseFromTo,
)
?: initialToCurrencyResolver.tryGetWithMaxAmountV2(state = state, isReverseFromTo = isReverseFromTo)
?: group.accountCurrencyList.firstNotNullOfOrNull { accountSwapAvailability ->
accountSwapAvailability.currencyList.firstOrNull { accountSwapCurrency ->
accountSwapCurrency.isAvailable
}
}
}
override fun getNativeToken(networkId: String): CryptoCurrency {
@ -1367,7 +1402,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
swapAmount = amount,
quotesLoadedState = swapState,
isAllowedToSpend = isAllowedToSpend,
spenderAddress = quoteModel.allowanceContract,
spenderAddress = requireNotNull(quoteModel.allowanceContract) {
"allowanceContract is required for DEX"
},
)
if (state !is SwapState.QuotesLoadedState) return state
state.copy(
@ -1548,19 +1585,23 @@ internal class SwapInteractorImpl @AssistedInject constructor(
selectedFee: FeeType,
expressOperationType: ExpressOperationType,
): SwapState {
val fromNetworkAddress = fromToken.value.networkAddress
val dexFromAddress = fromNetworkAddress?.defaultAddress?.value.orEmpty()
val toNetworkAddress = toToken.value.networkAddress
val dexToAddress = toNetworkAddress?.defaultAddress?.value.orEmpty()
return repository.getExchangeData(
userWallet = userWallet,
fromContractAddress = fromToken.currency.getContractAddress(),
fromNetwork = fromToken.currency.network.backendId,
toContractAddress = toToken.currency.getContractAddress(),
fromAddress = fromToken.value.networkAddress?.defaultAddress?.value.orEmpty(),
fromAddress = dexFromAddress,
toNetwork = toToken.currency.network.backendId,
fromAmount = amount.toStringWithRightOffset(),
fromDecimals = amount.decimals,
toDecimals = toToken.currency.decimals,
providerId = provider.providerId,
rateType = RateType.FLOAT,
toAddress = toToken.value.networkAddress?.defaultAddress?.value.orEmpty(),
toAddress = dexToAddress,
refundAddress = fromToken.value.networkAddress?.defaultAddress?.value,
expressOperationType = expressOperationType,
).fold(
@ -1813,7 +1854,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
fromAccount: Account.CryptoPortfolio?,
swapAmount: SwapAmount,
quotesLoadedState: SwapState.QuotesLoadedState,
spenderAddress: String?,
spenderAddress: String,
isAllowedToSpend: Boolean,
): SwapState {
val fromToken = fromTokenStatus.currency
@ -1837,7 +1878,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
val derivationPath = fromToken.network.derivationPath.value
// setting up amount for approve with given amount for swap [SwapApproveType.Limited]
val callData = SmartContractCallDataProviderFactory.getApprovalCallData(
spenderAddress = requireNotNull(spenderAddress) { "Spender address is null" },
spenderAddress = spenderAddress,
amount = swapAmount.value.convertToSdkAmount(fromTokenStatus),
blockchain = fromToken.network.toBlockchain(),
)
@ -2212,9 +2253,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
val token = tokens
.filterIsInstance<CryptoCurrency.Token>()
.find {
it.contractAddress.equals(feePaidCurrency.contractAddress, ignoreCase = true) &&
it.network.derivationPath == fromTokenStatus.currency.network.derivationPath
.find { cryptoToken ->
cryptoToken.contractAddress.equals(feePaidCurrency.contractAddress, ignoreCase = true) &&
cryptoToken.network.derivationPath == fromTokenStatus.currency.network.derivationPath
}
SwapFeeState.NotEnough(
@ -2268,18 +2309,18 @@ internal class SwapInteractorImpl @AssistedInject constructor(
}
private suspend fun Set<CryptoCurrency.RawID>.getQuotesOrEmpty(): Set<QuoteStatus> {
return runCatching {
val cachedQuotes = quotesRepository.getMultiQuoteSyncOrNull(currenciesIds = this)
return runSuspendCatching {
val cachedQuotes = quotesRepository.getMultiQuoteSyncOrNull(currenciesIds = this@getQuotesOrEmpty)
val allQuotesFound = cachedQuotes?.all { it.value !is QuoteStatus.Empty } == true
val areAllQuotesFound = cachedQuotes?.all { quote -> quote.value !is QuoteStatus.Empty } == true
if (allQuotesFound) return@runCatching cachedQuotes
if (areAllQuotesFound) return@runSuspendCatching cachedQuotes.orEmpty()
val currenciesIds = if (cachedQuotes.isNullOrEmpty()) {
this
this@getQuotesOrEmpty
} else {
cachedQuotes.mapNotNullTo(hashSetOf()) {
if (it.value is QuoteStatus.Empty) it.rawCurrencyId else null
cachedQuotes.mapNotNullTo(hashSetOf()) { quote ->
if (quote.value is QuoteStatus.Empty) quote.rawCurrencyId else null
}
}
@ -2287,10 +2328,11 @@ internal class SwapInteractorImpl @AssistedInject constructor(
params = MultiQuoteStatusFetcher.Params(currenciesIds = currenciesIds, appCurrencyId = null),
)
quotesRepository.getMultiQuoteSyncOrNull(currenciesIds = this)
quotesRepository.getMultiQuoteSyncOrNull(currenciesIds = this@getQuotesOrEmpty).orEmpty()
}.getOrElse { e ->
Timber.e(e, "Failed to get quotes: ${e.message.orEmpty()}")
emptySet()
}
.getOrNull()
.orEmpty()
}
private fun isSolana(networkId: String): Boolean {
@ -2302,7 +2344,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
return try {
SolanaTransactionHelper.removeSignaturesPlaceholders(hash)
} catch (e: Exception) {
Timber.e("Failed to format the hash: ${e.message}")
Timber.e("Failed to format the hash: ${e.message.orEmpty()}")
hash
}
}

View file

@ -1,73 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>BooleanPropertyNaming:ChooseFeeBottomSheet.kt$val showDivider = content.feeItems.lastIndex != index</ID>
<ID>BooleanPropertyNaming:SwapEvents.kt$SwapEvents.ChooseTokenScreenOpened$val availableTokens: Boolean</ID>
<ID>BooleanPropertyNaming:SwapEvents.kt$SwapEvents.ChooseTokenScreenResult$val tokenChosen: Boolean</ID>
<ID>BooleanPropertyNaming:SwapNotificationsFactory.kt$SwapNotificationsFactory$val needShowCoverWarning = quoteModel.preparedSwapConfigState.isBalanceEnough &amp;&amp; quoteModel.permissionState !is PermissionDataState.PermissionLoading &amp;&amp; feeEnoughState.feeCurrency != fromToken</ID>
<ID>BooleanPropertyNaming:SwapSelectTokenStateHolder.kt$SwapSelectTokenStateHolder$val afterSearch: Boolean</ID>
<ID>BooleanPropertyNaming:SwapSelectTokenStateHolder.kt$TokenToSelectState.TokenToSelect$val available: Boolean = true</ID>
<ID>BooleanPropertyNaming:SwapStateHolder.kt$SwapButton$val enabled: Boolean</ID>
<ID>BooleanPropertyNaming:SwapStateHolder.kt$TransactionCardType.ReadOnly$val showWarning: Boolean = false</ID>
<ID>BooleanPropertyNaming:SwapSuccessStateHolder.kt$SwapSuccessStateHolder$val showStatusButton: Boolean</ID>
<ID>CastNullableToNonNullableType:SwapModel.kt$SwapModel$as</ID>
<ID>MaxChainedCallsOnSameLine:SwapModel.kt$SwapModel$it.value.toTokenInfo.cryptoCurrencyStatus.currency.decimals</ID>
<ID>MaxChainedCallsOnSameLine:SwapNotificationsFactory.kt$SwapNotificationsFactory$quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.network.currencySymbol</ID>
<ID>MultilineLambdaItParameter:AccountTokenItemConverter.kt$AccountTokenItemConverter${ TokenItemState.TitleState.Content( text = stringReference(value = it.currency.name), isAvailable = false, ) }</ID>
<ID>MultilineLambdaItParameter:AccountTokenItemConverter.kt$AccountTokenItemConverter${ createSubtitleState( status = it, isAvailable = false, text = unavailableErrorText, ) }</ID>
<ID>MultilineLambdaItParameter:AccountTokenItemConverter.kt$AccountTokenItemConverter${ createSubtitleState( status = it, isAvailable = true, text = stringReference(value = it.currency.symbol), ) }</ID>
<ID>MultilineLambdaItParameter:ProviderItem.kt${ Text( text = if (it &gt; 0) "+$it%" else "$it%", style = TangemTheme.typography.body2, color = textColor, modifier = Modifier.padding(start = TangemTheme.dimens.spacing4), overflow = TextOverflow.Ellipsis, maxLines = 1, ) }</ID>
<ID>MultilineLambdaItParameter:ProviderItem.kt${ Text( text = it, style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.primary1, ) }</ID>
<ID>MultilineLambdaItParameter:ProviderItem.kt${ Text( text = it, style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, ) }</ID>
<ID>MultilineLambdaItParameter:ProviderItem.kt${ Text( text = it, style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, modifier = Modifier.padding(start = TangemTheme.dimens.spacing4), ) }</ID>
<ID>MultilineLambdaItParameter:ProviderItem.kt${ Text( text = it.resolveReference(), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, modifier = Modifier.padding(top = TangemTheme.dimens.spacing6), ) }</ID>
<ID>MultilineLambdaItParameter:ProviderItem.kt${ Text( text = it.resolveReference(), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, overflow = TextOverflow.Ellipsis, maxLines = 1, ) }</ID>
<ID>MultilineLambdaItParameter:StateBuilder.kt$StateBuilder${ LegalState( title = resourceReference(R.string.common_privacy_policy), link = it, onClick = actions.onLinkClick, ) }</ID>
<ID>MultilineLambdaItParameter:StateBuilder.kt$StateBuilder${ LegalState( title = resourceReference(R.string.common_terms_of_use), link = it, onClick = actions.onLinkClick, ) }</ID>
<ID>MultilineLambdaItParameter:StateBuilder.kt$StateBuilder${ it is SwapNotificationUM.Error || it is NotificationUM.Error || it is SwapNotificationUM.Warning.ExpressError || it is SwapNotificationUM.Warning.ExpressGeneralError || it is SwapNotificationUM.Warning.NoAvailableTokensToSwap || it is SwapNotificationUM.Warning.NeedReserveToCreateAccount || it is SwapNotificationUM.Info.PermissionNeeded }</ID>
<ID>MultilineLambdaItParameter:StateBuilder.kt$StateBuilder${ it.convertToProviderBottomSheetState( pricesLowerBest = pricesLowerBest, onProviderSelect = actions.onProviderSelect, needApplyFCARestrictions = needApplyFCARestrictions, ) }</ID>
<ID>MultilineLambdaItParameter:StateBuilder.kt$StateBuilder${ val selectedItem = when (it) { FeeType.NORMAL -&gt; txFeeState.normalFee FeeType.PRIORITY -&gt; txFeeState.priorityFee } actions.onSelectFeeType.invoke(selectedItem) }</ID>
<ID>MultilineLambdaItParameter:StateBuilder.kt$StateBuilder${ val tokenInfo = tokenSwapInfoForProviders[it.id] if (it is ProviderState.Content &amp;&amp; tokenInfo != null) { val rateString = tokenInfo.tokenAmount .getFormattedCryptoAmount(tokenInfo.cryptoCurrencyStatus.currency) it.copy( subtitle = stringReference(rateString), percentLowerThenBest = pricesLowerBest[it.id]?.let { percent -&gt; PercentDifference.Value(percent) } ?: PercentDifference.Value(0f), ) } else { it } }</ID>
<ID>MultilineLambdaItParameter:StateBuilder.kt$StateBuilder${ when (it) { is TokenToSelectState.TokenToSelect -&gt; { it.copy( addedTokenBalanceData = it.addedTokenBalanceData?.copy(isBalanceHidden = isBalanceHidden), ) } is TokenToSelectState.Title -&gt; { it } } }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ AccountCryptoCurrencyStatus( account = it.account, status = it.cryptoCurrencyStatus, ) }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ Timber.d("${coin.id} balance is ${it.value.amount}") dataState = dataState.copy( feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase( userWalletId = userWalletId, cryptoCurrencyStatus = it, ).getOrNull() ?: it, ) uiState = if (isFromCurrency) { dataState = dataState.copy(fromCryptoCurrency = it) stateBuilder.updateSendCurrencyBalance(uiState, it) } else { dataState = dataState.copy(toCryptoCurrency = it) stateBuilder.updateReceiveCurrencyBalance(uiState, it) } startLoadingQuotesFromLastState(isSilent = true) }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ Timber.e("Error when loading quotes: $it") uiState = stateBuilder.addNotification(uiState, null) { startLoadingQuotesFromLastState() } }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ Timber.e(it) applyInitialTokenChoice( state = TokensDataStateExpress.EMPTY, selectedCurrency = null, selectedAccount = null, isReverseFromTo = isReverseFromTo, ) uiState = stateBuilder.createInitialErrorState( uiState, (it as? ExpressException)?.expressDataError?.code ?: ExpressDataError.UnknownError.code, ) { uiState = stateBuilder.createInitialLoadingState( initialCurrencyFrom = initialCurrencyFrom, initialCurrencyTo = initialCurrencyTo, fromNetworkInfo = initialCurrencyFrom.getNetworkInfo(), ) initTokens(isReverseFromTo) } }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ Timber.e(it) startLoadingQuotesFromLastState() makeDefaultAlert() }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ Timber.e(it.message.orEmpty()) makeDefaultAlert() }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ getAccountCurrencyStatusUseCase.invokeSync( userWalletId = userWalletId, currency = it, ).getOrNull() }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync( userWalletId = userWalletId, cryptoCurrencyId = it.id, ).getOrNull() }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ if (!it.value.fromTokenInfo.amountFiat.isNullOrZero() &amp;&amp; !it.value.toTokenInfo.amountFiat.isNullOrZero()) { it.value.fromTokenInfo.amountFiat.divide( it.value.toTokenInfo.amountFiat, it.value.toTokenInfo.cryptoCurrencyStatus.currency.decimals, RoundingMode.HALF_UP, ) } else { BigDecimal.ZERO } }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ if (it.key != selectedProviderEntry.key) { val amount = it.value.toTokenInfo.tokenAmount.value val percentDiff = BigDecimal.ONE.minus( selectedProviderRate.divide(amount, RoundingMode.HALF_UP), ).multiply(hundredPercent) it.key.providerId to percentDiff.setScale(2, RoundingMode.HALF_UP).toFloat() } else { null } }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ isBalanceHidden = it.isBalanceHidden uiState = stateBuilder.updateBalanceHiddenState(uiState, isBalanceHidden) }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ it is SwapState.SwapError &amp;&amp; ( it.error is ExpressDataError.ExchangeTooSmallAmountError || it.error is ExpressDataError.ExchangeTooBigAmountError ) }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ it.currencyStatus.currency.name.contains(searchQuery, ignoreCase = true) || it.currencyStatus.currency.symbol.contains(searchQuery, ignoreCase = true) }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ subscribeToCoinBalanceUpdates( userWalletId = userWalletId, coin = it, isFromCurrency = false, ) }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ subscribeToCoinBalanceUpdates( userWalletId = userWalletId, coin = it, isFromCurrency = true, ) }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ uiState = stateBuilder.dismissBottomSheet(uiState) dataState = dataState.copy(selectedFee = it) modelScope.launch(dispatchers.io) { startLoadingQuotesFromLastState(false) } }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ val balance = swapInteractor.getTokenBalance(it) onAmountChanged(balance.formatToUIRepresentation()) }</ID>
<ID>MultilineLambdaItParameter:SwapModel.kt$SwapModel${ val provider = findAndSelectProvider(it) val swapState = dataState.lastLoadedSwapStates[provider] val fromToken = dataState.fromCryptoCurrency if (provider != null &amp;&amp; swapState != null &amp;&amp; fromToken != null) { analyticsEventHandler.send(SwapEvents.ProviderChosen(provider)) uiState = stateBuilder.dismissBottomSheet(uiState) setupLoadedState( provider = provider, state = swapState, fromToken = fromToken, ) } }</ID>
<ID>MultilineLambdaItParameter:TransactionCard.kt${ Text( text = it, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, ) }</ID>
<ID>MultilineLambdaItParameter:TransactionCard.kt${ Text( text = it, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, modifier = Modifier .align(Alignment.CenterVertically) .testTag(SwapTokenScreenTestTags.BALANCE), ) }</ID>
<ID>MultilineLambdaItParameter:TransactionCard.kt${ Text( text = it, color = TangemTheme.colors.text.tertiary, style = TangemTheme.typography.body2, modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20), ) }</ID>
<ID>NamedArguments:SwapModel.kt$SwapModel$PeriodicTask( UPDATE_DELAY, task = { uiState = stateBuilder.createSilentLoadState(uiState) runCatching(dispatchers.io) { dataState = dataState.copy( amount = amount, reduceBalanceBy = reduceBalanceBy, swapDataModel = null, approveDataModel = null, ) swapInteractor.findBestQuote( fromToken = fromToken, fromAccount = fromAccount, toToken = toToken, toAccount = toAccount, providers = toProvidersList, amountToSwap = amount, reduceBalanceBy = reduceBalanceBy, selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL, ) } }, onSuccess = { providersState -&gt; if (providersState.isNotEmpty()) { val (provider, state) = updateLoadedQuotes(providersState) setupLoadedState(provider, state, fromToken) val successStates = providersState.getLastLoadedSuccessStates() val pricesLowerBest = getPricesLowerBest(provider.providerId, successStates) uiState = stateBuilder.updateProvidersBottomSheetContent( uiState = uiState, pricesLowerBest = pricesLowerBest, tokenSwapInfoForProviders = successStates.entries .associate { it.key.providerId to it.value.toTokenInfo }, ) } else { Timber.e("Accidentally empty quotes list") } }, onError = { Timber.e("Error when loading quotes: $it") uiState = stateBuilder.addNotification(uiState, null) { startLoadingQuotesFromLastState() } }, )</ID>
<ID>NoNameShadowing:SwapModel.kt$SwapModel${ it.cryptoCurrencyStatus }</ID>
<ID>NoNameShadowing:SwapModel.kt$SwapModel${ it.cryptoCurrencyStatus.currency.id.value == id }</ID>
<ID>NoNameShadowing:SwapModel.kt$SwapModel${ it.key == selectedSwapProvider }</ID>
<ID>NullableToStringCall:SwapModel.kt$SwapModel$${currencyStatus.value.amount}</ID>
<ID>NullableToStringCall:SwapModel.kt$SwapModel$${it.value.amount}</ID>
<ID>NullableToStringCall:TransactionCard.kt$data.toString()</ID>
<ID>PropertyUsedBeforeDeclaration:SwapModel.kt$SwapModel$isAccountsMode</ID>
<ID>SuspendFunSwallowedCancellation:SwapModel.kt$SwapModel$runCatching</ID>
<ID>UnnecessaryEventHandlerParameter:ChooseFeeBottomSheet.kt$onReadMoreClick: (String) -&gt; Unit</ID>
<ID>UnnecessaryLet:SwapModel.kt$SwapModel$let { return nonEmptyStates.entries.first { it.key == selectedSwapProvider }.toPair() }</ID>
<ID>UnusedImports:TransactionCard.kt$import com.tangem.domain.models.account.Account</ID>
<ID>UseEmptyCounterpart:StoriesEvents.kt$StoriesEvents$mapOf()</ID>
<ID>UseEmptyCounterpart:SwapEvents.kt$SwapEvents$mapOf()</ID>
<ID>UseOrEmpty:StateBuilder.kt$StateBuilder$initialCurrencyTo?.symbol ?: ""</ID>
<ID>UseOrEmpty:SwapModel.kt$SwapModel$receiveToken ?: ""</ID>
<ID>UseOrEmpty:SwapScreenContent.kt$swapCardState.tokenIconUrl ?: ""</ID>
<ID>VarCouldBeVal:SwapModel.kt$SwapModel$private var swapRouter: SwapRouter = SwapRouter(router = router)</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -6,7 +6,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.WATCHED
sealed class StoriesEvents(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent("Stories", event, params) {
data class SwapStories(

View file

@ -15,7 +15,7 @@ private const val PROMO_CATEGORY = "Promo"
sealed class SwapEvents(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(SWAP_CATEGORY, event, params) {
data class SwapScreenOpened(val token: String) : SwapEvents(
@ -25,15 +25,15 @@ sealed class SwapEvents(
class SendTokenBalanceClicked : SwapEvents(event = "Send Token Balance Clicked")
data class ChooseTokenScreenOpened(val availableTokens: Boolean) : SwapEvents(
data class ChooseTokenScreenOpened(val hasAvailableTokens: Boolean) : SwapEvents(
event = "Choose Token Screen Opened",
params = mapOf("Available tokens" to if (availableTokens) "Yes" else "No"),
params = mapOf("Available tokens" to if (hasAvailableTokens) "Yes" else "No"),
)
data class ChooseTokenScreenResult(val tokenChosen: Boolean, val token: String? = null) : SwapEvents(
data class ChooseTokenScreenResult(val isTokenChosen: Boolean, val token: String? = null) : SwapEvents(
event = "Choose Token Screen Result",
params = buildMap {
put("Token Chosen", if (tokenChosen) "Yes" else "No")
put("Token Chosen", if (isTokenChosen) "Yes" else "No")
token?.let { put("Token", it) }
},
)

View file

@ -45,11 +45,11 @@ internal class AccountTokenItemConverter(
fun createAvailableItemConverter(): TokenItemStateConverter {
return TokenItemStateConverter(
appCurrency = appCurrency,
subtitleStateProvider = {
subtitleStateProvider = { status ->
createSubtitleState(
status = it,
status = status,
isAvailable = true,
text = stringReference(value = it.currency.symbol),
text = stringReference(value = status.currency.symbol),
)
},
subtitle2StateProvider = ::createSubtitle2State,
@ -64,15 +64,15 @@ internal class AccountTokenItemConverter(
return TokenItemStateConverter(
appCurrency = appCurrency,
iconStateProvider = { CryptoCurrencyToIconStateConverter(isAvailable = false).convert(it) },
titleStateProvider = {
titleStateProvider = { status ->
TokenItemState.TitleState.Content(
text = stringReference(value = it.currency.name),
text = stringReference(value = status.currency.name),
isAvailable = false,
)
},
subtitleStateProvider = {
subtitleStateProvider = { status ->
createSubtitleState(
status = it,
status = status,
isAvailable = false,
text = unavailableErrorText,
)

View file

@ -58,7 +58,7 @@ internal class TokensDataConverter(
onSearchEntered = onSearchEntered,
onTokenSelected = onTokenSelected,
isBalanceHidden = isBalanceHiddenProvider(),
afterSearch = group.isAfterSearch,
isAfterSearch = group.isAfterSearch,
)
}
@ -71,7 +71,7 @@ internal class TokensDataConverter(
id = cryptoCurrencyStatus.currency.id.value,
name = cryptoCurrencyStatus.currency.name,
symbol = cryptoCurrencyStatus.currency.symbol,
available = isAvailable,
isAvailable = isAvailable,
tokenIcon = convertIcon(cryptoCurrencyStatus.currency, isAvailable),
addedTokenBalanceData = TokenBalanceData(
amount = formatCryptoAmount(cryptoCurrencyStatus),

View file

@ -60,7 +60,7 @@ internal class TokensDataConverterV2(
onSearchEntered = onSearchEntered,
onTokenSelected = onTokenSelected,
isBalanceHidden = isBalanceHidden,
afterSearch = tokensDataState.isAfterSearch,
isAfterSearch = tokensDataState.isAfterSearch,
),
)
}

View file

@ -139,6 +139,7 @@ internal class SwapModel @Inject constructor(
private var initialToStatus: CryptoCurrencyStatus? = null
private var isBalanceHidden = true
private var isAccountsMode: Boolean = false
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
@ -151,7 +152,10 @@ internal class SwapModel @Inject constructor(
)
private val inputNumberFormatter =
InputNumberFormatter(NumberFormat.getInstance(Locale.getDefault()) as DecimalFormat)
InputNumberFormatter(
NumberFormat.getInstance(Locale.getDefault()) as? DecimalFormat
?: error("NumberFormat is not DecimalFormat"),
)
private val amountDebouncer = Debouncer()
private val singleTaskScheduler = SingleTaskScheduler<Map<SwapProvider, SwapState>>()
@ -170,18 +174,17 @@ internal class SwapModel @Inject constructor(
private var isOrderReversed = false
private val lastAmount = mutableStateOf(INITIAL_AMOUNT)
private val lastReducedBalanceBy = mutableStateOf(BigDecimal.ZERO)
private var swapRouter: SwapRouter = SwapRouter(router = router)
private val swapRouter: SwapRouter = SwapRouter(router = router)
private var userCountry: UserCountry? = null
private lateinit var fromAccountCurrencyStatus: AccountCryptoCurrencyStatus
private var toAccountCurrencyStatus: AccountCryptoCurrencyStatus? = null
private var isAccountsMode: Boolean = false
private val isUserResolvableError: (SwapState) -> Boolean = {
it is SwapState.SwapError &&
private val isUserResolvableError: (SwapState) -> Boolean = { swapState ->
swapState is SwapState.SwapError &&
(
it.error is ExpressDataError.ExchangeTooSmallAmountError ||
it.error is ExpressDataError.ExchangeTooBigAmountError
swapState.error is ExpressDataError.ExchangeTooSmallAmountError ||
swapState.error is ExpressDataError.ExchangeTooBigAmountError
)
}
@ -209,10 +212,10 @@ internal class SwapModel @Inject constructor(
userWalletId = userWalletId,
currency = initialCurrencyFrom,
).getOrNull()
val toAccountStatus = initialCurrencyTo?.let {
val toAccountStatus = initialCurrencyTo?.let { currencyTo ->
getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = userWalletId,
currency = it,
currency = currencyTo,
).getOrNull()
}
@ -227,10 +230,10 @@ internal class SwapModel @Inject constructor(
}
} else {
val fromStatus = getFromStatus()
val toStatus = initialCurrencyTo?.let {
val toStatus = initialCurrencyTo?.let { currencyTo ->
getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWalletId = userWalletId,
cryptoCurrencyId = it.id,
cryptoCurrencyId = currencyTo.id,
).getOrNull()
}
@ -247,8 +250,8 @@ internal class SwapModel @Inject constructor(
analyticsEventHandler.send(SwapEvents.SwapScreenOpened(initialCurrencyFrom.symbol))
getBalanceHidingSettingsUseCase()
.onEach {
isBalanceHidden = it.isBalanceHidden
.onEach { settings ->
isBalanceHidden = settings.isBalanceHidden
uiState = stateBuilder.updateBalanceHiddenState(uiState, isBalanceHidden)
}
.launchIn(modelScope)
@ -274,7 +277,7 @@ internal class SwapModel @Inject constructor(
val isAnyAvailableAccountTokensFrom = !dataState.tokensDataState?.fromGroup?.accountCurrencyList.isNullOrEmpty()
val isAnyAvailableTokens = isAnyAvailableTokensTo || isAnyAvailableTokensFrom ||
isAnyAvailableAccountTokensTo || isAnyAvailableAccountTokensFrom
analyticsEventHandler.send(SwapEvents.ChooseTokenScreenOpened(availableTokens = isAnyAvailableTokens))
analyticsEventHandler.send(SwapEvents.ChooseTokenScreenOpened(hasAvailableTokens = isAnyAvailableTokens))
}
private fun initTokens(isReverseFromTo: Boolean) {
@ -289,10 +292,10 @@ internal class SwapModel @Inject constructor(
initialCryptoCurrency = initialCurrencyFrom,
state = state,
isReverseFromTo = isReverseFromTo,
)?.let {
)?.let { accountSwapCurrency ->
AccountCryptoCurrencyStatus(
account = it.account,
status = it.cryptoCurrencyStatus,
account = accountSwapCurrency.account,
status = accountSwapCurrency.cryptoCurrencyStatus,
)
}
selectedAccountCurrency?.status to selectedAccountCurrency?.account
@ -312,23 +315,23 @@ internal class SwapModel @Inject constructor(
isReverseFromTo = isReverseFromTo,
)
(dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Coin)?.let {
(dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Coin)?.let { coin ->
subscribeToCoinBalanceUpdates(
userWalletId = userWalletId,
coin = it,
coin = coin,
isFromCurrency = true,
)
}
(dataState.toCryptoCurrency?.currency as? CryptoCurrency.Coin)?.let {
(dataState.toCryptoCurrency?.currency as? CryptoCurrency.Coin)?.let { coin ->
subscribeToCoinBalanceUpdates(
userWalletId = userWalletId,
coin = it,
coin = coin,
isFromCurrency = false,
)
}
}.onFailure {
Timber.e(it)
}.onFailure { error ->
Timber.e(error)
applyInitialTokenChoice(
state = TokensDataStateExpress.EMPTY,
@ -339,7 +342,7 @@ internal class SwapModel @Inject constructor(
uiState = stateBuilder.createInitialErrorState(
uiState,
(it as? ExpressException)?.expressDataError?.code ?: ExpressDataError.UnknownError.code,
(error as? ExpressException)?.expressDataError?.code ?: ExpressDataError.UnknownError.code,
) {
uiState = stateBuilder.createInitialLoadingState(
initialCurrencyFrom = initialCurrencyFrom,
@ -496,7 +499,7 @@ internal class SwapModel @Inject constructor(
toProvidersList: List<SwapProvider>,
): PeriodicTask<Map<SwapProvider, SwapState>> {
return PeriodicTask(
UPDATE_DELAY,
delay = UPDATE_DELAY,
task = {
uiState = stateBuilder.createSilentLoadState(uiState)
runCatching(dispatchers.io) {
@ -534,8 +537,8 @@ internal class SwapModel @Inject constructor(
Timber.e("Accidentally empty quotes list")
}
},
onError = {
Timber.e("Error when loading quotes: $it")
onError = { error ->
Timber.e("Error when loading quotes: $error")
uiState = stateBuilder.addNotification(uiState, null) { startLoadingQuotesFromLastState() }
},
)
@ -599,13 +602,13 @@ internal class SwapModel @Inject constructor(
}
private fun sendErrorAnalyticsEvent(error: ExpressDataError, provider: SwapProvider) {
val receiveToken = dataState.toCryptoCurrency?.currency?.let {
"${it.network.backendId}:${it.symbol}"
val receiveToken = dataState.toCryptoCurrency?.currency?.let { currency ->
"${currency.network.backendId}:${currency.symbol}"
}
analyticsErrorEventHandler.sendErrorEvent(
SwapEvents.NoticeProviderError(
sendToken = "${initialCurrencyFrom.network.backendId}:${initialCurrencyFrom.symbol}",
receiveToken = receiveToken ?: "",
receiveToken = receiveToken.orEmpty(),
provider = provider,
errorCode = error.code,
errorMessage = error.message,
@ -614,7 +617,7 @@ internal class SwapModel @Inject constructor(
}
private fun updateLoadedQuotes(state: Map<SwapProvider, SwapState>): Pair<SwapProvider, SwapState> {
val nonEmptyStates = state.filter { it.value !is SwapState.EmptyAmountState }
val nonEmptyStates = state.filter { entry -> entry.value !is SwapState.EmptyAmountState }
val selectedSwapProvider = if (nonEmptyStates.isNotEmpty()) {
selectProvider(state)
} else {
@ -624,8 +627,8 @@ internal class SwapModel @Inject constructor(
selectedProvider = selectedSwapProvider,
lastLoadedSwapStates = state,
)
selectedSwapProvider?.let {
return nonEmptyStates.entries.first { it.key == selectedSwapProvider }.toPair()
if (selectedSwapProvider != null) {
return nonEmptyStates.entries.first { entry -> entry.key == selectedSwapProvider }.toPair()
}
return state.entries.first().toPair()
}
@ -790,8 +793,8 @@ internal class SwapModel @Inject constructor(
processTangemPayWithdrawal(swapTransactionState = swapTransactionState)
}
}
}.onFailure {
Timber.e(it)
}.onFailure { error ->
Timber.e(error)
startLoadingQuotesFromLastState()
makeDefaultAlert()
}
@ -867,7 +870,7 @@ internal class SwapModel @Inject constructor(
@Suppress("LongMethod")
private fun givePermissionsToSwap() {
modelScope.launch(dispatchers.main) {
runCatching {
runSuspendCatching {
val fromCryptoCurrency = requireNotNull(dataState.fromCryptoCurrency) {
"dataState.fromCryptoCurrency might not be null"
}
@ -884,7 +887,7 @@ internal class SwapModel @Inject constructor(
TxFeeState.Empty -> {
makeDefaultAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
Timber.e("Fee should not be Empty")
return@runCatching
return@launch
}
is TxFeeState.MultipleFeeState -> fee.priorityFee
is TxFeeState.SingleFeeState -> fee.fee
@ -931,8 +934,8 @@ internal class SwapModel @Inject constructor(
}
}
}.onFailure { makeDefaultAlert() }
}.onFailure {
Timber.e(it.message.orEmpty())
}.onFailure { error ->
Timber.e(error.message.orEmpty())
makeDefaultAlert()
}
}
@ -947,13 +950,13 @@ internal class SwapModel @Inject constructor(
tokenDataState.toGroup
}
val available = group.available.filter {
it.currencyStatus.currency.name.contains(searchQuery, ignoreCase = true) ||
it.currencyStatus.currency.symbol.contains(searchQuery, ignoreCase = true)
val available = group.available.filter { swapAvailability ->
swapAvailability.currencyStatus.currency.name.contains(searchQuery, ignoreCase = true) ||
swapAvailability.currencyStatus.currency.symbol.contains(searchQuery, ignoreCase = true)
}
val unavailable = group.unavailable.filter {
it.currencyStatus.currency.name.contains(searchQuery, ignoreCase = true) ||
it.currencyStatus.currency.symbol.contains(searchQuery, ignoreCase = true)
val unavailable = group.unavailable.filter { swapAvailability ->
swapAvailability.currencyStatus.currency.name.contains(searchQuery, ignoreCase = true) ||
swapAvailability.currencyStatus.currency.symbol.contains(searchQuery, ignoreCase = true)
}
val accountCurrencyList = group.accountCurrencyList.mapNotNull { accountSwapAvailability ->
val filteredCurrencies = accountSwapAvailability.currencyList.filter { accountSwapCurrency ->
@ -998,8 +1001,8 @@ internal class SwapModel @Inject constructor(
val tokens = dataState.tokensDataState ?: return
val (foundToken, foundAccount) = getSelectedTokenAndAccount(tokens, id)
foundToken?.currency?.symbol?.let {
analyticsEventHandler.send(SwapEvents.ChooseTokenScreenResult(tokenChosen = true, token = it))
foundToken?.currency?.symbol?.let { symbol ->
analyticsEventHandler.send(SwapEvents.ChooseTokenScreenResult(isTokenChosen = true, token = symbol))
}
if (foundToken != null) {
@ -1079,8 +1082,10 @@ internal class SwapModel @Inject constructor(
tokens.fromGroup
} else {
tokens.toGroup
}.accountCurrencyList.firstNotNullOfOrNull {
it.currencyList.firstOrNull { it.cryptoCurrencyStatus.currency.id.value == id }
}.accountCurrencyList.firstNotNullOfOrNull { accountSwapAvailability ->
accountSwapAvailability.currencyList.firstOrNull { accountSwapCurrency ->
accountSwapCurrency.cryptoCurrencyStatus.currency.id.value == id
}
}
accountCryptoCurrencyStatus?.cryptoCurrencyStatus to accountCryptoCurrencyStatus?.account
} else {
@ -1088,7 +1093,9 @@ internal class SwapModel @Inject constructor(
tokens.fromGroup
} else {
tokens.toGroup
}.available.firstOrNull { it.currencyStatus.currency.id.value == id }?.currencyStatus to null
}.available.firstOrNull { swapAvailability ->
swapAvailability.currencyStatus.currency.id.value == id
}?.currencyStatus to null
}
}
@ -1105,7 +1112,7 @@ internal class SwapModel @Inject constructor(
currency = coin,
).distinctUntilChanged { old, new -> old.status.value.amount == new.status.value.amount } // Check only balance changes
.onEach { (account, currencyStatus) ->
Timber.d("${coin.id} balance is ${currencyStatus.value.amount}")
Timber.d("${coin.id} balance is ${currencyStatus.value.amount ?: "null"}")
dataState = dataState.copy(
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
@ -1135,24 +1142,24 @@ internal class SwapModel @Inject constructor(
userWalletId = userWalletId,
currencyId = coin.id,
isSingleWalletWithTokens = false,
).mapNotNull { (it as? Either.Right)?.value }
).mapNotNull { either -> (either as? Either.Right)?.value }
.distinctUntilChanged { old, new -> old.value.amount == new.value.amount } // Check only balance changes
.onEach {
Timber.d("${coin.id} balance is ${it.value.amount}")
.onEach { status ->
Timber.d("${coin.id} balance is ${status.value.amount ?: "null"}")
dataState = dataState.copy(
feePaidCryptoCurrency = getFeePaidCryptoCurrencyStatusSyncUseCase(
userWalletId = userWalletId,
cryptoCurrencyStatus = it,
).getOrNull() ?: it,
cryptoCurrencyStatus = status,
).getOrNull() ?: status,
)
uiState = if (isFromCurrency) {
dataState = dataState.copy(fromCryptoCurrency = it)
stateBuilder.updateSendCurrencyBalance(uiState, it)
dataState = dataState.copy(fromCryptoCurrency = status)
stateBuilder.updateSendCurrencyBalance(uiState, status)
} else {
dataState = dataState.copy(toCryptoCurrency = it)
stateBuilder.updateReceiveCurrencyBalance(uiState, it)
dataState = dataState.copy(toCryptoCurrency = status)
stateBuilder.updateReceiveCurrencyBalance(uiState, status)
}
startLoadingQuotesFromLastState(isSilent = true)
@ -1179,8 +1186,8 @@ internal class SwapModel @Inject constructor(
toAccount = newToAccount,
)
isOrderReversed = !isOrderReversed
dataState.tokensDataState?.let {
updateTokensState(it)
dataState.tokensDataState?.let { tokensDataState ->
updateTokensState(tokensDataState)
}
val minTxAmount = getMinimumTransactionAmountSyncUseCase(
@ -1259,8 +1266,8 @@ internal class SwapModel @Inject constructor(
}
private fun onMaxAmountClicked() {
dataState.fromCryptoCurrency?.let {
val balance = swapInteractor.getTokenBalance(it)
dataState.fromCryptoCurrency?.let { fromCurrency ->
val balance = swapInteractor.getTokenBalance(fromCurrency)
onAmountChanged(balance.formatToUIRepresentation())
}
}
@ -1324,7 +1331,7 @@ internal class SwapModel @Inject constructor(
uiState = stateBuilder.dismissBottomSheet(uiState)
} else {
if (swapRouter.currentScreen == SwapNavScreen.SelectToken) {
analyticsEventHandler.send(SwapEvents.ChooseTokenScreenResult(tokenChosen = false))
analyticsEventHandler.send(SwapEvents.ChooseTokenScreenResult(isTokenChosen = false))
}
swapRouter.back()
}
@ -1358,9 +1365,9 @@ internal class SwapModel @Inject constructor(
uiState = stateBuilder.dismissBottomSheet(uiState)
}
},
onSelectFeeType = {
onSelectFeeType = { feeType ->
uiState = stateBuilder.dismissBottomSheet(uiState)
dataState = dataState.copy(selectedFee = it)
dataState = dataState.copy(selectedFee = feeType)
modelScope.launch(dispatchers.io) {
startLoadingQuotesFromLastState(false)
}
@ -1377,8 +1384,8 @@ internal class SwapModel @Inject constructor(
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
) { uiState = stateBuilder.dismissBottomSheet(uiState) }
},
onProviderSelect = {
val provider = findAndSelectProvider(it)
onProviderSelect = { providerId ->
val provider = findAndSelectProvider(providerId)
val swapState = dataState.lastLoadedSwapStates[provider]
val fromToken = dataState.fromCryptoCurrency
if (provider != null && swapState != null && fromToken != null) {
@ -1478,11 +1485,14 @@ internal class SwapModel @Inject constructor(
private fun findBestQuoteProvider(state: SuccessLoadedSwapData): SwapProvider? {
// finding best quotes
return state.minByOrNull {
if (!it.value.fromTokenInfo.amountFiat.isNullOrZero() && !it.value.toTokenInfo.amountFiat.isNullOrZero()) {
it.value.fromTokenInfo.amountFiat.divide(
it.value.toTokenInfo.amountFiat,
it.value.toTokenInfo.cryptoCurrencyStatus.currency.decimals,
return state.minByOrNull { entry ->
val toTokenInfo = entry.value.toTokenInfo
val fromAmountFiat = entry.value.fromTokenInfo.amountFiat
val toAmountFiat = toTokenInfo.amountFiat
if (!fromAmountFiat.isNullOrZero() && !toAmountFiat.isNullOrZero()) {
fromAmountFiat.divide(
toAmountFiat,
toTokenInfo.cryptoCurrencyStatus.currency.decimals,
RoundingMode.HALF_UP,
)
} else {
@ -1492,17 +1502,19 @@ internal class SwapModel @Inject constructor(
}
private fun getPricesLowerBest(selectedProviderId: String, state: SuccessLoadedSwapData): Map<String, Float> {
val selectedProviderEntry = state.filter { it.key.providerId == selectedProviderId }.entries.firstOrNull()
?: return emptyMap()
val selectedProviderEntry = state
.filter { entry -> entry.key.providerId == selectedProviderId }
.entries
.firstOrNull() ?: return emptyMap()
val selectedProviderRate = selectedProviderEntry.value.toTokenInfo.tokenAmount.value
val hundredPercent = BigDecimal("100")
return state.entries.mapNotNull {
if (it.key != selectedProviderEntry.key) {
val amount = it.value.toTokenInfo.tokenAmount.value
return state.entries.mapNotNull { entry ->
if (entry.key != selectedProviderEntry.key) {
val amount = entry.value.toTokenInfo.tokenAmount.value
val percentDiff = BigDecimal.ONE.minus(
selectedProviderRate.divide(amount, RoundingMode.HALF_UP),
).multiply(hundredPercent)
it.key.providerId to percentDiff.setScale(2, RoundingMode.HALF_UP).toFloat()
entry.key.providerId to percentDiff.setScale(2, RoundingMode.HALF_UP).toFloat()
} else {
null
}
@ -1534,11 +1546,15 @@ internal class SwapModel @Inject constructor(
return if (accountsFeatureToggles.isFeatureEnabled) {
groupToFind.accountCurrencyList.firstNotNullOfOrNull { (_, currencyList) ->
currencyList.find { idToFind == it.cryptoCurrencyStatus.currency.id.value && it.isAvailable }
currencyList.find { accountSwapCurrency ->
idToFind == accountSwapCurrency.cryptoCurrencyStatus.currency.id.value &&
accountSwapCurrency.isAvailable
}
}?.providers
} else {
groupToFind.available.find { idToFind == it.currencyStatus.currency.id.value }
?.providers
groupToFind.available.find { swapAvailability ->
idToFind == swapAvailability.currencyStatus.currency.id.value
}?.providers
}
?.filterForTangemPayWithdrawal()
.orEmpty()
@ -1553,13 +1569,13 @@ internal class SwapModel @Inject constructor(
}
private fun Map<SwapProvider, SwapState>.getLastLoadedSuccessStates(): SuccessLoadedSwapData {
return this.filter { it.value is SwapState.QuotesLoadedState }
.mapValues { it.value as SwapState.QuotesLoadedState }
return this.filter { entry -> entry.value is SwapState.QuotesLoadedState }
.mapValues { entry -> entry.value as SwapState.QuotesLoadedState }
}
private fun Map<SwapProvider, SwapState>.consideredProvidersStates(): Map<SwapProvider, SwapState> {
return this.filter {
it.value is SwapState.QuotesLoadedState || isUserResolvableError(it.value)
return this.filter { entry ->
entry.value is SwapState.QuotesLoadedState || isUserResolvableError(entry.value)
}
}
@ -1577,10 +1593,14 @@ internal class SwapModel @Inject constructor(
val chosen = if (isOrderReversed) from else to
return if (accountsFeatureToggles.isFeatureEnabled) {
currenciesGroup.accountCurrencyList.flatMap { it.currencyList.map { it.cryptoCurrencyStatus } }
currenciesGroup.accountCurrencyList.flatMap { accountSwapAvailability ->
accountSwapAvailability.currencyList.map { accountSwapCurrency ->
accountSwapCurrency.cryptoCurrencyStatus
}
}
} else {
currenciesGroup.available.map { it.currencyStatus }
}.map { it.currency }.contains(chosen.currency)
currenciesGroup.available.map { swapAvailability -> swapAvailability.currencyStatus }
}.map { currencyStatus -> currencyStatus.currency }.contains(chosen.currency)
}
private fun sendPermissionApproveClickedEvent() {

View file

@ -125,9 +125,10 @@ internal class SwapNotificationsFactory(
if (quoteModel.permissionState is PermissionDataState.PermissionLoading) {
add(SwapNotificationUM.Error.ApprovalInProgressWarning)
} else if (quoteModel.preparedSwapConfigState.hasOutgoingTransaction) {
val fromCurrency = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency
add(
SwapNotificationUM.Error.TransactionInProgressWarning(
currencySymbol = quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.network.currencySymbol,
currencySymbol = fromCurrency.network.currencySymbol,
),
)
}
@ -290,10 +291,10 @@ internal class SwapNotificationsFactory(
) {
if (hideFee) return
val feeEnoughState = quoteModel.preparedSwapConfigState.feeState as? SwapFeeState.NotEnough ?: return
val needShowCoverWarning = quoteModel.preparedSwapConfigState.isBalanceEnough &&
val shouldShowCoverWarning = quoteModel.preparedSwapConfigState.isBalanceEnough &&
quoteModel.permissionState !is PermissionDataState.PermissionLoading &&
feeEnoughState.feeCurrency != fromToken
if (needShowCoverWarning) {
if (shouldShowCoverWarning) {
add(
SwapNotificationUM.Error.UnableToCoverFeeWarning(
fromToken = fromToken,

View file

@ -11,7 +11,7 @@ internal data class SwapSelectTokenStateHolder(
val unavailableTokens: ImmutableList<TokenToSelectState>,
val tokensListData: TokenListUMData,
val isBalanceHidden: Boolean,
val afterSearch: Boolean,
val isAfterSearch: Boolean,
val onSearchEntered: (String) -> Unit,
val onTokenSelected: (String) -> Unit,
)
@ -25,7 +25,7 @@ internal sealed class TokenToSelectState {
val name: String,
val symbol: String,
val tokenIcon: CurrencyIconState,
val available: Boolean = true,
val isAvailable: Boolean = true,
val addedTokenBalanceData: TokenBalanceData? = null,
) : TokenToSelectState()
}

View file

@ -77,7 +77,7 @@ sealed class SwapCardState {
data class SwapButton(
@DrawableRes val walletInteractionIcon: Int?,
val enabled: Boolean,
val isEnabled: Boolean,
val onClick: () -> Unit,
)
@ -94,7 +94,7 @@ sealed interface TransactionCardType {
) : TransactionCardType
data class ReadOnly(
val showWarning: Boolean = false,
val shouldShowWarning: Boolean = false,
val onWarningClick: (() -> Unit)? = null,
override val inputError: InputError = InputError.Empty,
override val accountTitleUM: AccountTitleUM? = null,

View file

@ -9,7 +9,7 @@ data class SwapSuccessStateHolder(
val txUrl: String,
val fee: TextReference,
val rate: TextReference,
val showStatusButton: Boolean,
val shouldShowStatusButton: Boolean,
val providerName: TextReference,
val providerType: TextReference,
val providerIcon: String,

View file

@ -17,7 +17,7 @@ internal data object SwapSuccessStatePreview {
fee = TextReference.Str("1 000 DAI ~ 1 000 MATIC"),
providerName = TextReference.Str("1inch"),
providerType = TextReference.Str(ExchangeProviderType.DEX.providerName),
showStatusButton = false,
shouldShowStatusButton = false,
providerIcon = "",
fromTitle = AccountTitleUM.Account(
prefixText = stringReference("From"),

View file

@ -58,14 +58,13 @@ private fun ChooseFeeBottomSheetContent(content: ChooseFeeBottomSheetConfig) {
}
FooterBlock(
readMore = content.readMore,
readMoreUrl = content.readMoreUrl,
onReadMoreClick = content.onReadMoreClick,
onReadMoreClick = { content.onReadMoreClick(content.readMoreUrl) },
)
}
}
@Composable
private fun FooterBlock(readMore: TextReference, readMoreUrl: String, onReadMoreClick: (String) -> Unit) {
private fun FooterBlock(readMore: TextReference, onReadMoreClick: () -> Unit) {
val linkText = readMore.resolveReference()
val fullString = stringResourceSafe(R.string.common_fee_selector_footer, linkText)
val linkTextPosition = fullString.length - linkText.length
@ -81,7 +80,7 @@ private fun FooterBlock(readMore: TextReference, readMoreUrl: String, onReadMore
val click = { i: Int ->
val readMoreStyle = requireNotNull(annotatedString.spanStyles.getOrNull(1))
if (i in readMoreStyle.start..readMoreStyle.end) {
onReadMoreClick(readMoreUrl)
onReadMoreClick()
}
}
@ -102,7 +101,7 @@ private fun FooterBlock(readMore: TextReference, readMoreUrl: String, onReadMore
private fun FeeItemsBlock(content: ChooseFeeBottomSheetConfig) {
content.feeItems.forEachIndexed { index, feeItem ->
val isSelected = feeItem.feeType == content.selectedFee
val showDivider = content.feeItems.lastIndex != index
val shouldShowDivider = content.feeItems.lastIndex != index
val symbol = "${feeItem.symbolCrypto}"
val preDotText = "${feeItem.amountCrypto}$symbol"
val postDot = feeItem.amountFiatFormatted
@ -117,7 +116,7 @@ private fun FeeItemsBlock(content: ChooseFeeBottomSheetConfig) {
ellipsizeOffset = ellipsizeOffset,
isSelected = isSelected,
onSelect = { content.onSelectFeeType(feeItem.feeType) },
showDivider = showDivider,
showDivider = shouldShowDivider,
)
}
FeeType.PRIORITY -> {
@ -129,7 +128,7 @@ private fun FeeItemsBlock(content: ChooseFeeBottomSheetConfig) {
ellipsizeOffset = ellipsizeOffset,
isSelected = isSelected,
onSelect = { content.onSelectFeeType(feeItem.feeType) },
showDivider = showDivider,
showDivider = shouldShowDivider,
)
}
}

View file

@ -132,16 +132,16 @@ private fun ProviderContentState(
modifier = Modifier.padding(end = TangemTheme.dimens.spacing4),
)
}
AnimatedContent(targetState = state.name, label = "") {
AnimatedContent(targetState = state.name, label = "") { name ->
Text(
text = it,
text = name,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
)
}
AnimatedContent(targetState = state.type, label = "") {
AnimatedContent(targetState = state.type, label = "") { type ->
Text(
text = it,
text = type,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.padding(start = TangemTheme.dimens.spacing4),
@ -162,9 +162,9 @@ private fun ProviderContentState(
end = TangemTheme.dimens.spacing56,
),
) {
AnimatedContent(targetState = state.subtitle, label = "") {
AnimatedContent(targetState = state.subtitle, label = "") { subtitle ->
Text(
text = it.resolveReference(),
text = subtitle.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
overflow = TextOverflow.Ellipsis,
@ -179,9 +179,9 @@ private fun ProviderContentState(
} else {
TangemTheme.colors.text.warning
}
AnimatedContent(targetState = state.percentLowerThenBest.value, label = "") {
AnimatedContent(targetState = state.percentLowerThenBest.value, label = "") { percentValue ->
Text(
text = if (it > 0) "+$it%" else "$it%",
text = if (percentValue > 0) "+$percentValue%" else "$percentValue%",
style = TangemTheme.typography.body2,
color = textColor,
modifier = Modifier.padding(start = TangemTheme.dimens.spacing4),
@ -231,25 +231,25 @@ private fun ProviderUnavailableState(
modifier = Modifier.padding(start = TangemTheme.dimens.spacing12),
) {
Row {
AnimatedContent(targetState = state.name, label = "") {
AnimatedContent(targetState = state.name, label = "") { name ->
Text(
text = it,
text = name,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
AnimatedContent(targetState = state.type, label = "") {
AnimatedContent(targetState = state.type, label = "") { type ->
Text(
text = it,
text = type,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.padding(start = TangemTheme.dimens.spacing4),
)
}
}
AnimatedContent(targetState = state.alertText, label = "") {
AnimatedContent(targetState = state.alertText, label = "") { alertText ->
Text(
text = it.resolveReference(),
text = alertText.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing6),

View file

@ -109,7 +109,7 @@ internal class StateBuilder(
type = TransactionCardType.ReadOnly(),
amountEquivalent = null,
tokenIconUrl = initialCurrencyTo?.iconUrl,
tokenCurrency = initialCurrencyTo?.symbol ?: "",
tokenCurrency = initialCurrencyTo?.symbol.orEmpty(),
token = null,
amountTextFieldValue = null,
canSelectAnotherToken = false,
@ -122,7 +122,7 @@ internal class StateBuilder(
fee = FeeItemState.Empty,
swapButton = SwapButton(
walletInteractionIcon = walletInterationIcon(userWalletProvider()),
enabled = false,
isEnabled = false,
onClick = {},
),
onRefresh = {},
@ -181,7 +181,7 @@ internal class StateBuilder(
fee = FeeItemState.Empty,
swapButton = SwapButton(
walletInteractionIcon = walletInterationIcon(userWalletProvider()),
enabled = false,
isEnabled = false,
onClick = { },
),
changeCardsButtonState = ChangeCardsButtonState.DISABLED,
@ -246,7 +246,7 @@ internal class StateBuilder(
fee = FeeItemState.Empty,
swapButton = SwapButton(
walletInteractionIcon = walletInterationIcon(userWalletProvider()),
enabled = false,
isEnabled = false,
onClick = {},
),
providerState = ProviderState.Loading(),
@ -335,7 +335,7 @@ internal class StateBuilder(
),
receiveCardData = SwapCardState.SwapCardData(
type = TransactionCardType.ReadOnly(
showWarning = true,
shouldShowWarning = true,
onWarningClick = actions.onReceiveCardWarningClick,
accountTitleUM = getToCardAccountTitle(toAccount),
),
@ -368,7 +368,7 @@ internal class StateBuilder(
fee = feeState,
swapButton = SwapButton(
walletInteractionIcon = walletInterationIcon(userWalletProvider()),
enabled = getSwapButtonEnabled(notifications),
isEnabled = getSwapButtonEnabled(notifications),
onClick = actions.onSwapClick,
),
changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible),
@ -397,17 +397,17 @@ internal class StateBuilder(
private fun createTosState(swapProvider: SwapProvider): TosState {
return TosState(
tosLink = swapProvider.termsOfUse?.let {
tosLink = swapProvider.termsOfUse?.let { termsUrl ->
LegalState(
title = resourceReference(R.string.common_terms_of_use),
link = it,
link = termsUrl,
onClick = actions.onLinkClick,
)
},
policyLink = swapProvider.privacyPolicy?.let {
policyLink = swapProvider.privacyPolicy?.let { policyUrl ->
LegalState(
title = resourceReference(R.string.common_privacy_policy),
link = it,
link = policyUrl,
onClick = actions.onLinkClick,
)
},
@ -420,12 +420,13 @@ internal class StateBuilder(
}
private fun getSwapButtonEnabled(notifications: ImmutableList<NotificationUM>): Boolean {
return notifications.none {
it is SwapNotificationUM.Error || it is NotificationUM.Error ||
it is SwapNotificationUM.Warning.ExpressError || it is SwapNotificationUM.Warning.ExpressGeneralError ||
it is SwapNotificationUM.Warning.NoAvailableTokensToSwap ||
it is SwapNotificationUM.Warning.NeedReserveToCreateAccount ||
it is SwapNotificationUM.Info.PermissionNeeded
return notifications.none { notification ->
notification is SwapNotificationUM.Error || notification is NotificationUM.Error ||
notification is SwapNotificationUM.Warning.ExpressError ||
notification is SwapNotificationUM.Warning.ExpressGeneralError ||
notification is SwapNotificationUM.Warning.NoAvailableTokensToSwap ||
notification is SwapNotificationUM.Warning.NeedReserveToCreateAccount ||
notification is SwapNotificationUM.Info.PermissionNeeded
}
}
@ -495,7 +496,7 @@ internal class StateBuilder(
fee = FeeItemState.Empty,
swapButton = SwapButton(
walletInteractionIcon = walletInterationIcon(userWalletProvider()),
enabled = false,
isEnabled = false,
onClick = actions.onSwapClick,
),
changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible),
@ -592,7 +593,7 @@ internal class StateBuilder(
fee = FeeItemState.Empty,
swapButton = SwapButton(
walletInteractionIcon = walletInterationIcon(userWalletProvider()),
enabled = false,
isEnabled = false,
onClick = { },
),
changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible),
@ -604,7 +605,7 @@ internal class StateBuilder(
fun createSwapInProgressState(uiState: SwapStateHolder): SwapStateHolder {
return uiState.copy(
swapButton = uiState.swapButton.copy(
enabled = false,
isEnabled = false,
),
)
}
@ -722,15 +723,17 @@ internal class StateBuilder(
)
val selectTokenState = uiState.selectTokenState?.copy(
isBalanceHidden = isBalanceHidden,
availableTokens = uiState.selectTokenState.availableTokens.map {
when (it) {
availableTokens = uiState.selectTokenState.availableTokens.map { tokenState ->
when (tokenState) {
is TokenToSelectState.TokenToSelect -> {
it.copy(
addedTokenBalanceData = it.addedTokenBalanceData?.copy(isBalanceHidden = isBalanceHidden),
tokenState.copy(
addedTokenBalanceData = tokenState.addedTokenBalanceData?.copy(
isBalanceHidden = isBalanceHidden,
),
)
}
is TokenToSelectState.Title -> {
it
tokenState
}
}
}.toImmutableList(),
@ -804,7 +807,7 @@ internal class StateBuilder(
fun loadingPermissionState(uiState: SwapStateHolder): SwapStateHolder {
return uiState.copy(
swapButton = uiState.swapButton.copy(
enabled = false,
isEnabled = false,
),
permissionState = GiveTxPermissionState.InProgress,
notifications = notificationsFactory.getApprovalInProgressStateNotification(uiState.notifications),
@ -837,7 +840,7 @@ internal class StateBuilder(
txUrl = txUrl,
providerName = stringReference(providerState.name),
providerType = stringReference(providerState.type),
showStatusButton = shouldShowStatus,
shouldShowStatusButton = shouldShowStatus,
providerIcon = providerState.iconUrl,
rate = providerState.subtitle,
fee = stringReference("${fee.feeCryptoFormattedWithNative} (${fee.feeFiatFormattedWithNative})"),
@ -877,7 +880,7 @@ internal class StateBuilder(
txUrl = txUrl,
providerName = stringReference(providerState.name),
providerType = stringReference(providerState.type),
showStatusButton = false,
shouldShowStatusButton = false,
providerIcon = providerState.iconUrl,
rate = providerState.subtitle,
fee = TextReference.EMPTY,
@ -1101,8 +1104,8 @@ internal class StateBuilder(
onDismiss: () -> Unit,
): SwapStateHolder {
val availableProvidersStates = providersStates.entries
.mapNotNull {
it.convertToProviderBottomSheetState(
.mapNotNull { entry ->
entry.convertToProviderBottomSheetState(
pricesLowerBest = pricesLowerBest,
onProviderSelect = actions.onProviderSelect,
needApplyFCARestrictions = needApplyFCARestrictions,
@ -1139,19 +1142,19 @@ internal class StateBuilder(
uiState.copy(
bottomSheetConfig = uiState.bottomSheetConfig.copy(
content = config.copy(
providers = providers.map {
val tokenInfo = tokenSwapInfoForProviders[it.id]
if (it is ProviderState.Content && tokenInfo != null) {
providers = providers.map { providerState ->
val tokenInfo = tokenSwapInfoForProviders[providerState.id]
if (providerState is ProviderState.Content && tokenInfo != null) {
val rateString = tokenInfo.tokenAmount
.getFormattedCryptoAmount(tokenInfo.cryptoCurrencyStatus.currency)
it.copy(
providerState.copy(
subtitle = stringReference(rateString),
percentLowerThenBest = pricesLowerBest[it.id]?.let { percent ->
percentLowerThenBest = pricesLowerBest[providerState.id]?.let { percent ->
PercentDifference.Value(percent)
} ?: PercentDifference.Value(0f),
)
} else {
it
providerState
}
}.toImmutableList(),
),
@ -1170,8 +1173,8 @@ internal class StateBuilder(
): SwapStateHolder {
val config = ChooseFeeBottomSheetConfig(
selectedFee = selectedFee,
onSelectFeeType = {
val selectedItem = when (it) {
onSelectFeeType = { feeType ->
val selectedItem = when (feeType) {
FeeType.NORMAL -> txFeeState.normalFee
FeeType.PRIORITY -> txFeeState.priorityFee
}

View file

@ -175,7 +175,7 @@ private fun TransactionCardData(
balance = swapCardState.balance.orMaskWithStars(swapCardState.isBalanceHidden),
textFieldValue = swapCardState.amountTextFieldValue,
amountEquivalent = swapCardState.amountEquivalent,
tokenIconUrl = swapCardState.tokenIconUrl ?: "",
tokenIconUrl = swapCardState.tokenIconUrl.orEmpty(),
tokenCurrency = swapCardState.tokenCurrency,
priceImpact = priceImpact,
networkIconRes = if (swapCardState.isNotNativeToken) swapCardState.networkIconRes else null,
@ -374,7 +374,7 @@ private fun MainButton(state: SwapStateHolder) {
modifier = Modifier.fillMaxWidth(),
text = stringResourceSafe(id = R.string.swapping_swap_action),
iconResId = state.swapButton.walletInteractionIcon,
enabled = state.swapButton.enabled,
enabled = state.swapButton.isEnabled,
onClick = state.swapButton.onClick,
)
}
@ -437,7 +437,7 @@ private val state = SwapStateHolder(
),
SwapNotificationUM.Warning.NoAvailableTokensToSwap("POLYGON"),
),
swapButton = SwapButton(enabled = true, onClick = {}, walletInteractionIcon = null),
swapButton = SwapButton(isEnabled = true, onClick = {}, walletInteractionIcon = null),
onRefresh = {},
onBackClicked = {},
onChangeCardsClicked = {},

View file

@ -57,11 +57,11 @@ internal fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: ()
val modifier = Modifier.padding(padding)
when {
state.availableTokens.isEmpty() && state.unavailableTokens.isEmpty() &&
state.tokensListData.tokensList.isEmpty() && state.afterSearch -> {
state.tokensListData.tokensList.isEmpty() && state.isAfterSearch -> {
TokensNotFound(modifier)
}
state.availableTokens.isEmpty() && state.unavailableTokens.isEmpty() &&
state.tokensListData.tokensList.isEmpty() && !state.afterSearch -> {
state.tokensListData.tokensList.isEmpty() && !state.isAfterSearch -> {
EmptyTokensList(modifier)
}
else -> {
@ -313,7 +313,7 @@ private fun TokenItem(
.fillMaxWidth()
.height(TangemTheme.dimens.size72)
.clickable(
enabled = token.available,
enabled = token.isAvailable,
onClick = onTokenClick,
)
.padding(
@ -336,7 +336,7 @@ private fun TokenItem(
EllipsisText(
text = token.name,
style = TangemTheme.typography.subtitle1,
color = if (token.available) {
color = if (token.isAvailable) {
TangemTheme.colors.text.primary1
} else {
TangemTheme.colors.text.tertiary
@ -366,7 +366,7 @@ private fun TokenItem(
maxLines = 1,
softWrap = false,
overflow = TextOverflow.Visible,
color = if (token.available) {
color = if (token.isAvailable) {
TangemTheme.colors.text.primary1
} else {
TangemTheme.colors.text.tertiary
@ -417,7 +417,7 @@ private fun TokenScreenPreview() {
availableTokens = listOf(title, token, token, token).toImmutableList(),
unavailableTokens = listOf(title, token, token, token).toImmutableList(),
tokensListData = TokenListUMData.EmptyList,
afterSearch = false,
isAfterSearch = false,
isBalanceHidden = false,
onSearchEntered = {},
onTokenSelected = {},

View file

@ -48,7 +48,7 @@ fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) {
SwapSuccessScreenButtons(
textRes = R.string.common_close,
txUrl = state.txUrl,
showStatusButton = state.showStatusButton,
shouldShowStatusButton = state.shouldShowStatusButton,
onExploreClick = state.onExploreButtonClick,
onStatusClick = state.onStatusButtonClick,
onDoneClick = onBack,
@ -160,7 +160,7 @@ private fun SwapAmountBlock(
private fun SwapSuccessScreenButtons(
@StringRes textRes: Int,
txUrl: String,
showStatusButton: Boolean,
shouldShowStatusButton: Boolean,
onExploreClick: () -> Unit,
onStatusClick: () -> Unit,
onDoneClick: () -> Unit,
@ -178,7 +178,7 @@ private fun SwapSuccessScreenButtons(
onClick = onExploreClick,
modifier = Modifier.weight(1f),
)
if (showStatusButton) {
if (shouldShowStatusButton) {
SpacerW12()
SecondaryButtonIconStart(
text = stringResourceSafe(id = R.string.express_cex_status_button_title),

View file

@ -46,7 +46,6 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.SwapTokenScreenTestTags
import com.tangem.core.ui.utils.ImageBackgroundContrastChecker
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.CryptoPortfolioIcon
import com.tangem.feature.swap.domain.models.ui.PriceImpact
import com.tangem.feature.swap.models.TransactionCardType
@ -217,9 +216,9 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie
}
SpacerW16()
if (balance.isNotBlank()) {
AnimatedContent(targetState = balance, label = "") {
AnimatedContent(targetState = balance, label = "") { balanceText ->
Text(
text = it,
text = balanceText,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
modifier = Modifier
@ -318,15 +317,15 @@ private fun Content(
style = TangemTheme.typography.body2,
)
} else {
AnimatedContent(targetState = amountEquivalent, label = "") {
AnimatedContent(targetState = amountEquivalent, label = "") { amount ->
Text(
text = it,
text = amount,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
)
}
}
if (type.showWarning) {
if (type.shouldShowWarning) {
SpacerW4()
IconButton(
onClick = {
@ -348,9 +347,9 @@ private fun Content(
}
}
} else {
AnimatedContent(targetState = amountEquivalent, label = "") {
AnimatedContent(targetState = amountEquivalent, label = "") { amount ->
Text(
text = it,
text = amount,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size20),
@ -409,6 +408,7 @@ fun Token(
}
}
@Suppress("NullableToStringCall")
@Composable
private fun TokenIcon(
tokenIconUrl: String,
@ -595,7 +595,7 @@ private fun TransactionCardPreview() {
private fun TransactionCardPreviewWithPriceImpact() {
TransactionCard(
type = TransactionCardType.ReadOnly(
showWarning = true,
shouldShowWarning = true,
accountTitleUM = AccountTitleUM.Account(
prefixText = resourceReference(R.string.common_from),
name = AccountNameUM.DefaultMain.value,