Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-12 14:13:17 +03:00
commit 8b740e170b
11 changed files with 68 additions and 32 deletions

View file

@ -647,6 +647,8 @@
<string name="warning_developer_card_title">Не для пользователя!</string>
<string name="warning_existential_deposit_message">Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены.</string>
<string name="warning_existential_deposit_title">Для работы с сетью необходим депозит</string>
<string name="warning_express_active_transaction_message">Обмен будет доступен после завершения %s транзакции</string>
<string name="warning_express_active_transaction_title">У вас есть активная транзакция</string>
<string name="warning_express_no_exchangeable_coins_description">У вас в списке нет монет доступных для обмена с %s</string>
<string name="warning_express_no_exchangeable_coins_title">Нет доступных токенов для обмена</string>
<string name="warning_express_refresh_required_title">Cервис временно недоступен</string>

View file

@ -228,7 +228,7 @@
<string name="express_provider_best_rate">Best rate</string>
<string name="express_provider_min_amount">Available from %s</string>
<string name="express_provider_not_available">Unavailable for this pair</string>
<string name="express_provider_permission_needed">Permission Needed</string>
<string name="express_provider_permission_needed">Permission Required</string>
<string name="feedback_data_collection_message">The following information is optional. You can erase it if you don\'t want to share it.</string>
<string name="feedback_preface_rate_negative">Tell us what functions you are missing, and we will try to help you.</string>
<string name="feedback_preface_scan_failed">Please tell us what card do you have</string>
@ -677,6 +677,8 @@
<string name="warning_developer_card_title">Not for users!</string>
<string name="warning_existential_deposit_message">%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.</string>
<string name="warning_existential_deposit_title">Network requires Existential Deposit</string>
<string name="warning_express_active_transaction_message">Swap will be available after the %s transaction is complete</string>
<string name="warning_express_active_transaction_title">You have active transaction</string>
<string name="warning_express_no_exchangeable_coins_description">You do not have any %s exchangeable coins in your list</string>
<string name="warning_express_no_exchangeable_coins_title">No available tokens to swap</string>
<string name="warning_express_not_enough_fee_for_token_tx_description">To make a transaction you need to deposit some %1$s %2$s</string>

View file

@ -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)

View file

@ -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

View file

@ -14,6 +14,7 @@ data class PreparedSwapConfigState(
val isAllowedToSpend: Boolean,
val isBalanceEnough: Boolean,
val isFeeEnough: Boolean,
val hasOutgoingTransaction: Boolean,
val includeFeeInAmount: IncludeFeeInAmount,
)

View file

@ -19,6 +19,7 @@ sealed interface SwapState {
isAllowedToSpend = false,
isBalanceEnough = false,
isFeeEnough = false,
hasOutgoingTransaction = false,
includeFeeInAmount = IncludeFeeInAmount.Excluded,
),
val permissionState: PermissionDataState = PermissionDataState.Empty,

View file

@ -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 {

View file

@ -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),

View file

@ -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),

View file

@ -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<SwapWarning>) {
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()

View file

@ -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,