diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt
index 94cd9cbf1f..3be2a6ebec 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt
@@ -48,6 +48,9 @@ enum class ExchangeStatus {
@Json(name = "verifying")
VERIFYING,
+
+ @Json(name = "cancelled")
+ CANCELLED,
}
data class ExchangeStatusError(
diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/StatusCodeInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/di/StatusCodeInterceptor.kt
index 7a6362efe3..4da4c8b17d 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/di/StatusCodeInterceptor.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/di/StatusCodeInterceptor.kt
@@ -36,13 +36,14 @@ class StatusCodeInterceptor : Interceptor {
}
private fun getBody(): String {
- return "\"error\": {\n" +
- " \"code\": 2290,\n" +
- " \"description\": \"Core: receivedDecimals is not equal to expressDecimals\",\n" +
+ return "{\n" +
+ " \"error\": {\n" +
+ " \"code\": 2250,\n" +
+ " \"description\": \"Core: exchange too small amount\",\n" +
" \"message\": \"Not valid\",\n" +
- " \"receivedToDecimals\": 5,\n" +
- " \"expressToDecimals\": 5\n" +
- " }"
+ " \"minAmount\": 5\n" +
+ " }\n" +
+ "}"
}
companion object {
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index c2cdf27727..f9f34ddd5f 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -189,8 +189,6 @@
Условия использования
К сожалению, текущая версия приложения не готова к работе с этой картой, проверьте наличие обновлений
Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком.
- Вы получаете
- Вы отправляете
Мои токены
У вас нет добавленных токенов. Добавьте токены для обмена
Токены не найдены. Пожалуйста, попробуйте другой запрос
@@ -205,6 +203,7 @@
Операция не выполнена провайдером
Посетите сайт провайдера для проверки
Провайдер запрашивает прохождение верификации
+ Отменен
Подтверждено
Подтверждение
Подтверждение...
@@ -523,6 +522,7 @@
Подтверждения считаются отраслевым стандартом для всех децентрализованных бирж и защищают ваш кошелек от доступа со стороны смарт-контракта без вашего разрешения. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту 1inch разрешение тратить ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете обменять свой токен.
Подтвердить
Ошибка: %s
+ Вы отправляете
Произошла ошибка. Пожалуйста, попробуйте еще раз.
Дать разрешение
Сильные колебания цены!
@@ -541,13 +541,12 @@
Ваш кошелек
Чтобы продолжить, вам нужно разрешить смарт-контракту 1inch использовать ваш %s
Безлимитно
- Вы отправляете
- Вы получите
Открыть в обозревателе
В процессе
Обменять
Обмен %s на
Котировки включают дополнительную комиссию Tangem в размере %s. Это помогает нам предоставлять первоклассный продукт.
+ Вы получите
Другие токены
Выберите токен
Ваши токены
@@ -732,4 +731,7 @@
Сканировать карту
Используйте %s или отсканируйте карту для входа в приложение
C возвращением!
+ Обмен через %s
+ Данные провайдера. Сумма к получению может измениться в зависимости от рыночных условий.
+ Статус обмена
diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml
index f595e68773..adb5903417 100644
--- a/core/res/src/main/res/values-zh-rTW/strings.xml
+++ b/core/res/src/main/res/values-zh-rTW/strings.xml
@@ -137,8 +137,6 @@
服務條款
糟糕,當前版本的應用程序無法使用此卡,請檢查更新
您使用了另一個錢包中的卡。點按與此錢包關聯的卡片
- 您收到
- 您發送
以下信息是可選的。如果不想共享,可以將其刪除
告訴我們您缺少哪些功能,我們會盡力幫助您
請告訴我們你有什麼卡
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 94f6ed4c2e..bf64538d4b 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -188,8 +188,6 @@
Terms of Service
Oops, the current version of the application is not ready to work with this card, please check for updates.
You have used a card from another wallet. Tap the card associated with this wallet
- You receive
- You send
My tokens
You haven\'t added any tokens yet. Add tokens via Market to swap
No tokens found. Please try another request
@@ -205,6 +203,7 @@
Operation failed by provider
Visit provider’s website for verification
KYC verification required by provider
+ Canceled
Confirmed
Confirming
Confirming…
@@ -219,7 +218,7 @@
Sending to you
Sending to you…
Sent
- Provider-sourced data. Estimated amount subject to change.
+ Provider-sourced data. Estimated amount subject to change due to market conditions.
Exchange status
Verified
Verification required
@@ -534,6 +533,7 @@
All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval.
Approve
Error: %s
+ You swap
There was an error. Please try again.
Give Permission
High price impact!
@@ -552,13 +552,12 @@
Your wallet
To continue, grant 1inch smart contracts permission to use your %s
Unlimited
- You swap
- You receive
View in Explorer
In progress
Swap
Swap %s for
Quotes include an additional Tangem commission of %s. This helps us deliver a top-of-the-line product.
+ You receive
Other tokens
Choose token
Your tokens
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt
index 867774adfb..4dfd01d36d 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithSearch.kt
@@ -95,7 +95,6 @@ private fun CollapsedSearchView(
modifier = Modifier
.background(TangemTheme.colors.background.secondary)
.fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt
index 0370fe0efe..bfae5cb313 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt
@@ -111,7 +111,7 @@ internal fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIconP
TangemButtonSize.Default,
TangemButtonSize.WideAction,
TangemButtonSize.TwoLines,
- -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32
+ -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16
TangemButtonSize.Text -> when (icon) {
is TangemButtonIconPosition.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16
is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16
diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
index c64c860e28..e2d0c8dc42 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
@@ -22,7 +22,13 @@ object BigDecimalFormatter {
roundingMode = RoundingMode.DOWN
}
- return formatter.format(cryptoAmount) + "\u2009$cryptoCurrency"
+ return formatter.format(cryptoAmount).let {
+ if (cryptoCurrency.isEmpty()) {
+ it
+ } else {
+ it + "\u2009$cryptoCurrency"
+ }
+ }
}
fun formatCryptoAmount(cryptoAmount: BigDecimal?, cryptoCurrency: CryptoCurrency): String {
diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSwapPromoRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultSwapPromoRepository.kt
index dd94a2e07c..8ba9fe211e 100644
--- a/data/settings/src/main/java/com/tangem/data/settings/DefaultSwapPromoRepository.kt
+++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultSwapPromoRepository.kt
@@ -40,16 +40,23 @@ class DefaultSwapPromoRepository(
)
}
- private fun checkPromoPeriod(): Boolean {
- val currentDate = Calendar.getInstance()
- return currentDate.get(Calendar.DATE) in START_DATE_KEY..END_DATE_KEY &&
- currentDate.get(Calendar.MONTH) == MONTH_KEY && currentDate.get(Calendar.YEAR) == YEAR_KEY
+ private suspend fun checkPromoPeriod(): Boolean {
+ val calendar = Calendar.getInstance()
+ val currentTime = calendar.timeInMillis
+ calendar.set(END_YEAR_KEY, END_MONTH_KEY, END_DAY_KEY, 0, 0, 0)
+ val endTime = calendar.timeInMillis
+
+ val shouldShow = endTime - currentTime > 0
+ if (!shouldShow) {
+ setNeverToShowToken()
+ setNeverToShowWallet()
+ }
+ return shouldShow
}
companion object {
- private const val START_DATE_KEY = 15
- private const val END_DATE_KEY = 31
- private const val MONTH_KEY = 11 // December
- private const val YEAR_KEY = 2023 // just in case
+ private const val END_DAY_KEY = 1
+ private const val END_MONTH_KEY = 1 // February
+ private const val END_YEAR_KEY = 2024
}
}
\ No newline at end of file
diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ErrorsDataConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ErrorsDataConverter.kt
index 6ca55d7f04..fa28cf44f2 100644
--- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ErrorsDataConverter.kt
+++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ErrorsDataConverter.kt
@@ -1,6 +1,7 @@
package com.tangem.feature.swap.converters
import com.squareup.moshi.JsonAdapter
+import com.tangem.datasource.api.express.models.response.ExpressError
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
@@ -21,28 +22,47 @@ internal class ErrorsDataConverter(
2220 -> DataError.ExchangeProviderNotActiveError(code = error.code)
2230 -> DataError.ExchangeProviderNotAvailableError(code = error.code)
2240 -> DataError.ExchangeNotPossibleError(code = error.code)
- 2250 -> DataError.ExchangeTooSmallAmountError(
- code = error.code,
- amount = createFromAmountWithOffset(
- requireNotNull(error.value?.minAmount),
- requireNotNull(error.value?.decimals),
- ),
- )
- 2260 -> DataError.ExchangeNotEnoughAllowanceError(
- code = error.code,
- currentAllowance = requireNotNull(error.value?.currentAllowance),
- )
+ 2250 -> tryParseExchangeTooSmallAmountError(error = error)
+ 2260 -> tryParseExchangeNotEnoughAllowanceError(error = error)
2270 -> DataError.ExchangeNotEnoughBalanceError(code = error.code)
2280 -> DataError.ExchangeInvalidAddressError(code = error.code)
- 2290 -> DataError.ExchangeInvalidFromDecimalsError(
- code = error.code,
- receivedFromDecimals = requireNotNull(error.value?.receivedFromDecimals),
- expressFromDecimals = requireNotNull(error.value?.expressFromDecimals),
- )
+ 2290 -> tryParseExchangeInvalidFromDecimalsError(error = error)
else -> DataError.UnknownErrorWithCode(error.code)
}
} catch (e: Exception) {
return DataError.UnknownError
}
}
+
+ private fun tryParseExchangeTooSmallAmountError(error: ExpressError): DataError {
+ val minAmount = error.value?.minAmount ?: return DataError.UnknownErrorWithCode(error.code)
+ val decimals = error.value?.decimals ?: return DataError.UnknownErrorWithCode(error.code)
+
+ return DataError.ExchangeTooSmallAmountError(
+ code = error.code,
+ amount = createFromAmountWithOffset(minAmount, decimals),
+ )
+ }
+
+ private fun tryParseExchangeNotEnoughAllowanceError(error: ExpressError): DataError {
+ val currentAllowance = error.value?.currentAllowance ?: return DataError.UnknownErrorWithCode(error.code)
+
+ return DataError.ExchangeNotEnoughAllowanceError(
+ code = error.code,
+ currentAllowance = currentAllowance,
+ )
+ }
+
+ private fun tryParseExchangeInvalidFromDecimalsError(error: ExpressError): DataError {
+ val receivedFromDecimals = error.value?.receivedFromDecimals ?: return DataError.UnknownErrorWithCode(
+ code = error.code,
+ )
+ val expressFromDecimals = error.value?.expressFromDecimals ?: return DataError.UnknownErrorWithCode(error.code)
+
+ return DataError.ExchangeInvalidFromDecimalsError(
+ code = error.code,
+ receivedFromDecimals = receivedFromDecimals,
+ expressFromDecimals = expressFromDecimals,
+ )
+ }
}
\ No newline at end of file
diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt
index e412c74751..1e3727b208 100644
--- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt
+++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt
@@ -17,4 +17,5 @@ enum class ExchangeStatus {
Sending,
Finished,
Refunded,
+ Cancelled,
}
\ No newline at end of file
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt
index d475bad6f2..936254d3fa 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/converters/TokensDataConverter.kt
@@ -8,7 +8,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.domain.models.domain.CryptoCurrencySwapInfo
-import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
+import com.tangem.feature.swap.models.CurrenciesGroupWithFromCurrency
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
import com.tangem.feature.swap.models.TokenBalanceData
import com.tangem.feature.swap.models.TokenToSelectState
@@ -21,17 +21,21 @@ class TokensDataConverter(
private val onTokenSelected: (String) -> Unit,
private val isBalanceHiddenProvider: Provider,
private val appCurrencyProvider: Provider,
-) : Converter {
+) : Converter {
- override fun convert(value: CurrenciesGroup): SwapSelectTokenStateHolder {
+ override fun convert(value: CurrenciesGroupWithFromCurrency): SwapSelectTokenStateHolder {
+ val group = value.group
val availableTitle = TokenToSelectState.Title(
resourceReference(R.string.exchange_tokens_available_tokens_header),
)
val unavailableTitle = TokenToSelectState.Title(
- resourceReference(R.string.exchange_tokens_available_tokens_header),
+ resourceReference(
+ R.string.exchange_tokens_unavailable_tokens_header,
+ wrappedList(value.fromCurrency.name),
+ ),
)
return SwapSelectTokenStateHolder(
- availableTokens = value.available.map { tokenWithBalanceToTokenToSelect(it, true) }
+ availableTokens = group.available.map { tokenWithBalanceToTokenToSelect(it, true) }
.toMutableList()
.apply {
if (this.isNotEmpty()) {
@@ -39,7 +43,7 @@ class TokensDataConverter(
}
}
.toImmutableList(),
- unavailableTokens = value.unavailable.map { tokenWithBalanceToTokenToSelect(it, false) }
+ unavailableTokens = group.unavailable.map { tokenWithBalanceToTokenToSelect(it, false) }
.toMutableList()
.apply {
if (this.isNotEmpty()) {
@@ -49,7 +53,7 @@ class TokensDataConverter(
.toImmutableList(),
onSearchEntered = onSearchEntered,
onTokenSelected = onTokenSelected,
- afterSearch = value.afterSearch,
+ afterSearch = group.afterSearch,
)
}
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/CurrenciesGroupWithFromCurrency.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/CurrenciesGroupWithFromCurrency.kt
new file mode 100644
index 0000000000..8251580bb8
--- /dev/null
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/CurrenciesGroupWithFromCurrency.kt
@@ -0,0 +1,9 @@
+package com.tangem.feature.swap.models
+
+import com.tangem.domain.tokens.model.CryptoCurrency
+import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
+
+data class CurrenciesGroupWithFromCurrency(
+ val group: CurrenciesGroup,
+ val fromCurrency: CryptoCurrency,
+)
\ No newline at end of file
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt
index 577c2e96af..2ccc7c9571 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt
@@ -105,12 +105,12 @@ sealed interface TransactionCardType {
data class Inputtable(
val onAmountChanged: ((String) -> Unit),
val onFocusChanged: ((Boolean) -> Unit),
- @StringRes override val headerResId: Int = R.string.exchange_send_view_header,
+ @StringRes override val headerResId: Int = R.string.swapping_from_title,
) : TransactionCardType
data class ReadOnly(
val highPriceImpact: String? = null,
- @StringRes override val headerResId: Int = R.string.exchange_receive_view_header,
+ @StringRes override val headerResId: Int = R.string.swapping_to_title,
) : TransactionCardType
}
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 0621d0a12a..d30d7591f2 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
@@ -34,16 +34,16 @@ fun ChooseProviderBottomSheet(config: TangemBottomSheetConfig) {
}
}
+@Suppress("LongMethod")
@Composable
private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetConfig) {
- Column {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = stringResource(R.string.express_choose_providers_title),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
modifier = Modifier
- .padding(top = TangemTheme.dimens.spacing10)
- .align(Alignment.CenterHorizontally),
+ .padding(top = TangemTheme.dimens.spacing10),
)
Text(
text = stringResource(R.string.express_choose_providers_subtitle),
@@ -51,8 +51,7 @@ private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetC
color = TangemTheme.colors.text.secondary,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing10)
- .padding(horizontal = TangemTheme.dimens.spacing56)
- .align(Alignment.CenterHorizontally),
+ .padding(horizontal = TangemTheme.dimens.spacing56),
textAlign = TextAlign.Center,
)
Column(
@@ -79,7 +78,11 @@ private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetC
enabled = provider.onProviderClick != null,
onClick = { provider.onProviderClick?.invoke(provider.id) },
)
- .padding(TangemTheme.dimens.spacing12),
+ .padding(
+ top = TangemTheme.dimens.spacing12,
+ bottom = TangemTheme.dimens.spacing12,
+ end = TangemTheme.dimens.spacing12,
+ ),
)
}
}
@@ -87,8 +90,6 @@ private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetC
painterResource(id = R.drawable.ic_lightning_16),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
- modifier = Modifier
- .align(Alignment.CenterHorizontally),
)
Text(
text = stringResource(R.string.express_more_providers_soon),
@@ -96,8 +97,7 @@ private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetC
color = TangemTheme.colors.icon.informative,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing6, bottom = TangemTheme.dimens.spacing16)
- .padding(horizontal = TangemTheme.dimens.spacing56)
- .align(Alignment.CenterHorizontally),
+ .padding(horizontal = TangemTheme.dimens.spacing56),
textAlign = TextAlign.Center,
)
}
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 b7c7418dae..5246e8ed36 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
@@ -98,12 +98,8 @@ internal class StateBuilder(
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
return uiStateHolder.copy(
sendCardData = SwapCardState.SwapCardData(
- type = TransactionCardType.ReadOnly(
- headerResId = R.string.exchange_send_view_header,
- ),
- amountTextFieldValue = TextFieldValue(
- text = "0",
- ),
+ type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.Inputtable),
+ amountTextFieldValue = null,
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
token = fromToken,
tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl,
@@ -111,7 +107,7 @@ internal class StateBuilder(
isNotNativeToken = uiStateHolder.sendCardData.isNotNativeToken,
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
- balance = fromToken.getFormattedAmount(),
+ balance = fromToken.getFormattedAmount(isNeedSymbol = false),
networkIconRes = getActiveIconRes(fromToken.currency.network.id.value),
isBalanceHidden = isBalanceHiddenProvider(),
),
@@ -209,7 +205,7 @@ internal class StateBuilder(
fromToken: CryptoCurrency,
swapProvider: SwapProvider,
bestRatedProviderId: String,
- isManyProviders: Boolean,
+ isNeedBestRateBadge: Boolean,
selectedFeeType: FeeType,
isReverseSwapPossible: Boolean,
): SwapStateHolder {
@@ -231,7 +227,7 @@ internal class StateBuilder(
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
networkIconRes = uiStateHolder.sendCardData.networkIconRes,
- balance = fromCurrencyStatus.getFormattedAmount(),
+ balance = fromCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
isBalanceHidden = isBalanceHiddenProvider(),
),
receiveCardData = SwapCardState.SwapCardData(
@@ -245,7 +241,7 @@ internal class StateBuilder(
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
networkIconRes = uiStateHolder.receiveCardData.networkIconRes,
- balance = toCurrencyStatus.getFormattedAmount(),
+ balance = toCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
isBalanceHidden = isBalanceHiddenProvider(),
),
networkCurrency = quoteModel.networkCurrency,
@@ -270,7 +266,7 @@ internal class StateBuilder(
isBestRate = bestRatedProviderId == swapProvider.providerId,
fromTokenInfo = quoteModel.fromTokenInfo,
toTokenInfo = quoteModel.toTokenInfo,
- isNeedBadge = isManyProviders,
+ isNeedBestRateBadge = isNeedBestRateBadge,
selectionType = ProviderState.SelectionType.CLICK,
onProviderClick = actions.onProviderClick,
),
@@ -302,6 +298,7 @@ internal class StateBuilder(
)
}
+ @Suppress("LongMethod", "CyclomaticComplexMethod")
private fun getWarningsForSuccessState(
quoteModel: SwapState.QuotesLoadedState,
fromToken: CryptoCurrency,
@@ -326,18 +323,7 @@ internal class StateBuilder(
)
else -> Unit
}
- if (!quoteModel.preparedSwapConfigState.isFeeEnough &&
- quoteModel.preparedSwapConfigState.isBalanceEnough
- ) {
- warnings.add(
- SwapWarning.UnableToCoverFeeWarning(
- createUnableToCoverFeeNotificationConfig(
- fromToken = fromToken,
- onBuyClick = actions.onBuyClick,
- ),
- ),
- )
- }
+ addUnableCoverFeeWarning(quoteModel, fromToken, warnings)
// check isBalanceEnough, but for dex includeFeeInAmount always Excluded
if (!quoteModel.preparedSwapConfigState.isBalanceEnough &&
quoteModel.preparedSwapConfigState.includeFeeInAmount !is IncludeFeeInAmount.Included
@@ -377,6 +363,26 @@ internal class StateBuilder(
return warnings
}
+ private fun addUnableCoverFeeWarning(
+ quoteModel: SwapState.QuotesLoadedState,
+ fromToken: CryptoCurrency,
+ warnings: MutableList,
+ ) {
+ if (!quoteModel.preparedSwapConfigState.isFeeEnough &&
+ quoteModel.preparedSwapConfigState.isBalanceEnough &&
+ quoteModel.permissionState !is PermissionDataState.PermissionLoading
+ ) {
+ warnings.add(
+ SwapWarning.UnableToCoverFeeWarning(
+ createUnableToCoverFeeNotificationConfig(
+ fromToken = fromToken,
+ onBuyClick = actions.onBuyClick,
+ ),
+ ),
+ )
+ }
+ }
+
private fun getSwapButtonEnabled(preparedSwapConfigState: PreparedSwapConfigState): Boolean {
if (preparedSwapConfigState.hasOutgoingTransaction) return false
return when (preparedSwapConfigState.includeFeeInAmount) {
@@ -431,7 +437,7 @@ internal class StateBuilder(
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
networkIconRes = uiStateHolder.receiveCardData.networkIconRes,
- balance = toToken.getFormattedAmount(),
+ balance = toToken.getFormattedAmount(isNeedSymbol = false),
isBalanceHidden = isBalanceHiddenProvider(),
)
} ?: SwapCardState.Empty(
@@ -445,6 +451,7 @@ internal class StateBuilder(
return uiStateHolder.copy(
sendCardData = uiStateHolder.sendCardData.copy(
amountEquivalent = getFormattedFiatAmount(fromToken.amountFiat),
+ balance = fromToken.cryptoCurrencyStatus.getFormattedAmount(isNeedSymbol = false),
),
receiveCardData = receiveCardData,
warnings = warnings,
@@ -583,10 +590,17 @@ internal class StateBuilder(
)
}
- fun addTokensToState(uiState: SwapStateHolder, tokensDataState: CurrenciesGroup): SwapStateHolder {
+ fun addTokensToState(
+ uiState: SwapStateHolder,
+ fromToken: CryptoCurrency,
+ tokensDataState: CurrenciesGroup,
+ ): SwapStateHolder {
return uiState.copy(
selectTokenState = tokensDataConverter.convert(
- value = tokensDataState,
+ value = CurrenciesGroupWithFromCurrency(
+ fromCurrency = fromToken,
+ group = tokensDataState,
+ ),
),
)
}
@@ -1121,7 +1135,7 @@ internal class StateBuilder(
fromTokenInfo: TokenSwapInfo,
toTokenInfo: TokenSwapInfo,
selectionType: ProviderState.SelectionType,
- isNeedBadge: Boolean,
+ isNeedBestRateBadge: Boolean,
onProviderClick: (String) -> Unit,
): ProviderState {
val rate = toTokenInfo.tokenAmount.value.calculateRate(
@@ -1131,7 +1145,7 @@ internal class StateBuilder(
val fromCurrencySymbol = fromTokenInfo.cryptoCurrencyStatus.currency.symbol
val toCurrencySymbol = toTokenInfo.cryptoCurrencyStatus.currency.symbol
val rateString = "1 $fromCurrencySymbol ≈ $rate $toCurrencySymbol"
- val badge = if (isNeedBadge && isBestRate) {
+ val badge = if (isNeedBestRateBadge && isBestRate) {
ProviderState.AdditionalBadge.BestTrade
} else {
ProviderState.AdditionalBadge.Empty
@@ -1214,10 +1228,10 @@ internal class StateBuilder(
)
}
- private fun CryptoCurrencyStatus.getFormattedAmount(): String {
+ private fun CryptoCurrencyStatus.getFormattedAmount(isNeedSymbol: Boolean): String {
val amount = value.amount ?: return UNKNOWN_AMOUNT_SIGN
-
- return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals)
+ val symbol = if (isNeedSymbol) currency.symbol else ""
+ return BigDecimalFormatter.formatCryptoAmount(amount, symbol, currency.decimals)
}
@Suppress("UnusedPrivateMember")
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 571a1b51f1..e3c408bde2 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
@@ -131,7 +131,7 @@ private fun MainInfo(state: SwapStateHolder) {
},
onSelectTokenClick = state.onSelectTokenClick,
)
- val marginCard = TangemTheme.dimens.spacing16
+ val marginCard = TangemTheme.dimens.spacing12
TransactionCardData(
priceImpact = priceImpact,
swapCardState = state.receiveCardData,
@@ -140,7 +140,7 @@ private fun MainInfo(state: SwapStateHolder) {
},
onSelectTokenClick = state.onSelectTokenClick,
)
- val marginButton = TangemTheme.dimens.spacing32
+ val marginButton = TangemTheme.dimens.spacing30
SwapButton(
state,
modifier = Modifier.constrainAs(button) {
@@ -413,7 +413,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U
else -> {
PrimaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
- text = stringResource(id = R.string.common_swap),
+ text = stringResource(id = R.string.swapping_swap_action),
iconResId = R.drawable.ic_tangem_24,
enabled = state.swapButton.enabled,
onClick = state.swapButton.onClick,
@@ -494,12 +494,12 @@ private val state = SwapStateHolder(
priceImpact = PriceImpact.Empty(),
tosState = TosState(
tosLink = LegalState(
- title = stringReference("Tangem"),
+ title = stringReference("Terms of Use"),
link = "https://tangem.com",
onClick = {},
),
policyLink = LegalState(
- title = stringReference("Tangem"),
+ title = stringReference("Privacy Policy"),
link = "https://tangem.com",
onClick = {},
),
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 87a82413f1..930039d697 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
@@ -127,8 +127,6 @@ private fun ListOfTokens(state: SwapSelectTokenStateHolder, modifier: Modifier =
.imePadding(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- item { SpacerH8() }
-
tokensToSelectItems(state.availableTokens, state.onTokenSelected)
item { SpacerH12() }
@@ -182,6 +180,7 @@ private fun TitleHeader(item: TokenToSelectState.Title, modifier: Modifier = Mod
Text(
text = item.title.resolveReference().uppercase(),
style = TangemTheme.typography.overline,
+ color = TangemTheme.colors.text.tertiary,
modifier = Modifier
.padding(
top = TangemTheme.dimens.spacing16,
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt
index d540514a81..4b44fb9b18 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt
@@ -60,7 +60,7 @@ private fun SwapSuccessScreenContent(state: SwapSuccessStateHolder, padding: Pad
TransactionDoneTitle(titleRes = R.string.swapping_success_view_title, date = state.timestamp)
SpacerH16()
InputRowImage(
- title = TextReference.Res(R.string.swapping_success_from_title),
+ title = TextReference.Res(R.string.swapping_from_title),
subtitle = state.fromTokenAmount,
caption = state.fromTokenFiatAmount,
tokenIconState = state.fromTokenIconState ?: TokenIconState.Loading,
@@ -70,7 +70,7 @@ private fun SwapSuccessScreenContent(state: SwapSuccessStateHolder, padding: Pad
)
SpacerH16()
InputRowImage(
- title = TextReference.Res(R.string.swapping_success_to_title),
+ title = TextReference.Res(R.string.swapping_to_title),
subtitle = state.toTokenAmount,
caption = state.toTokenFiatAmount,
tokenIconState = state.toTokenIconState ?: TokenIconState.Loading,
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 955d5e5f74..a18226212a 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
@@ -216,11 +216,12 @@ internal class SwapViewModel @Inject constructor(
}
}
- private fun updateTokensState(dataState: TokensDataStateExpress) {
- val tokensDataState = if (isOrderReversed) dataState.fromGroup else dataState.toGroup
+ private fun updateTokensState(tokenDataState: TokensDataStateExpress) {
+ val tokensDataState = if (isOrderReversed) tokenDataState.fromGroup else tokenDataState.toGroup
uiState = stateBuilder.addTokensToState(
uiState = uiState,
tokensDataState = tokensDataState,
+ fromToken = dataState.fromCryptoCurrency?.currency ?: initialCryptoCurrency,
)
}
@@ -326,7 +327,7 @@ internal class SwapViewModel @Inject constructor(
fromToken = fromToken.currency,
swapProvider = provider,
bestRatedProviderId = bestRatedProviderId,
- isManyProviders = dataState.lastLoadedSwapStates.size > 1,
+ isNeedBestRateBadge = dataState.lastLoadedSwapStates.consideredProvidersStates().size > 1,
selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
isReverseSwapPossible = isReverseSwapPossible(),
)
@@ -390,9 +391,7 @@ internal class SwapViewModel @Inject constructor(
}
private fun selectProvider(state: Map): SwapProvider {
- val consideredProviders = state.filter {
- it.value is SwapState.QuotesLoadedState || isExchangeTooSmallAmountError(it.value)
- }
+ val consideredProviders = state.consideredProvidersStates()
return if (consideredProviders.isNotEmpty()) {
val currentSelected = dataState.selectedProvider
@@ -420,16 +419,21 @@ internal class SwapViewModel @Inject constructor(
} else {
dataState.copy(
swapDataModel = swapDataModel,
- selectedFee = selectDefaultFee(state),
+ selectedFee = updateOrSelectFee(state),
)
}
}
- private fun selectDefaultFee(state: SwapState.QuotesLoadedState): TxFee? {
- return dataState.selectedFee ?: when (val txFee = state.txFee) {
+ private fun updateOrSelectFee(state: SwapState.QuotesLoadedState): TxFee? {
+ val selectedFeeType = dataState.selectedFee?.feeType ?: FeeType.NORMAL
+ return when (val txFee = state.txFee) {
TxFeeState.Empty -> null
is TxFeeState.MultipleFeeState -> {
- txFee.normalFee
+ if (selectedFeeType == FeeType.NORMAL) {
+ txFee.normalFee
+ } else {
+ txFee.priorityFee
+ }
}
is TxFeeState.SingleFeeState -> {
txFee.fee
@@ -689,19 +693,22 @@ internal class SwapViewModel @Inject constructor(
private fun onAmountChanged(value: String) {
val fromToken = dataState.fromCryptoCurrency
val toToken = dataState.toCryptoCurrency
- if (fromToken != null && toToken != null) {
+ if (fromToken != null) {
val decimals = fromToken.currency.decimals
val cutValue = cutAmountWithDecimals(decimals, value)
lastAmount.value = cutValue
uiState =
stateBuilder.updateSwapAmount(uiState, inputNumberFormatter.formatWithThousands(cutValue, decimals))
- amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) {
- startLoadingQuotes(
- fromToken = fromToken,
- toToken = toToken,
- amount = lastAmount.value,
- toProvidersList = findSwapProviders(fromToken, toToken),
- )
+
+ if (toToken != null) {
+ amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) {
+ startLoadingQuotes(
+ fromToken = fromToken,
+ toToken = toToken,
+ amount = lastAmount.value,
+ toProvidersList = findSwapProviders(fromToken, toToken),
+ )
+ }
}
}
}
@@ -853,14 +860,10 @@ internal class SwapViewModel @Inject constructor(
startLoadingQuotesFromLastState()
},
onPolicyClick = {
- uiState = stateBuilder.showWebViewBottomSheet(uiState, it) {
- uiState = stateBuilder.dismissBottomSheet(uiState)
- }
+ swapRouter.openUrl(it)
},
onTosClick = {
- uiState = stateBuilder.showWebViewBottomSheet(uiState, it) {
- uiState = stateBuilder.dismissBottomSheet(uiState)
- }
+ swapRouter.openUrl(it)
},
)
}
@@ -947,6 +950,12 @@ internal class SwapViewModel @Inject constructor(
.mapValues { it.value as SwapState.QuotesLoadedState }
}
+ private fun Map.consideredProvidersStates(): Map {
+ return this.filter {
+ it.value is SwapState.QuotesLoadedState || isExchangeTooSmallAmountError(it.value)
+ }
+ }
+
private fun isReverseSwapPossible(): Boolean {
val from = dataState.fromCryptoCurrency ?: return false
val to = dataState.toCryptoCurrency ?: return false
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt
index d55821009b..c1e0f67210 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt
@@ -29,6 +29,7 @@ internal data class SwapTransactionsState(
val fromCryptoSymbol: String,
val fromFiatAmount: String,
val fromCurrencyIcon: TokenIconState,
+ val showProviderLink: Boolean,
val onClick: () -> Unit,
val onGoToProviderClick: (String) -> Unit,
)
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt
index 8af1d13a13..3575d02e0f 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsActionButton.kt
@@ -78,7 +78,7 @@ internal sealed class TokenDetailsActionButton(val config: ActionButtonConfig) {
*/
data class Swap(val enabled: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton(
config = ActionButtonConfig(
- text = TextReference.Res(id = R.string.common_swap),
+ text = TextReference.Res(id = R.string.common_exchange),
iconResId = R.drawable.ic_exchange_vertical_24,
onClick = onClick,
enabled = enabled,
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt
index 514afc7709..c5069303c7 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt
@@ -62,6 +62,8 @@ internal class TokenDetailsSwapTransactionsStateConverter(
val toFiatAmount = toAmount.multiply(toCurrency.value.fiatRate)
val fromFiatAmount = fromAmount.multiply(fromCurrency.value.fiatRate)
val timestamp = transaction.timestamp
+ val notifications = getNotification(transaction.status?.status, transaction.status?.txExternalUrl)
+ val showProviderLink = getShowProviderLink(notifications, transaction.status)
result.add(
SwapTransactionsState(
txId = transaction.txId,
@@ -92,6 +94,7 @@ internal class TokenDetailsSwapTransactionsStateConverter(
fromCryptoSymbol = fromCurrency.currency.symbol,
fromFiatAmount = getFiatAmount(fromFiatAmount),
fromCurrencyIcon = iconStateConverter.convert(fromCurrency),
+ showProviderLink = showProviderLink,
onClick = { clickIntents.onSwapTransactionClick(transaction.txId) },
onGoToProviderClick = { url ->
analyticsEventsHandlerProvider().send(
@@ -109,12 +112,15 @@ internal class TokenDetailsSwapTransactionsStateConverter(
fun updateTxStatus(tx: SwapTransactionsState, statusModel: ExchangeStatusModel?): SwapTransactionsState {
if (statusModel == null || tx.activeStatus == statusModel.status) return tx
val hasFailed = tx.hasFailed || statusModel.status == ExchangeStatus.Failed
+ val notifications = getNotification(statusModel.status, statusModel.txExternalUrl)
+ val showProviderLink = getShowProviderLink(notifications, statusModel)
return tx.copy(
activeStatus = statusModel.status,
hasFailed = hasFailed,
- notification = getNotification(statusModel.status, statusModel.txExternalUrl),
+ notification = notifications,
statuses = getStatuses(statusModel.status, hasFailed),
txUrl = statusModel.txExternalUrl,
+ showProviderLink = showProviderLink,
)
}
@@ -149,6 +155,9 @@ internal class TokenDetailsSwapTransactionsStateConverter(
}
}
+ private fun getShowProviderLink(notifications: ExchangeStatusNotifications?, status: ExchangeStatusModel?) =
+ notifications == null && status?.txExternalUrl != null && status.status != ExchangeStatus.Cancelled
+
private fun getStatuses(status: ExchangeStatus?, hasFailed: Boolean = false): ImmutableList {
if (status == null) return persistentListOf()
val isWaiting = status == ExchangeStatus.New || status == ExchangeStatus.Waiting
@@ -164,26 +173,37 @@ internal class TokenDetailsSwapTransactionsStateConverter(
val isExchangingDone = !isExchanging && isConfirmingDone
val isSendingDone = !isSending && !isVerifying && !isFailed && isExchangingDone
- return listOf(
- waitStep(isWaiting, isWaitingDone),
- confirmStep(isConfirming, isConfirmingDone),
- exchangeStep(
- isExchanging = isExchanging,
- isExchangingDone = isExchangingDone,
- isRefunded = isRefunded,
- hasFailed = hasFailed,
- isVerifying = isVerifying,
- isFailed = isFailed,
- ),
- sendStep(
- isSending = isSending,
- isSendingDone = isSendingDone,
- isRefunded = isRefunded,
- hasFailed = hasFailed,
- ),
- ).toPersistentList()
+ return if (status == ExchangeStatus.Cancelled) {
+ listOf(cancelledStep())
+ } else {
+ listOf(
+ waitStep(isWaiting, isWaitingDone),
+ confirmStep(isConfirming, isConfirmingDone),
+ exchangeStep(
+ isExchanging = isExchanging,
+ isExchangingDone = isExchangingDone,
+ isRefunded = isRefunded,
+ hasFailed = hasFailed,
+ isVerifying = isVerifying,
+ isFailed = isFailed,
+ ),
+ sendStep(
+ isSending = isSending,
+ isSendingDone = isSendingDone,
+ isRefunded = isRefunded,
+ hasFailed = hasFailed,
+ ),
+ )
+ }.toPersistentList()
}
+ private fun cancelledStep() = ExchangeStatusState(
+ status = ExchangeStatus.Cancelled,
+ text = TextReference.Res(R.string.express_exchange_status_canceled),
+ isActive = true,
+ isDone = true,
+ )
+
private fun waitStep(isNew: Boolean, isNewDone: Boolean) = ExchangeStatusState(
status = ExchangeStatus.New,
text = when {
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt
index 1574bb2db6..ac5edc4fc4 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt
@@ -75,11 +75,15 @@ internal fun ExchangeStatusBlock(
}
}
- statuses.forEachIndexed { index, item ->
- ExchangeStatusStep(
- stepStatus = item,
- isLast = index == statuses.lastIndex,
- )
+ AnimatedContent(targetState = statuses.lastIndex, label = "Exchange Status List Change") {
+ Column {
+ statuses.forEachIndexed { index, item ->
+ ExchangeStatusStep(
+ stepStatus = item,
+ isLast = index == it,
+ )
+ }
+ }
}
}
}
@@ -101,6 +105,11 @@ private fun ExchangeStatusStep(
.size(TangemTheme.dimens.size20),
) {
when {
+ it.status == ExchangeStatus.Cancelled -> ExchangeStep(
+ iconRes = R.drawable.ic_close_24,
+ color = TangemTheme.colors.icon.warning,
+ isDone = false,
+ )
it.status == ExchangeStatus.Failed -> ExchangeStep(
iconRes = R.drawable.ic_close_24,
color = TangemTheme.colors.icon.warning,
@@ -131,6 +140,7 @@ private fun ExchangeStatusStep(
@Composable
private fun ExchangeStatusStepText(stepStatus: ExchangeStatusState) {
val textColor = when {
+ stepStatus.status == ExchangeStatus.Cancelled -> TangemTheme.colors.icon.warning
stepStatus.status == ExchangeStatus.Failed && !stepStatus.isDone -> TangemTheme.colors.icon.warning
stepStatus.status == ExchangeStatus.Verifying && !stepStatus.isDone -> TangemTheme.colors.icon.attention
stepStatus.isDone -> TangemTheme.colors.text.primary1
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt
index 7cd58e238d..da9113487a 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt
@@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH10
import com.tangem.core.ui.components.SpacerH12
@@ -50,6 +51,7 @@ private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetC
text = stringResource(id = R.string.express_exchange_status_subtitle),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.secondary,
+ textAlign = TextAlign.Center,
modifier = Modifier
.align(CenterHorizontally),
)
@@ -74,7 +76,7 @@ private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetC
SpacerH12()
ExchangeStatusBlock(
statuses = config.statuses,
- showLink = config.notification == null && config.txUrl != null,
+ showLink = config.showProviderLink,
onClick = { config.onGoToProviderClick(config.txUrl.orEmpty()) },
)
AnimatedContent(
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt
index c32011f436..94c1d12187 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt
@@ -4,7 +4,9 @@ import androidx.annotation.DrawableRes
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
@@ -42,7 +44,9 @@ internal fun LazyListScope.swapTransactionsItems(
val item = swapTxs[it]
val (iconRes, tint) = when (item.activeStatus) {
ExchangeStatus.Verifying -> R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention
- ExchangeStatus.Failed -> R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning
+ ExchangeStatus.Failed, ExchangeStatus.Cancelled -> {
+ R.drawable.ic_alert_circle_24 to TangemTheme.colors.icon.warning
+ }
else -> null to null
}
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt
index 4c78d11155..8ff627eaec 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt
@@ -18,7 +18,9 @@ import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
import com.tangem.feature.swap.domain.models.domain.ExchangeStatusModel
import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListModel
import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState
+import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsSwapTransactionsStateConverter
+import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
@@ -41,6 +43,7 @@ internal class ExchangeStatusFactory(
private val clickIntents: TokenDetailsClickIntents,
private val appCurrencyProvider: Provider,
private val analyticsEventsHandlerProvider: Provider,
+ private val currentStateProvider: Provider,
private val userWalletId: UserWalletId,
private val cryptoCurrency: CryptoCurrency,
) {
@@ -64,6 +67,27 @@ internal class ExchangeStatusFactory(
)
}
+ suspend fun removeTransactionOnBottomSheetClosed(): TokenDetailsState {
+ val state = currentStateProvider()
+ val bottomSheetConfig = state.bottomSheetConfig?.content as? ExchangeStatusBottomSheetConfig ?: return state
+ val selectedTx = bottomSheetConfig.value
+
+ return if (selectedTx.activeStatus.isTerminal()) {
+ swapTransactionRepository.removeTransaction(
+ userWalletId = userWalletId,
+ fromCryptoCurrencyId = selectedTx.fromCryptoCurrencyId,
+ toCryptoCurrencyId = selectedTx.toCryptoCurrencyId,
+ txId = selectedTx.txId,
+ )
+ val filteredTxs = state.swapTxs
+ .filterNot { it.txId == selectedTx.txId }
+ .toPersistentList()
+ state.copy(swapTxs = filteredTxs)
+ } else {
+ state
+ }
+ }
+
private fun getWalletCryptoCurrencies() = flow {
val selectedWallet = getSelectedWalletSyncUseCase().fold(
ifLeft = { null },
@@ -80,14 +104,16 @@ internal class ExchangeStatusFactory(
suspend fun updateSwapTxStatuses(swapTxList: PersistentList) = withContext(dispatchers.io) {
swapTxList.map { tx ->
async {
- val statusModel = getExchangeStatus(tx.txId)
- swapTransactionsStateConverter
- .updateTxStatus(tx, statusModel)
- .removeIfFinished(statusModel?.status)
+ if (tx.activeStatus.isTerminal()) {
+ tx
+ } else {
+ val statusModel = getExchangeStatus(tx.txId)
+ swapTransactionsStateConverter
+ .updateTxStatus(tx, statusModel)
+ }
}
}
.awaitAll()
- .filterNotNull()
.toPersistentList()
}
@@ -130,21 +156,8 @@ internal class ExchangeStatusFactory(
)
}
- private suspend fun SwapTransactionsState.removeIfFinished(status: ExchangeStatus?) = when (status) {
- null -> null // not found
- ExchangeStatus.Refunded, ExchangeStatus.Finished -> {
- swapTransactionRepository.removeTransaction(
- userWalletId = userWalletId,
- fromCryptoCurrencyId = fromCryptoCurrencyId,
- toCryptoCurrencyId = toCryptoCurrencyId,
- txId = txId,
- )
- null
- }
- else -> {
- this
- }
- }
+ private fun ExchangeStatus?.isTerminal() =
+ this == ExchangeStatus.Refunded || this == ExchangeStatus.Finished || this == ExchangeStatus.Cancelled
private fun toAnalyticStatus(status: ExchangeStatus?): ExchangeAnalyticsStatus? {
return when (status) {
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt
index 1c3f77a105..b2d4ba321e 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt
@@ -125,6 +125,7 @@ internal class TokenDetailsViewModel @Inject constructor(
clickIntents = this,
appCurrencyProvider = Provider { selectedAppCurrencyFlow.value },
analyticsEventsHandlerProvider = Provider { analyticsEventsHandler },
+ currentStateProvider = Provider { uiState },
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
)
@@ -229,7 +230,7 @@ internal class TokenDetailsViewModel @Inject constructor(
delay = EXCHANGE_STATUS_UPDATE_DELAY,
task = {
runCatching(dispatchers.io) {
- exchangeStatusFactory.updateSwapTxStatuses(swapTxs)
+ exchangeStatusFactory.updateSwapTxStatuses(uiState.swapTxs)
}
},
onSuccess = ::updateSwapTx,
@@ -547,6 +548,11 @@ internal class TokenDetailsViewModel @Inject constructor(
}
override fun onDismissBottomSheet() {
+ if (uiState.bottomSheetConfig?.content is ExchangeStatusBottomSheetConfig) {
+ viewModelScope.launch(dispatchers.main) {
+ uiState = exchangeStatusFactory.removeTransactionOnBottomSheetClosed()
+ }
+ }
uiState = stateFactory.getStateWithClosedBottomSheet()
}
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt
index ae4d06b0dc..c5af5a2cf6 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt
@@ -19,10 +19,7 @@ import org.burnoutcrew.reorderable.detectReorder
@Composable
internal fun NetworkGroupItem(networkName: String, modifier: Modifier = Modifier) {
- InternalNetworkGroupItem(
- modifier = modifier,
- networkName = networkName,
- )
+ InternalNetworkGroupItem(modifier = modifier, networkName = networkName)
}
@Composable
@@ -69,12 +66,13 @@ private fun InternalNetworkGroupItem(
.background(TangemTheme.colors.background.primary)
.padding(horizontal = TangemTheme.dimens.spacing14)
.fillMaxWidth()
- .heightIn(min = TangemTheme.dimens.size48),
+ .heightIn(min = TangemTheme.dimens.size40),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(id = R.string.wallet_network_group_title, networkName),
+ modifier = Modifier.padding(top = TangemTheme.dimens.spacing12, bottom = TangemTheme.dimens.spacing8),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt
index fabf18401f..2628469375 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt
@@ -88,7 +88,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
*/
data class Swap(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton(
config = ActionButtonConfig(
- text = TextReference.Res(id = R.string.common_swap),
+ text = TextReference.Res(id = R.string.common_exchange),
iconResId = R.drawable.ic_exchange_vertical_24,
onClick = onClick,
enabled = enabled,
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/TokenActionsProvider.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/TokenActionsProvider.kt
index df989ef9b8..7a452a255c 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/TokenActionsProvider.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/TokenActionsProvider.kt
@@ -78,7 +78,7 @@ internal class TokenActionsProvider(
action = { clickIntents.onMultiCurrencySendClick(cryptoCurrencyStatus) }
}
is TokenActionsState.ActionState.Swap -> {
- title = resourceReference(R.string.common_swap)
+ title = resourceReference(R.string.common_exchange)
icon = R.drawable.ic_exchange_horizontal_24
action = { clickIntents.onSwapClick(cryptoCurrencyStatus) }
}
diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml
index 237bd3e4a9..805056d1a5 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-423"
+tangemBlockchainSdk = "release-app_5.4-434"
#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 ^