Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-17 14:05:58 +03:00
commit 3cbc2dadfb
822 changed files with 12838 additions and 5366 deletions

View file

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

View file

@ -15,7 +15,7 @@ private const val PROMO_CATEGORY = "Promo"
sealed class SwapEvents(
event: String,
params: Map<String, String> = mapOf(),
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(SWAP_CATEGORY, event, params) {
data class SwapScreenOpened(val token: String) : SwapEvents(
@ -23,17 +23,17 @@ sealed class SwapEvents(
params = mapOf("Token" to token),
)
data object SendTokenBalanceClicked : SwapEvents(event = "Send Token Balance Clicked")
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) }
},
)
@ -71,10 +71,11 @@ sealed class SwapEvents(
),
)
data object ButtonPermissionCancelClicked : SwapEvents(event = "Button - Permission Cancel")
class ButtonPermissionCancelClicked : SwapEvents(event = "Button - Permission Cancel")
data object ButtonSwipeClicked : SwapEvents(event = "Button - Swipe")
class ButtonSwipeClicked : SwapEvents(event = "Button - Swipe")
@Suppress("NullableToStringCall")
data class SwapInProgressScreen(
val provider: SwapProvider,
val commission: FeeType, // Market / Fast
@ -82,6 +83,8 @@ sealed class SwapEvents(
val receiveBlockchain: String,
val sendToken: String,
val receiveToken: String,
val fromDerivationIndex: Int?,
val toDerivationIndex: Int?,
) : SwapEvents(
event = "Swap in Progress Screen Opened",
params = mapOf(
@ -91,10 +94,11 @@ sealed class SwapEvents(
"Receive Token" to receiveToken,
"Send Blockchain" to sendBlockchain,
"Receive Blockchain" to receiveBlockchain,
"Account Derivation From or To (optional)" to "$fromDerivationIndex, $toDerivationIndex",
),
)
data object ProviderClicked : SwapEvents("Provider Clicked")
class ProviderClicked : SwapEvents("Provider Clicked")
data class ProviderChosen(val provider: SwapProvider) : SwapEvents(
event = "Provider Chosen",
@ -111,7 +115,7 @@ sealed class SwapEvents(
params = mapOf("Token" to token),
)
data object NoticeNoAvailableTokensToSwap : SwapEvents("Notice - No Available Tokens To Swap")
class NoticeNoAvailableTokensToSwap : SwapEvents("Notice - No Available Tokens To Swap")
data class NoticeNotEnoughFee(val token: String, val blockchain: String) : SwapEvents(
event = "Notice - Not Enough Fee",

View file

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

View file

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

View file

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

View file

@ -139,6 +139,7 @@ internal class SwapModel @Inject constructor(
private var initialToStatus: CryptoCurrencyStatus? = null
private var isBalanceHidden = true
private var isAccountsMode: Boolean = false
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
@ -151,7 +152,10 @@ internal class SwapModel @Inject constructor(
)
private val inputNumberFormatter =
InputNumberFormatter(NumberFormat.getInstance(Locale.getDefault()) as DecimalFormat)
InputNumberFormatter(
NumberFormat.getInstance(Locale.getDefault()) as? DecimalFormat
?: error("NumberFormat is not DecimalFormat"),
)
private val amountDebouncer = Debouncer()
private val singleTaskScheduler = SingleTaskScheduler<Map<SwapProvider, SwapState>>()
@ -170,18 +174,17 @@ internal class SwapModel @Inject constructor(
private var isOrderReversed = false
private val lastAmount = mutableStateOf(INITIAL_AMOUNT)
private val lastReducedBalanceBy = mutableStateOf(BigDecimal.ZERO)
private var swapRouter: SwapRouter = SwapRouter(router = router)
private val swapRouter: SwapRouter = SwapRouter(router = router)
private var userCountry: UserCountry? = null
private lateinit var fromAccountCurrencyStatus: AccountCryptoCurrencyStatus
private var toAccountCurrencyStatus: AccountCryptoCurrencyStatus? = null
private var isAccountsMode: Boolean = false
private val isUserResolvableError: (SwapState) -> Boolean = {
it is SwapState.SwapError &&
private val isUserResolvableError: (SwapState) -> Boolean = { swapState ->
swapState is SwapState.SwapError &&
(
it.error is ExpressDataError.ExchangeTooSmallAmountError ||
it.error is ExpressDataError.ExchangeTooBigAmountError
swapState.error is ExpressDataError.ExchangeTooSmallAmountError ||
swapState.error is ExpressDataError.ExchangeTooBigAmountError
)
}
@ -210,10 +213,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()
}
@ -228,10 +231,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()
}
@ -248,8 +251,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)
@ -275,7 +278,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) {
@ -290,10 +293,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
@ -313,23 +316,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,
@ -340,7 +343,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,
@ -376,7 +379,7 @@ internal class SwapModel @Inject constructor(
) {
// exceptional case
if (selectedCurrency == null) {
analyticsEventHandler.send(SwapEvents.NoticeNoAvailableTokensToSwap)
analyticsEventHandler.send(SwapEvents.NoticeNoAvailableTokensToSwap())
uiState = stateBuilder.createNoAvailableTokensToSwapState(
uiStateHolder = uiState,
fromToken = initialFromStatus,
@ -389,7 +392,7 @@ internal class SwapModel @Inject constructor(
} else {
initialFromStatus to selectedCurrency
}
val (fromAccount, toAccount) = if (accountsFeatureToggles.isFeatureEnabled) {
val (fromAccount, toAccount) = if (accountsFeatureToggles.isFeatureEnabled && tangemPayInput == null) {
if (isOrderReversed) {
selectedAccount to fromAccountCurrencyStatus.account
} else {
@ -497,7 +500,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) {
@ -535,8 +538,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() }
},
)
@ -641,13 +644,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,
@ -656,7 +659,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 {
@ -666,8 +669,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()
}
@ -832,8 +835,8 @@ internal class SwapModel @Inject constructor(
processTangemPayWithdrawal(swapTransactionState = swapTransactionState)
}
}
}.onFailure {
Timber.e(it)
}.onFailure { error ->
Timber.e(error)
startLoadingQuotesFromLastState()
makeDefaultAlert()
}
@ -889,6 +892,8 @@ internal class SwapModel @Inject constructor(
val fee = dataState.selectedFee?.feeType ?: return
val fromCurrency = dataState.fromCryptoCurrency?.currency ?: return
val toCurrency = dataState.toCryptoCurrency?.currency ?: return
val fromDerivationIndex = dataState.fromAccount?.derivationIndex?.value
val toDerivationIndex = dataState.toAccount?.derivationIndex?.value
analyticsEventHandler.send(
SwapEvents.SwapInProgressScreen(
@ -898,6 +903,8 @@ internal class SwapModel @Inject constructor(
receiveBlockchain = toCurrency.network.name,
sendToken = fromCurrency.symbol,
receiveToken = toCurrency.symbol,
fromDerivationIndex = fromDerivationIndex,
toDerivationIndex = toDerivationIndex,
),
)
}
@ -905,7 +912,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"
}
@ -922,7 +929,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
@ -969,8 +976,8 @@ internal class SwapModel @Inject constructor(
}
}
}.onFailure { makeDefaultAlert() }
}.onFailure {
Timber.e(it.message.orEmpty())
}.onFailure { error ->
Timber.e(error.message.orEmpty())
makeDefaultAlert()
}
}
@ -985,13 +992,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 ->
@ -1036,8 +1043,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) {
@ -1117,8 +1124,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 {
@ -1126,7 +1135,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
}
}
@ -1143,7 +1154,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(
@ -1173,24 +1184,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)
@ -1217,8 +1228,8 @@ internal class SwapModel @Inject constructor(
toAccount = newToAccount,
)
isOrderReversed = !isOrderReversed
dataState.tokensDataState?.let {
updateTokensState(it)
dataState.tokensDataState?.let { tokensDataState ->
updateTokensState(tokensDataState)
}
val minTxAmount = getMinimumTransactionAmountSyncUseCase(
@ -1297,8 +1308,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())
}
}
@ -1313,7 +1324,7 @@ internal class SwapModel @Inject constructor(
private fun onAmountSelected(selected: Boolean) {
if (selected) {
analyticsEventHandler.send(SwapEvents.SendTokenBalanceClicked)
analyticsEventHandler.send(SwapEvents.SendTokenBalanceClicked())
}
}
@ -1354,7 +1365,7 @@ internal class SwapModel @Inject constructor(
},
onChangeCardsClicked = {
onChangeCardsClicked()
analyticsEventHandler.send(SwapEvents.ButtonSwipeClicked)
analyticsEventHandler.send(SwapEvents.ButtonSwipeClicked())
},
onBackClicked = {
val bottomSheet = uiState.bottomSheetConfig
@ -1362,7 +1373,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()
}
@ -1376,7 +1387,7 @@ internal class SwapModel @Inject constructor(
sendGivePermissionClickedEvent()
uiState = stateBuilder.showPermissionBottomSheet(uiState) {
startLoadingQuotesFromLastState(isSilent = true)
analyticsEventHandler.send(SwapEvents.ButtonPermissionCancelClicked)
analyticsEventHandler.send(SwapEvents.ButtonPermissionCancelClicked())
uiState = stateBuilder.dismissBottomSheet(uiState)
}
},
@ -1396,15 +1407,15 @@ 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)
}
},
onProviderClick = { providerId ->
analyticsEventHandler.send(SwapEvents.ProviderClicked)
analyticsEventHandler.send(SwapEvents.ProviderClicked())
val states = dataState.lastLoadedSwapStates.getLastLoadedSuccessStates()
val pricesLowerBest = getPricesLowerBest(providerId, states)
uiState = stateBuilder.showSelectProviderBottomSheet(
@ -1415,8 +1426,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) {
@ -1516,11 +1527,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 {
@ -1530,17 +1544,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
}
@ -1572,11 +1588,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()
@ -1591,13 +1611,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)
}
}
@ -1615,10 +1635,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 sendNoticePermissionNeededEvent() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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