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..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 @@ -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..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 @@ -261,6 +262,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 ...", @@ -373,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 6881df60df..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 @@ -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 } @@ -430,10 +450,18 @@ 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), + 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())) + }, iconResId = R.drawable.img_attention_20, buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( text = resourceReference(R.string.warning_button_refresh), @@ -441,13 +469,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, - ), - ) } } @@ -956,7 +977,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), 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() 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,