From c19ca08957ef3a9a1e2f4adf9699af9b753f2d82 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Dec 2023 12:15:00 +0300 Subject: [PATCH 1/5] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 2 ++ core/res/src/main/res/values/strings.xml | 2 ++ features/swap/domain/build.gradle.kts | 1 + .../feature/swap/domain/SwapInteractorImpl.kt | 9 ++++-- .../models/domain/PreparedSwapConfigState.kt | 1 + .../swap/domain/models/ui/SwapState.kt | 1 + .../feature/swap/models/SwapStateHolder.kt | 2 ++ .../tangem/feature/swap/ui/ProviderItem.kt | 1 + .../tangem/feature/swap/ui/StateBuilder.kt | 20 +++++++++++++ .../feature/swap/ui/SwapScreenContent.kt | 29 ++++++++++--------- 10 files changed, 52 insertions(+), 16 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 6fe946df8b..391fe57950 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -647,6 +647,8 @@ Не для пользователя! Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены. Для работы с сетью необходим депозит + Обмен будет доступен после завершения %s транзакции + У вас есть активная транзакция У вас в списке нет монет доступных для обмена с %s Нет доступных токенов для обмена Cервис временно недоступен diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index cbd1bf2dab..340dc79988 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -677,6 +677,8 @@ Not for users! %1$s network requires an Existential Deposit. If your account drops below %2$s, it will be deactivated, and any remaining funds will be destroyed. Network requires Existential Deposit + Swap will be available after the %s transaction is complete + You have active transaction You do not have any %s exchangeable coins in your list No available tokens to swap To make a transaction you need to deposit some %1$s %2$s diff --git a/features/swap/domain/build.gradle.kts b/features/swap/domain/build.gradle.kts index f3841b400d..b1a50d2773 100644 --- a/features/swap/domain/build.gradle.kts +++ b/features/swap/domain/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation(projects.domain.demo) implementation(projects.domain.card) implementation(projects.domain.appCurrency.models) + implementation(projects.domain.txhistory.models) /** Core modules */ implementation(projects.core.utils) 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 6504c6d53a..f15db9df11 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 @@ -420,7 +420,6 @@ internal class SwapInteractorImpl @Inject constructor( permissionState = PermissionDataState.Empty, preparedSwapConfigState = state.preparedSwapConfigState.copy( isBalanceEnough = includeFeeInAmount !is IncludeFeeInAmount.BalanceNotEnough, - isFeeEnough = includeFeeInAmount !is IncludeFeeInAmount.BalanceNotEnough, includeFeeInAmount = includeFeeInAmount, ), ) @@ -798,6 +797,7 @@ internal class SwapInteractorImpl @Inject constructor( isFeeEnough = includeFeeInAmount !is IncludeFeeInAmount.BalanceNotEnough, isAllowedToSpend = isAllowedToSpend, isBalanceEnough = isBalanceWithoutFeeEnough, + hasOutgoingTransaction = hasOutgoingTransaction(fromToken), includeFeeInAmount = includeFeeInAmount, ), ) @@ -873,7 +873,7 @@ internal class SwapInteractorImpl @Inject constructor( /** * Load swap data calls only if spend is allowed for token contract address */ - @Suppress("LongParameterList") + @Suppress("LongParameterList", "LongMethod") private suspend fun loadDexSwapData( provider: SwapProvider, networkId: String, @@ -932,6 +932,7 @@ internal class SwapInteractorImpl @Inject constructor( isAllowedToSpend = true, isBalanceEnough = isBalanceIncludeFeeEnough, isFeeEnough = isFeeEnough, + hasOutgoingTransaction = hasOutgoingTransaction(fromToken), includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex ), ) @@ -1226,6 +1227,10 @@ internal class SwapInteractorImpl @Inject constructor( } } + private fun hasOutgoingTransaction(cryptoCurrencyStatuses: CryptoCurrencyStatus): Boolean { + return cryptoCurrencyStatuses.value.pendingTransactions.any { it.isOutgoing } + } + private fun Fee.getGasLimit(): Int { return when (this) { is Fee.Common -> 0 diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PreparedSwapConfigState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PreparedSwapConfigState.kt index 53cfae4ded..ca44dc046e 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PreparedSwapConfigState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PreparedSwapConfigState.kt @@ -14,6 +14,7 @@ data class PreparedSwapConfigState( val isAllowedToSpend: Boolean, val isBalanceEnough: Boolean, val isFeeEnough: Boolean, + val hasOutgoingTransaction: Boolean, val includeFeeInAmount: IncludeFeeInAmount, ) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index d031891ba3..d2788d35a4 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -19,6 +19,7 @@ sealed interface SwapState { isAllowedToSpend = false, isBalanceEnough = false, isFeeEnough = false, + hasOutgoingTransaction = false, includeFeeInAmount = IncludeFeeInAmount.Excluded, ), val permissionState: PermissionDataState = PermissionDataState.Empty, 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 e30eff225c..ae4d9ba3cf 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 @@ -6,6 +6,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.components.states.Item import com.tangem.core.ui.components.states.SelectableItemsState +import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.swap.domain.models.ui.TxFee import com.tangem.feature.swap.models.states.FeeItemState @@ -125,6 +126,7 @@ sealed interface SwapWarning { data class TooSmallAmountWarning(val notificationConfig: NotificationConfig) : SwapWarning data class UnableToCoverFeeWarning(val notificationConfig: NotificationConfig) : SwapWarning data class GeneralWarning(val notificationConfig: NotificationConfig) : SwapWarning + data class TransactionInProgressWarning(val title: TextReference, val description: TextReference) : SwapWarning } enum class GenericWarningType { 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 51f6936885..9c3b990219 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 @@ -261,6 +261,7 @@ private fun ProviderLoadingState(modifier: Modifier = Modifier) { CircularProgressIndicator( modifier = Modifier.size(TangemTheme.dimens.size16), color = TangemTheme.colors.icon.informative, + strokeWidth = TangemTheme.dimens.size2, ) Text( text = "Fetching best rates ...", 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 6881df60df..7042da2c25 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 @@ -320,6 +320,26 @@ internal class StateBuilder( ), ) } + if (quoteModel.permissionState is PermissionDataState.PermissionLoading) { + warnings.add( + SwapWarning.TransactionInProgressWarning( + title = resourceReference(R.string.swapping_pending_transaction_title), + description = resourceReference(R.string.swapping_pending_transaction_subtitle), + ), + ) + } else if (quoteModel.preparedSwapConfigState.hasOutgoingTransaction) { + warnings.add( + SwapWarning.TransactionInProgressWarning( + title = resourceReference(R.string.warning_express_active_transaction_title), + description = resourceReference( + id = R.string.warning_express_active_transaction_message, + formatArgs = wrappedList( + quoteModel.fromTokenInfo.cryptoCurrencyStatus.currency.network.currencySymbol, + ), + ), + ), + ) + } return warnings } 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 ace1d19a31..8d1fe2e6df 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 @@ -20,6 +20,7 @@ import com.tangem.core.ui.components.* import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.getActiveIconResByCoinId +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.domain.models.ui.FeeType @@ -67,20 +68,6 @@ internal fun SwapScreenContent(state: SwapStateHolder, modifier: Modifier = Modi if (state.warnings.isNotEmpty()) SwapWarnings(warnings = state.warnings) - AnimatedVisibility(visible = state.permissionState is SwapPermissionState.InProgress) { - CardWithIcon( - title = stringResource(id = R.string.swapping_pending_transaction_title), - description = stringResource(id = R.string.swapping_pending_transaction_subtitle), - icon = { - CircularProgressIndicator( - modifier = Modifier - .size(TangemTheme.dimens.size16), - color = TangemTheme.colors.icon.primary1, - strokeWidth = TangemTheme.dimens.size2, - ) - }, - ) - } MainButton(state = state, onPermissionWarningClick = state.onShowPermissionBottomSheet) } } @@ -285,6 +272,20 @@ private fun SwapWarnings(warnings: List) { config = warning.notificationConfig, ) } + is SwapWarning.TransactionInProgressWarning -> { + CardWithIcon( + title = warning.title.resolveReference(), + description = warning.description.resolveReference(), + icon = { + CircularProgressIndicator( + modifier = Modifier + .size(TangemTheme.dimens.size16), + color = TangemTheme.colors.icon.primary1, + strokeWidth = TangemTheme.dimens.size2, + ) + }, + ) + } else -> {} } SpacerH8() From 30fbc7a45afa375f04b781a4e313a75142f19a25 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Dec 2023 13:42:13 +0300 Subject: [PATCH 2/5] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 2 +- .../src/main/java/com/tangem/feature/swap/ui/ProviderItem.kt | 3 ++- .../src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 340dc79988..08c1787e9b 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -228,7 +228,7 @@ Best rate Available from %s Unavailable for this pair - Permission Needed + Permission Required The following information is optional. You can erase it if you don\'t want to share it. Tell us what functions you are missing, and we will try to help you. Please tell us what card do you have 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 9c3b990219..41adc2a361 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 @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import coil.compose.SubcomposeAsyncImage @@ -374,7 +375,7 @@ private fun PermissionBadgeItem(modifier: Modifier = Modifier) { ), ) { Text( - text = "Permission required", + text = stringResource(id = R.string.express_provider_permission_needed), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.tertiary, modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing6), 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 7042da2c25..f2adfb4a9d 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 @@ -976,7 +976,7 @@ internal class StateBuilder( // region warnings private fun createPermissionNotificationConfig(fromTokenSymbol: String): NotificationConfig { return NotificationConfig( - title = resourceReference(R.string.swapping_permission_header), + title = resourceReference(R.string.express_provider_permission_needed), subtitle = resourceReference( id = R.string.swapping_permission_subheader, formatArgs = wrappedList(fromTokenSymbol), From 0f2b4464ef9fa022ec806901732ccda2ada32ebc Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Dec 2023 21:37:28 +0200 Subject: [PATCH 3/5] Updated on 2026-08-14 --- .../com/tangem/feature/swap/ui/StateBuilder.kt | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) 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 f2adfb4a9d..472810b335 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 @@ -450,10 +450,14 @@ internal class StateBuilder( iconResId = R.drawable.ic_alert_circle_24, ), ) - is DataError.UnknownError -> SwapWarning.GeneralWarning( + else -> SwapWarning.GeneralWarning( notificationConfig = NotificationConfig( title = resourceReference(R.string.common_error), - subtitle = resourceReference(R.string.swapping_generic_error), + subtitle = if (dataError.code == DataError.UnknownError.code){ + resourceReference(R.string.swapping_generic_error) + } else { + resourceReference(R.string.generic_error_code, wrappedList(dataError.code.toString())) + }, iconResId = R.drawable.img_attention_20, buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( text = resourceReference(R.string.warning_button_refresh), @@ -461,13 +465,6 @@ internal class StateBuilder( ), ), ) - else -> SwapWarning.GeneralWarning( - notificationConfig = NotificationConfig( - title = resourceReference(R.string.common_error), - subtitle = resourceReference(R.string.generic_error_code, wrappedList(dataError.code.toString())), - iconResId = R.drawable.img_attention_20, - ), - ) } } From 38b64433c05062f7c0121c75b6c2a4f7f692967b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 11 Dec 2023 21:40:15 +0200 Subject: [PATCH 4/5] Updated on 2026-08-14 --- .../src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 472810b335..38e2ac9240 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 @@ -453,7 +453,7 @@ internal class StateBuilder( else -> SwapWarning.GeneralWarning( notificationConfig = NotificationConfig( title = resourceReference(R.string.common_error), - subtitle = if (dataError.code == DataError.UnknownError.code){ + subtitle = if (dataError.code == DataError.UnknownError.code) { resourceReference(R.string.swapping_generic_error) } else { resourceReference(R.string.generic_error_code, wrappedList(dataError.code.toString())) From c9fee330ef024560d5ea6fd6f5ce488f432194f0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 12 Dec 2023 11:56:07 +0200 Subject: [PATCH 5/5] Updated on 2026-08-14 --- .../main/java/com/tangem/feature/swap/ui/StateBuilder.kt | 8 ++++++-- .../com/tangem/feature/swap/viewmodels/SwapViewModel.kt | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) 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 38e2ac9240..2837f48f2a 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 @@ -452,8 +452,12 @@ internal class StateBuilder( ) else -> SwapWarning.GeneralWarning( notificationConfig = NotificationConfig( - title = resourceReference(R.string.common_error), - subtitle = if (dataError.code == DataError.UnknownError.code) { + title = if (dataError is DataError.UnknownError) { + resourceReference(R.string.common_error) + } else { + resourceReference(R.string.warning_express_refresh_required_title) + }, + subtitle = if (dataError is DataError.UnknownError) { resourceReference(R.string.swapping_generic_error) } else { resourceReference(R.string.generic_error_code, wrappedList(dataError.code.toString())) 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 ac172210c3..b17c398d2e 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 @@ -346,9 +346,7 @@ internal class SwapViewModel @Inject constructor( ) } is SwapState.SwapError -> { - if (state.error is DataError.UnknownError) { - singleTaskScheduler.cancelTask() - } + singleTaskScheduler.cancelTask() uiState = stateBuilder.createQuotesErrorState( uiStateHolder = uiState, swapProvider = provider,