Updated on 2026-08-14
This commit is contained in:
parent
626d0914a0
commit
88ab462aa7
26 changed files with 365 additions and 428 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 && fromCurrency }</ID>
|
||||
<ID>MultilineLambdaItParameter:DefaultSwapTransactionRepository.kt$DefaultSwapTransactionRepository${ val isUserWallet = it.userWalletId == userWallet.walletId.stringValue val toCurrency = it.toCryptoCurrencyId == cryptoCurrencyId.value isUserWallet && 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>
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) && it.network.derivationPath == fromTokenStatus.currency.network.derivationPath }</ID>
|
||||
<ID>MultilineLambdaItParameter:SwapInteractorImpl.kt$SwapInteractorImpl${ tokenInfoForFilter(it).contractAddress == currency.getContractAddress() && 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 && currencyFilter && notCustomTokenFilter }</ID>
|
||||
<ID>MultilineLambdaItParameter:SwapInteractorImpl.kt$SwapInteractorImpl${ val listTokenInfo = tokenInfoForAvailable(it) if (cryptoCurrencyStatuses.currency.network.backendId == listTokenInfo.network && cryptoCurrencyStatuses.currency.getContractAddress() == listTokenInfo.contractAddress && 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>
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 && quoteModel.permissionState !is PermissionDataState.PermissionLoading && 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 > 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 -> txFeeState.normalFee FeeType.PRIORITY -> txFeeState.priorityFee } actions.onSelectFeeType.invoke(selectedItem) }</ID>
|
||||
<ID>MultilineLambdaItParameter:StateBuilder.kt$StateBuilder${ val tokenInfo = tokenSwapInfoForProviders[it.id] if (it is ProviderState.Content && tokenInfo != null) { val rateString = tokenInfo.tokenAmount .getFormattedCryptoAmount(tokenInfo.cryptoCurrencyStatus.currency) it.copy( subtitle = stringReference(rateString), percentLowerThenBest = pricesLowerBest[it.id]?.let { percent -> PercentDifference.Value(percent) } ?: PercentDifference.Value(0f), ) } else { it } }</ID>
|
||||
<ID>MultilineLambdaItParameter:StateBuilder.kt$StateBuilder${ when (it) { is TokenToSelectState.TokenToSelect -> { it.copy( addedTokenBalanceData = it.addedTokenBalanceData?.copy(isBalanceHidden = isBalanceHidden), ) } is TokenToSelectState.Title -> { 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() && !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 && ( 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 && swapState != null && 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 -> 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) -> 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>
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ internal class TokensDataConverterV2(
|
|||
onSearchEntered = onSearchEntered,
|
||||
onTokenSelected = onTokenSelected,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
afterSearch = tokensDataState.isAfterSearch,
|
||||
isAfterSearch = tokensDataState.isAfterSearch,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {},
|
||||
|
|
|
|||
|
|
@ -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 = {},
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue