diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 345f06a0f6..cbd1bf2dab 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -681,7 +681,7 @@
No available tokens to swap
To make a transaction you need to deposit some %1$s %2$s
Unable to cover %s fee
- Service temporary unavailable
+ Service temporarily unavailable
Please change the amount to swap
The amount to swap must be at least %s
This card might be a production sample or counterfeit
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt
index 4baf118b61..a920fda100 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt
@@ -1,13 +1,10 @@
package com.tangem.core.ui.components
import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.exclude
import androidx.compose.foundation.layout.ime
-import androidx.compose.foundation.layout.navigationBars
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -25,23 +22,9 @@ sealed interface Keyboard {
/**
* Allows to subscribe to a soft keyboard to detect when it's open/closed
*/
-@Deprecated("Use Modifier.imePadding() on pure Compose screens (without XML layouts)")
@Composable
fun keyboardAsState(): State {
- val density = LocalDensity.current
- val imeInsets = WindowInsets.ime
- .exclude(WindowInsets.navigationBars)
- .getBottom(density)
-
- return remember(imeInsets) {
- derivedStateOf {
- if (imeInsets > 0) {
- Keyboard.Opened(
- height = with(density) { imeInsets.toDp() },
- )
- } else {
- Keyboard.Closed
- }
- }
- }
+ val bottom = WindowInsets.ime.getBottom(LocalDensity.current)
+ val isImeVisible = bottom > 0
+ return rememberUpdatedState(if (isImeVisible) Keyboard.Opened(bottom.dp) else Keyboard.Closed)
}
\ No newline at end of file
diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt
index 87dee2d3d9..61962b7ca5 100644
--- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt
+++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt
@@ -118,12 +118,12 @@ class GetCryptoCurrencyActionsUseCase(
val isMulticurrencyWallet = cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()
// swap
- if (isMulticurrencyWallet &&
- marketCryptoCurrencyRepository.isExchangeable(userWalletId, cryptoCurrency)
- ) {
- activeList.add(TokenActionsState.ActionState.Swap(true))
- } else {
- disabledList.add(TokenActionsState.ActionState.Swap(false))
+ if (isMulticurrencyWallet) {
+ if (marketCryptoCurrencyRepository.isExchangeable(userWalletId, cryptoCurrency)) {
+ activeList.add(TokenActionsState.ActionState.Swap(true))
+ } else {
+ disabledList.add(TokenActionsState.ActionState.Swap(false))
+ }
}
// buy
@@ -164,11 +164,13 @@ class GetCryptoCurrencyActionsUseCase(
return activeList + disabledList
}
- private fun isSendDisabled(cryptoCurrencyStatus: CryptoCurrencyStatus, coinStatus: CryptoCurrencyStatus?): Boolean =
- cryptoCurrencyStatus.value.amount.isNullOrZero() ||
- coinStatus?.value?.amount.isNullOrZero() ||
- currenciesRepository.hasPendingTransactions(
- cryptoCurrencyStatus = cryptoCurrencyStatus,
- coinStatus = coinStatus,
- )
+ private fun isSendDisabled(
+ cryptoCurrencyStatus: CryptoCurrencyStatus,
+ coinStatus: CryptoCurrencyStatus?,
+ ): Boolean = cryptoCurrencyStatus.value.amount.isNullOrZero() ||
+ coinStatus?.value?.amount.isNullOrZero() ||
+ currenciesRepository.hasPendingTransactions(
+ cryptoCurrencyStatus = cryptoCurrencyStatus,
+ coinStatus = coinStatus,
+ )
}
\ No newline at end of file
diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt
index 9e41c08329..26ac346f90 100644
--- a/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt
+++ b/features/swap/data/src/main/java/com/tangem/feature/swap/SwapRepositoryImpl.kt
@@ -9,6 +9,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.extensions.Result
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
+import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.express.TangemExpressApi
@@ -28,6 +29,7 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.converters.*
import com.tangem.feature.swap.domain.SwapRepository
import com.tangem.feature.swap.domain.models.DataError
+import com.tangem.feature.swap.domain.models.ExpressException
import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel
import com.tangem.feature.swap.domain.models.domain.*
@@ -62,49 +64,60 @@ internal class SwapRepositoryImpl @Inject constructor(
currencyList: List,
): PairsWithProviders {
return withContext(coroutineDispatcher.io) {
- val initial = NetworkLeastTokenInfo(
- contractAddress = initialCurrency.contractAddress,
- network = initialCurrency.network,
- )
- val currenciesList = currencyList.map { leastTokenInfoConverter.convert(it) }
-
- val pairs = async {
- getPairsInternal(
- from = arrayListOf(initial),
- to = currenciesList,
+ try {
+ val initial = NetworkLeastTokenInfo(
+ contractAddress = initialCurrency.contractAddress,
+ network = initialCurrency.network,
)
- }
+ val currenciesList = currencyList.map { leastTokenInfoConverter.convert(it) }
- val reversedPairs = async {
- getPairsInternal(
- from = currenciesList,
- to = arrayListOf(initial),
+ val pairsDeferred = async {
+ getPairsInternal(
+ from = arrayListOf(initial),
+ to = currenciesList,
+ )
+ }
+
+ val reversedPairsDeferred = async {
+ getPairsInternal(
+ from = currenciesList,
+ to = arrayListOf(initial),
+ )
+ }
+
+ val pairs = pairsDeferred.await().getOrThrow()
+ val reversedPairs = reversedPairsDeferred.await().getOrThrow()
+
+ val allPairs = pairs + reversedPairs
+
+ val providers = tangemExpressApi.getProviders().getOrThrow()
+
+ return@withContext swapPairInfoConverter.convert(
+ SwapPairsWithProviders(
+ swapPair = allPairs,
+ providers = providers,
+ ),
)
+ } catch (exception: Exception) {
+ if (exception is ApiResponseError.HttpException) {
+ throw ExpressException(errorsDataConverter.convert(exception.errorBody ?: ""))
+ } else {
+ throw exception
+ }
}
-
- val allPairs = pairs.await() + reversedPairs.await()
-
- val providers = tangemExpressApi.getProviders().getOrThrow()
-
- return@withContext swapPairInfoConverter.convert(
- SwapPairsWithProviders(
- swapPair = allPairs,
- providers = providers,
- ),
- )
}
}
private suspend fun getPairsInternal(
from: List,
to: List,
- ): List {
+ ): ApiResponse> {
return tangemExpressApi.getPairs(
PairsRequestBody(
from = from,
to = to,
),
- ).getOrThrow()
+ )
}
override suspend fun getExchangeStatus(txId: String): Either {
diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
index f1af16641e..6504c6d53a 100644
--- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
+++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
@@ -461,10 +461,12 @@ internal class SwapInteractorImpl @Inject constructor(
amount,
currencyToSend.symbol,
),
+ fromAmountValue = amount.value,
toAmount = amountFormatter.formatSwapAmountToUI(
swapData.toTokenAmount,
currencyToGet.symbol,
),
+ toAmountValue = swapData.toTokenAmount.value,
txAddress = userWalletManager.getLastTransactionHash(networkId, derivationPath).orEmpty(),
timestamp = System.currentTimeMillis(),
)
@@ -541,10 +543,12 @@ internal class SwapInteractorImpl @Inject constructor(
amount,
currencyToSend.currency.symbol,
),
+ fromAmountValue = amount.value,
toAmount = amountFormatter.formatSwapAmountToUI(
exchangeData.dataModel.toTokenAmount,
currencyToGet.currency.symbol,
),
+ toAmountValue = exchangeData.dataModel.toTokenAmount.value,
txAddress = userWalletManager.getLastTransactionHash(
currencyToSend.currency.network.backendId,
derivationPath,
diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ExpressException.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ExpressException.kt
new file mode 100644
index 0000000000..cc416b82ca
--- /dev/null
+++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ExpressException.kt
@@ -0,0 +1,3 @@
+package com.tangem.feature.swap.domain.models
+
+class ExpressException(val dataError: DataError) : Exception()
\ No newline at end of file
diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt
index a593888610..526b5b2a73 100644
--- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt
+++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/TxState.kt
@@ -1,10 +1,14 @@
package com.tangem.feature.swap.domain.models.ui
+import java.math.BigDecimal
+
sealed class TxState {
data class TxSent(
val fromAmount: String? = null,
+ val fromAmountValue: BigDecimal? = null,
val toAmount: String? = null,
+ val toAmountValue: BigDecimal? = null,
val txAddress: String,
val txExternalUrl: String? = null,
val timestamp: Long,
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt
index 84254a5716..265631000e 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt
@@ -25,7 +25,7 @@ sealed class ProviderState {
val subtitle: TextReference,
val selectionType: SelectionType,
val additionalBadge: AdditionalBadge,
- val percentLowerThenBest: Float = 0f,
+ val percentLowerThenBest: PercentLowerThanBest = PercentLowerThanBest.Empty,
override val onProviderClick: (String) -> Unit,
) : ProviderState()
@@ -50,18 +50,35 @@ sealed class ProviderState {
}
}
+sealed class PercentLowerThanBest {
+ data class Value(val value: Float) : PercentLowerThanBest()
+ object Empty : PercentLowerThanBest()
+}
+
object ProviderPercentDiffComparator : Comparator {
override fun compare(o1: ProviderState, o2: ProviderState): Int {
if (o1 is ProviderState.Content && o2 !is ProviderState.Content) {
- return 1
- }
- if (o1 !is ProviderState.Content && o2 is ProviderState.Content) {
return -1
}
- return if (o1 is ProviderState.Content && o2 is ProviderState.Content) {
- o1.percentLowerThenBest.compareTo(o2.percentLowerThenBest)
+ if (o1 !is ProviderState.Content && o2 is ProviderState.Content) {
+ return 1
+ }
+ if (o1 is ProviderState.Content && o2 is ProviderState.Content) {
+ val o1Percent = o1.percentLowerThenBest
+ val o2Percent = o2.percentLowerThenBest
+ if (o1Percent is PercentLowerThanBest.Value && o2Percent !is PercentLowerThanBest.Value) {
+ return -1
+ }
+ if (o1Percent !is PercentLowerThanBest.Value && o2Percent is PercentLowerThanBest.Value) {
+ return 1
+ }
+ return if (o1Percent is PercentLowerThanBest.Value && o2Percent is PercentLowerThanBest.Value) {
+ o1Percent.value.compareTo(o2Percent.value)
+ } else {
+ 0
+ }
} else {
- 0
+ return 0
}
}
}
\ No newline at end of file
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt
index 2dd33c473f..363085e36c 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseProviderBottomSheet.kt
@@ -17,6 +17,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig
+import com.tangem.feature.swap.models.states.PercentLowerThanBest
import com.tangem.feature.swap.models.states.ProviderState
import com.tangem.feature.swap.presentation.R
import kotlinx.collections.immutable.toImmutableList
@@ -89,7 +90,7 @@ private fun ChooseProviderBottomSheet_Preview() {
iconUrl = "",
subtitle = stringReference("1 000 000"),
additionalBadge = ProviderState.AdditionalBadge.BestTrade,
- percentLowerThenBest = -1.0f,
+ percentLowerThenBest = PercentLowerThanBest.Value(-1.0f),
selectionType = ProviderState.SelectionType.SELECT,
onProviderClick = {},
),
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt
index e5fe4ef2c9..51f6936885 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt
@@ -24,6 +24,7 @@ import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.feature.swap.models.states.PercentLowerThanBest
import com.tangem.feature.swap.models.states.ProviderState
/**
@@ -151,8 +152,10 @@ private fun ProviderContentState(
maxLines = 1,
)
}
- if (state.percentLowerThenBest > 0f) {
- AnimatedContent(targetState = state.percentLowerThenBest, label = "") {
+ if (state.percentLowerThenBest is PercentLowerThanBest.Value &&
+ state.percentLowerThenBest.value > 0
+ ) {
+ AnimatedContent(targetState = state.percentLowerThenBest.value, label = "") {
Text(
text = "-$it%",
style = TangemTheme.typography.body2,
@@ -404,7 +407,7 @@ private fun ProviderItem_Content_Preview() {
iconUrl = "",
subtitle = stringReference("1 000 000"),
additionalBadge = ProviderState.AdditionalBadge.PermissionRequired,
- percentLowerThenBest = -1.0f,
+ percentLowerThenBest = PercentLowerThanBest.Value(-1.0f),
selectionType = ProviderState.SelectionType.SELECT,
onProviderClick = {},
)
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
index bb1842d035..6881df60df 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
@@ -579,14 +579,14 @@ internal class StateBuilder(
}
}
- fun createInitialErrorState(uiState: SwapStateHolder, onRefreshClick: () -> Unit): SwapStateHolder {
+ fun createInitialErrorState(uiState: SwapStateHolder, code: Int, onRefreshClick: () -> Unit): SwapStateHolder {
return uiState.copy(
warnings = listOf(
SwapWarning.GeneralWarning(
notificationConfig = NotificationConfig(
title = TextReference.Res(R.string.warning_express_refresh_required_title),
- subtitle = TextReference.EMPTY,
- iconResId = R.drawable.ic_alert_circle_24,
+ subtitle = TextReference.Res(R.string.generic_error_code, wrappedList(code)),
+ iconResId = R.drawable.ic_alert_triangle_20,
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
text = TextReference.Res(R.string.warning_button_refresh),
onClick = onRefreshClick,
@@ -639,7 +639,7 @@ internal class StateBuilder(
@Suppress("LongParameterList")
fun createSuccessState(
uiState: SwapStateHolder,
- timeStamp: Long,
+ txState: TxState.TxSent,
txUrl: String,
dataState: SwapProcessDataState,
onExploreClick: () -> Unit,
@@ -648,18 +648,16 @@ internal class StateBuilder(
val fee = requireNotNull(dataState.selectedFee)
val fromCryptoCurrency = requireNotNull(dataState.fromCryptoCurrency)
val toCryptoCurrency = requireNotNull(dataState.toCryptoCurrency)
- val fromAmount = toBigDecimalOrNull(requireNotNull(dataState.amount))
- val toAmount = requireNotNull(dataState.swapDataModel?.toTokenAmount?.value)
+ val fromAmount = txState.fromAmountValue ?: BigDecimal.ZERO
+ val toAmount = txState.toAmountValue ?: BigDecimal.ZERO
val providerState = uiState.providerState as ProviderState.Content
- val fromCryptoAmount = BigDecimalFormatter.formatCryptoAmount(fromAmount, fromCryptoCurrency.currency)
- val toCryptoAmount = BigDecimalFormatter.formatCryptoAmount(toAmount, toCryptoCurrency.currency)
val fromFiatAmount = getFormattedFiatAmount(fromCryptoCurrency.value.fiatRate?.multiply(fromAmount))
val toFiatAmount = getFormattedFiatAmount(toCryptoCurrency.value.fiatRate?.multiply(toAmount))
return uiState.copy(
successState = SwapSuccessStateHolder(
- timestamp = timeStamp,
+ timestamp = txState.timestamp,
txUrl = txUrl,
providerName = stringReference(providerState.name),
providerType = stringReference(providerState.type),
@@ -667,8 +665,8 @@ internal class StateBuilder(
providerIcon = providerState.iconUrl,
rate = providerState.subtitle,
fee = stringReference("${fee.feeCryptoFormatted} (${fee.feeFiatFormatted})"),
- fromTokenAmount = stringReference(fromCryptoAmount),
- toTokenAmount = stringReference(toCryptoAmount),
+ fromTokenAmount = stringReference(txState.fromAmount.orEmpty()),
+ toTokenAmount = stringReference(txState.toAmount.orEmpty()),
fromTokenFiatAmount = stringReference(fromFiatAmount),
toTokenFiatAmount = stringReference(toFiatAmount),
fromTokenIconState = iconStateConverter.convert(fromCryptoCurrency),
@@ -789,7 +787,7 @@ internal class StateBuilder(
fun showSelectProviderBottomSheet(
uiState: SwapStateHolder,
selectedProviderId: String,
- pricesLowerBest: Map,
+ pricesLowerBest: Map,
providersStates: Map,
unavailableProviders: List,
onDismiss: () -> Unit,
@@ -820,6 +818,7 @@ internal class StateBuilder(
fun updateProvidersBottomSheetContent(
uiState: SwapStateHolder,
+ pricesLowerBest: Map,
tokenSwapInfoForProviders: Map,
): SwapStateHolder {
val config = uiState.bottomSheetConfig?.content as? ChooseProviderBottomSheetConfig
@@ -835,6 +834,9 @@ internal class StateBuilder(
.getFormattedCryptoAmount(tokenInfo.cryptoCurrencyStatus.currency)
it.copy(
subtitle = stringReference(rateString),
+ percentLowerThenBest = pricesLowerBest[it.id]?.let { percent ->
+ PercentLowerThanBest.Value(percent)
+ } ?: PercentLowerThanBest.Empty,
)
} else {
it
@@ -928,7 +930,7 @@ internal class StateBuilder(
}
private fun Map.Entry.convertToProviderBottomSheetState(
- pricesLowerBest: Map,
+ pricesLowerBest: Map,
onProviderSelect: (String) -> Unit,
): ProviderState? {
val provider = this.key
@@ -1040,7 +1042,7 @@ internal class StateBuilder(
subtitle = stringReference(rateString),
additionalBadge = badge,
selectionType = selectionType,
- percentLowerThenBest = ZERO_PERCENT,
+ percentLowerThenBest = PercentLowerThanBest.Empty,
onProviderClick = onProviderClick,
)
}
@@ -1049,7 +1051,7 @@ internal class StateBuilder(
isBestRate: Boolean,
state: SwapState.QuotesLoadedState,
selectionType: ProviderState.SelectionType,
- pricesLowerBest: Map,
+ pricesLowerBest: Map,
onProviderClick: (String) -> Unit,
): ProviderState {
val toTokenInfo = state.toTokenInfo
@@ -1069,7 +1071,9 @@ internal class StateBuilder(
subtitle = stringReference(rateString),
additionalBadge = additionalBadge,
selectionType = selectionType,
- percentLowerThenBest = pricesLowerBest[this] ?: ZERO_PERCENT,
+ percentLowerThenBest = pricesLowerBest[this.providerId]?.let { percent ->
+ PercentLowerThanBest.Value(percent)
+ } ?: PercentLowerThanBest.Value(0f),
onProviderClick = onProviderClick,
)
}
@@ -1103,7 +1107,7 @@ internal class StateBuilder(
selectionType = selectionType,
subtitle = alertText,
additionalBadge = ProviderState.AdditionalBadge.Empty,
- percentLowerThenBest = ZERO_PERCENT,
+ percentLowerThenBest = PercentLowerThanBest.Empty,
onProviderClick = onProviderClick,
)
}
@@ -1148,6 +1152,5 @@ internal class StateBuilder(
private const val HUNDRED_PERCENTS = 100
private const val UNKNOWN_AMOUNT_SIGN = "—"
private const val MAX_DECIMALS_TO_SHOW = 8
- private const val ZERO_PERCENT = 0f
}
}
\ No newline at end of file
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt
index a70b6ce6f2..c25ea64c0b 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreen.kt
@@ -2,21 +2,33 @@ package com.tangem.feature.swap.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.models.SwapStateHolder
import com.tangem.feature.swap.models.states.ChooseFeeBottomSheetConfig
import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig
import com.tangem.feature.swap.models.states.GivePermissionBottomSheetConfig
+import com.tangem.feature.swap.presentation.R
@Composable
internal fun SwapScreen(stateHolder: SwapStateHolder) {
BackHandler(onBack = stateHolder.onBackClicked)
Scaffold(
+ modifier = Modifier.systemBarsPadding(),
+ topBar = {
+ AppBarWithBackButton(
+ text = stringResource(R.string.common_swap),
+ onBackClick = stateHolder.onBackClicked,
+ iconRes = R.drawable.ic_close_24,
+ )
+ },
containerColor = TangemTheme.colors.background.secondary,
) { scaffoldPaddings ->
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
index 949ead0f64..ace1d19a31 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
@@ -17,7 +17,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.constraintlayout.compose.ConstraintLayout
import com.tangem.common.Strings.STARS
import com.tangem.core.ui.components.*
-import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.getActiveIconResByCoinId
@@ -40,12 +39,6 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi
.background(color = TangemTheme.colors.background.secondary),
) {
Column {
- AppBarWithBackButton(
- text = stringResource(R.string.common_swap),
- onBackClick = state.onBackClicked,
- iconRes = R.drawable.ic_close_24,
- )
-
Column(
modifier = Modifier
.fillMaxWidth()
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt
index 82b05a7c86..4057aba9af 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt
@@ -97,7 +97,8 @@ private fun ListOfTokens(state: SwapSelectTokenStateHolder, modifier: Modifier =
LazyColumn(
modifier = modifier
.background(color = screenBackgroundColor)
- .fillMaxSize(),
+ .fillMaxSize()
+ .imePadding(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
item { SpacerH8() }
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt
index 101200169e..ac172210c3 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt
@@ -5,7 +5,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.*
import arrow.core.getOrElse
-import arrow.core.mapNotNull
import com.tangem.common.Provider
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.utils.InputNumberFormatter
@@ -19,6 +18,7 @@ import com.tangem.feature.swap.analytics.SwapEvents
import com.tangem.feature.swap.domain.BlockchainInteractor
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.feature.swap.domain.models.DataError
+import com.tangem.feature.swap.domain.models.ExpressException
import com.tangem.feature.swap.domain.models.domain.PermissionOptions
import com.tangem.feature.swap.domain.models.domain.SwapDataModel
import com.tangem.feature.swap.domain.models.domain.SwapProvider
@@ -175,13 +175,19 @@ internal class SwapViewModel @Inject constructor(
selectedCurrency = null,
)
- uiState = stateBuilder.createInitialErrorState(uiState) {
- uiState = stateBuilder.createInitialLoadingState(
- initialCurrency = initialCryptoCurrency,
- networkInfo = blockchainInteractor.getBlockchainInfo(initialCryptoCurrency.network.backendId),
- )
- initTokens()
- }
+ uiState =
+ stateBuilder.createInitialErrorState(
+ uiState,
+ (it as? ExpressException)?.dataError?.code ?: DataError.UnknownError.code,
+ ) {
+ uiState = stateBuilder.createInitialLoadingState(
+ initialCurrency = initialCryptoCurrency,
+ networkInfo = blockchainInteractor.getBlockchainInfo(
+ initialCryptoCurrency.network.backendId,
+ ),
+ )
+ initTokens()
+ }
}
}
}
@@ -289,11 +295,13 @@ internal class SwapViewModel @Inject constructor(
if (providersState.isNotEmpty()) {
val (provider, state) = updateLoadedQuotes(providersState)
setupLoadedState(provider, state, fromToken)
+ val successStates = providersState
+ .getLastLoadedSuccessStates()
+ val pricesLowerBest = getPricesLowerBest(successStates)
uiState = stateBuilder.updateProvidersBottomSheetContent(
uiState = uiState,
- tokenSwapInfoForProviders = providersState
- .getLastLoadedSuccessStates()
- .entries
+ pricesLowerBest = pricesLowerBest,
+ tokenSwapInfoForProviders = successStates.entries
.associate { it.key.providerId to it.value.toTokenInfo },
)
} else {
@@ -384,7 +392,7 @@ internal class SwapViewModel @Inject constructor(
val stateSuccess = state.getLastLoadedSuccessStates()
return if (stateSuccess.isNotEmpty()) {
val currentSelected = dataState.selectedProvider
- if (currentSelected != null && state.keys.contains(currentSelected)) {
+ if (currentSelected != null && stateSuccess.keys.contains(currentSelected)) {
currentSelected
} else {
findBestQuoteProvider(stateSuccess) ?: stateSuccess.keys.first()
@@ -457,7 +465,7 @@ internal class SwapViewModel @Inject constructor(
)
uiState = stateBuilder.createSuccessState(
uiState = uiState,
- timeStamp = it.timestamp,
+ txState = it,
dataState = dataState,
txUrl = url,
onExploreClick = {
@@ -869,21 +877,21 @@ internal class SwapViewModel @Inject constructor(
}?.key
}
- private fun getPricesLowerBest(state: SuccessLoadedSwapData): Map {
+ private fun getPricesLowerBest(state: SuccessLoadedSwapData): Map {
val bestRateEntry = state.maxByOrNull { it.value.toTokenInfo.tokenAmount.value } ?: return emptyMap()
val bestRate = bestRateEntry.value.toTokenInfo.tokenAmount.value
val hundredPercent = BigDecimal("100")
- return state.mapNotNull {
+ return state.entries.mapNotNull {
if (it.key != bestRateEntry.key) {
val amount = it.value.toTokenInfo.tokenAmount.value
val percentDiff = BigDecimal.ONE.minus(
amount.divide(bestRate, RoundingMode.HALF_UP),
).multiply(hundredPercent)
- percentDiff.setScale(2, RoundingMode.HALF_UP).toFloat().absoluteValue
+ it.key.providerId to percentDiff.setScale(2, RoundingMode.HALF_UP).toFloat().absoluteValue
} else {
null
}
- }
+ }.toMap()
}
private fun createSelectedAppCurrencyFlow(): StateFlow {
diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml
index edc76744e7..0df8cabe1b 100644
--- a/gradle/dependencies.toml
+++ b/gradle/dependencies.toml
@@ -88,7 +88,7 @@ spr-client = "3.6.2"
# endregion Other libraries
# region Tangem
-tangemBlockchainSdk = "release-app_5.4-412"
+tangemBlockchainSdk = "release-app_5.4-416"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "release-app_5.4-315"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^