From 7f71ef182eb09c1a920e27cefd39840abda59749 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Nov 2023 20:42:03 +0300 Subject: [PATCH 1/2] Updated on 2026-08-14 --- .../feature/swap/domain/SwapInteractor.kt | 1 - .../feature/swap/domain/SwapInteractorImpl.kt | 50 ++++---- .../swap/domain/di/SwapDomainModule.kt | 5 +- .../feature/swap/models/SwapStateHolder.kt | 2 + .../tangem/feature/swap/models/UiActions.kt | 1 + .../swap/models/states/ProviderState.kt | 5 +- .../swap/ui/ChooseProviderBottomSheet.kt | 4 +- .../tangem/feature/swap/ui/ProviderItem.kt | 40 ++++-- .../tangem/feature/swap/ui/StateBuilder.kt | 119 ++++++++++++++++-- .../feature/swap/ui/SwapScreenContent.kt | 14 ++- .../feature/swap/viewmodels/SwapViewModel.kt | 80 ++++++++++-- 11 files changed, 260 insertions(+), 61 deletions(-) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index aa76195519..6f550682f4 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -4,7 +4,6 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWallet -import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.swap.domain.models.ui.* 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 15bcdad9af..3907116097 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 @@ -1,7 +1,6 @@ package com.tangem.feature.swap.domain import arrow.core.getOrElse -import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase @@ -384,7 +383,7 @@ internal class SwapInteractorImpl @Inject constructor( amount = amount, fee = fee, providerId = swapProvider.providerId, - userWalletId = requireNotNull(getSelectedWallet()).walletId + userWalletId = requireNotNull(getSelectedWallet()).walletId, ) } ExchangeProviderType.DEX -> { @@ -485,7 +484,7 @@ internal class SwapInteractorImpl @Inject constructor( amount: SwapAmount, fee: TxFee, providerId: String, - userWalletId: UserWalletId + userWalletId: UserWalletId, ): TxState { val exchangeData = repository.getExchangeData( fromContractAddress = currencyToSend.currency.getContractAddress(), @@ -511,37 +510,38 @@ internal class SwapInteractorImpl @Inject constructor( val result = sendTransactionUseCase( requireNotNull(txData), userWallet = requireNotNull(getSelectedWallet()), - network = currencyToSend.currency.network + network = currencyToSend.currency.network, ) - return result.fold(ifLeft = { - when(it){ + when (it) { is SendTransactionError.NetworkError -> TxState.NetworkError is SendTransactionError.DataError -> TxState.BlockchainError SendTransactionError.DemoCardError -> TxState.UnknownError else -> TxState.UnknownError } - }, ifRight = { - TxState.TxSent( - fromAmount = amountFormatter.formatSwapAmountToUI( - amount, - currencyToSend.currency.symbol, - ), - toAmount = amountFormatter.formatSwapAmountToUI( - exchangeData.dataModel.toTokenAmount, - currencyToGet.currency.symbol, - ), - txAddress = userWalletManager.getLastTransactionHash(currencyToSend.currency.network.backendId, derivationPath) ?: "", - ) - TxState.TxSent( - txAddress = userWalletManager.getLastTransactionHash( - networkId = currencyToSend.currency.network.backendId, - derivationPath = derivationPath - ) ?: "" - ) - }) + TxState.TxSent( + fromAmount = amountFormatter.formatSwapAmountToUI( + amount, + currencyToSend.currency.symbol, + ), + toAmount = amountFormatter.formatSwapAmountToUI( + exchangeData.dataModel.toTokenAmount, + currencyToGet.currency.symbol, + ), + txAddress = userWalletManager.getLastTransactionHash( + currencyToSend.currency.network.backendId, + derivationPath, + ) ?: "", + ) + TxState.TxSent( + txAddress = userWalletManager.getLastTransactionHash( + networkId = currencyToSend.currency.network.backendId, + derivationPath = derivationPath, + ) ?: "", + ) + },) } @Deprecated("used in old swap mechanism") diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index 9c06a31f85..16fb6a7285 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -114,7 +114,7 @@ class SwapDomainModule { @SwapScope @Provides - fun provideDemoCardUseCase() : IsDemoCardUseCase { + fun provideDemoCardUseCase(): IsDemoCardUseCase { return IsDemoCardUseCase(config = DemoConfig()) } @@ -129,10 +129,9 @@ class SwapDomainModule { return SendTransactionUseCase( isDemoCardUseCase = isDemoCardUseCase, cardSdkConfigRepository = cardSdkConfigRepository, - walletManagersFacade = walletManagersFacade + walletManagersFacade = walletManagersFacade, ) } - } @Qualifier 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 61f1089d60..37ef409ce5 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 @@ -121,6 +121,8 @@ sealed interface SwapWarning { * @property priceImpact in format = 10 (means 10%) */ data class HighPriceImpact(val priceImpact: Int, val notificationConfig: NotificationConfig) : SwapWarning + data class TooSmallAmountWarning(val notificationConfig: NotificationConfig) : SwapWarning + data class UnableToCoverFeeWarning(val notificationConfig: NotificationConfig) : SwapWarning } enum class GenericWarningType { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt index 76844c8cd2..a4fa64ab24 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/UiActions.kt @@ -19,4 +19,5 @@ data class UiActions( val onSelectFeeType: (TxFee) -> Unit, val onProviderClick: (String) -> Unit, val onProviderSelect: (String) -> Unit, + val onBuyClick: () -> Unit, ) \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt index 78feaf99a9..2d1d66ef31 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/states/ProviderState.kt @@ -1,5 +1,7 @@ package com.tangem.feature.swap.models.states +import com.tangem.core.ui.extensions.TextReference + sealed class ProviderState { abstract val onProviderClick: ((String) -> Unit)? @@ -32,7 +34,8 @@ sealed class ProviderState { val name: String, val type: String, val iconUrl: String, - val alertText: String, + val alertText: TextReference, + val selectionType: SelectionType, override val onProviderClick: ((String) -> Unit)? = null, ) : ProviderState() 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 6d2bab0f4f..c0743867bd 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 @@ -14,6 +14,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig import com.tangem.feature.swap.models.states.ProviderState @@ -99,7 +100,8 @@ private fun ChooseProviderBottomSheet_Preview() { name = "1inch", type = "DEX", iconUrl = "", - alertText = "Unavailable", + selectionType = ProviderState.SelectionType.SELECT, + alertText = stringReference("Unavailable"), ), ) TangemTheme(isDark = false) { 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 5d75293da0..8e51119147 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 @@ -13,12 +13,15 @@ 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.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.core.ui.R import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SpacerH24 +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.states.ProviderState @@ -64,6 +67,7 @@ fun ProviderItem(state: ProviderState, modifier: Modifier = Modifier, isSelected ProviderUnavailableState( state = state, modifier = modifier, + isSelected = isSelected, ) } is ProviderState.Empty -> { @@ -127,31 +131,42 @@ private fun ProviderContentState( } } Row( - modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), + modifier = Modifier.padding( + top = TangemTheme.dimens.spacing8, + end = TangemTheme.dimens.spacing56, + ), ) { Text( text = state.rate, style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, + overflow = TextOverflow.Ellipsis, + maxLines = 1, ) if (state.percentLowerThenBest != null) { Text( - text = "${state.percentLowerThenBest}%", // todo add to strings + text = "${state.percentLowerThenBest}%", style = TangemTheme.typography.body2, color = TangemTheme.colors.text.warning, modifier = Modifier.padding(start = TangemTheme.dimens.spacing4), + overflow = TextOverflow.Ellipsis, + maxLines = 1, ) } } } } - ProviderChevron(state = state, isSelected = isSelected) + ProviderChevron(selectionType = state.selectionType, isSelected = isSelected) } } @Composable -private fun ProviderUnavailableState(state: ProviderState.Unavailable, modifier: Modifier = Modifier) { +private fun ProviderUnavailableState( + state: ProviderState.Unavailable, + isSelected: Boolean, + modifier: Modifier = Modifier, +) { Box(modifier = modifier.fillMaxWidth()) { Row { val (alpha, colorFilter) = GRAY_SCALE_ALPHA to GrayscaleColorFilter @@ -195,13 +210,15 @@ private fun ProviderUnavailableState(state: ProviderState.Unavailable, modifier: ) } Text( - text = state.alertText, + text = state.alertText.resolveReference(), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, modifier = Modifier.padding(top = TangemTheme.dimens.spacing8), ) } } + + ProviderChevron(selectionType = state.selectionType, isSelected = isSelected) } } @@ -247,8 +264,8 @@ private fun ProviderLoadingState(modifier: Modifier = Modifier) { } @Composable -private fun BoxScope.ProviderChevron(state: ProviderState.Content, isSelected: Boolean) { - when (state.selectionType) { +private fun BoxScope.ProviderChevron(selectionType: ProviderState.SelectionType, isSelected: Boolean) { + when (selectionType) { ProviderState.SelectionType.NONE -> { /* no-op */ } @@ -396,7 +413,8 @@ private fun ProviderItem_Unavailable_Preview() { name = "1inch", type = "DEX", iconUrl = "", - alertText = "Unavailable", + selectionType = ProviderState.SelectionType.SELECT, + alertText = stringReference("Unavailable"), ) Column { TangemTheme(isDark = false) { @@ -408,5 +426,11 @@ private fun ProviderItem_Unavailable_Preview() { TangemTheme(isDark = true) { ProviderItemBlock(state = state) } + + SpacerH24() + + TangemTheme(isDark = true) { + ProviderItem(state = state, isSelected = true) + } } } \ No newline at end of file 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 054e89a4bc..dd313d462a 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 @@ -6,10 +6,7 @@ import com.tangem.common.Provider import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrency @@ -125,7 +122,7 @@ internal class StateBuilder( notificationConfig = NotificationConfig( title = stringReference("No tokens"), subtitle = stringReference("Swap tokens not available"), - iconResId = R.drawable.ic_alert_24, + iconResId = R.drawable.img_attention_20, ), ), ), @@ -213,6 +210,18 @@ internal class StateBuilder( ), ) } + if (quoteModel.permissionState !is PermissionDataState.PermissionReadyForRequest && + !quoteModel.preparedSwapConfigState.isFeeEnough + ) { + warnings.add( + SwapWarning.UnableToCoverFeeWarning( + createUnableToCoverFeeNotificationConfig( + fromToken = fromToken, + onBuyClick = actions.onBuyClick, + ), + ), + ) + } if (!quoteModel.preparedSwapConfigState.isBalanceEnough) { warnings.add(SwapWarning.InsufficientFunds) } @@ -605,14 +614,23 @@ internal class StateBuilder( fun showSelectProviderBottomSheet( uiState: SwapStateHolder, selectedProviderId: String, + bestRatedProviderId: String, providersStates: Map, + unavailableProviders: List, onDismiss: () -> Unit, ): SwapStateHolder { + val availableProvidersStates = providersStates.entries.mapNotNull { + it.convertToProviderState(bestRatedProviderId, actions.onProviderSelect) + } + val unavailableProviderStates = unavailableProviders.map { + it.convertToUnavailableProviderState( + alertText = stringReference("Unavailable for pair"), + selectionType = ProviderState.SelectionType.NONE, + ) + } val config = ChooseProviderBottomSheetConfig( selectedProviderId = selectedProviderId, - providers = providersStates.entries - .mapNotNull { it.convertToProviderState(actions.onProviderSelect) } - .toImmutableList(), + providers = (availableProvidersStates + unavailableProviderStates).toImmutableList(), ) return uiState.copy( bottomSheetConfig = TangemBottomSheetConfig( @@ -703,21 +721,27 @@ internal class StateBuilder( } private fun Map.Entry.convertToProviderState( + bestRatedProviderId: String, onProviderSelect: (String) -> Unit, ): ProviderState? { val provider = this.key return when (val state = this.value) { is SwapState.EmptyAmountState -> null - is SwapState.QuotesLoadedState -> provider.convertToContentClickableProviderState( - fromTokenInfo = state.fromTokenInfo, - toTokenInfo = state.toTokenInfo, + is SwapState.QuotesLoadedState -> provider.convertToContentSelectableProviderState( + isBestRate = provider.providerId == bestRatedProviderId, + state = state, onProviderClick = onProviderSelect, selectionType = ProviderState.SelectionType.SELECT, ) - is SwapState.SwapError -> null + is SwapState.SwapError -> provider.convertToUnavailableProviderState( + alertText = stringReference("Available from"), + selectionType = ProviderState.SelectionType.NONE, + onProviderClick = onProviderSelect, + ) } } + // region warnings private fun createPermissionNotificationConfig(fromTokenSymbol: String): NotificationConfig { return NotificationConfig( title = resourceReference(R.string.swapping_permission_header), @@ -737,6 +761,25 @@ internal class StateBuilder( ) } + private fun createUnableToCoverFeeNotificationConfig( + fromToken: CryptoCurrency, + onBuyClick: () -> Unit, + ): NotificationConfig { + // todo add strings + return NotificationConfig( + title = stringReference("Unable to cover ${fromToken.name} fee"), + subtitle = stringReference( + "To make transaction you need to deposit some ${fromToken.name} ${fromToken.symbol}", + ), + iconResId = fromToken.networkIconResId, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = stringReference("Buy ${fromToken.name}"), + onClick = onBuyClick, + ), + ) + } + // end region + private fun getShortAddressValue(fullAddress: String): String { check(fullAddress.length > ADDRESS_MIN_LENGTH) { "Invalid address" } val firstAddressPart = fullAddress.substring(startIndex = 0, endIndex = ADDRESS_FIRST_PART_LENGTH) @@ -774,6 +817,58 @@ internal class StateBuilder( ) } + private fun SwapProvider.convertToContentSelectableProviderState( + isBestRate: Boolean, + state: SwapState.QuotesLoadedState, + selectionType: ProviderState.SelectionType, + onProviderClick: (String) -> Unit, + ): ProviderState { + val fromTokenInfo = state.fromTokenInfo + val toTokenInfo = state.toTokenInfo + val rate = toTokenInfo.tokenAmount.value.divide( + fromTokenInfo.tokenAmount.value, + toTokenInfo.cryptoCurrencyStatus.currency.decimals, + RoundingMode.HALF_UP, + ) + val fromCurrencySymbol = fromTokenInfo.cryptoCurrencyStatus.currency.symbol + val toCurrencySymbol = toTokenInfo.cryptoCurrencyStatus.currency.symbol + val rateString = "1 $fromCurrencySymbol ≈ $rate $toCurrencySymbol" + val additionalBadge = if (state.permissionState is PermissionDataState.PermissionReadyForRequest) { + ProviderState.AdditionalBadge.PermissionRequired + } else if (isBestRate) { + ProviderState.AdditionalBadge.BestTrade + } else { + ProviderState.AdditionalBadge.Empty + } + return ProviderState.Content( + id = this.providerId, + name = this.name, + iconUrl = this.imageLarge, + type = this.type.toString(), + rate = rateString, + additionalBadge = additionalBadge, + selectionType = selectionType, + percentLowerThenBest = null, + onProviderClick = onProviderClick, + ) + } + + private fun SwapProvider.convertToUnavailableProviderState( + alertText: TextReference, + selectionType: ProviderState.SelectionType, + onProviderClick: ((String) -> Unit)? = null, + ): ProviderState { + return ProviderState.Unavailable( + id = this.providerId, + name = this.name, + iconUrl = this.imageLarge, + type = this.type.toString(), + selectionType = selectionType, + alertText = alertText, + onProviderClick = onProviderClick, + ) + } + private fun CryptoCurrencyStatus.getFormattedAmount(): String { val amount = value.amount ?: return UNKNOWN_AMOUNT_SIGN 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 bf382cbc41..95fd4ce997 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 @@ -256,6 +256,7 @@ private fun SwapWarnings(warnings: List) { is SwapWarning.HighPriceImpact -> { Notification( config = warning.notificationConfig, + iconTint = TangemTheme.colors.icon.warning, ) } is SwapWarning.PermissionNeeded -> { @@ -282,6 +283,17 @@ private fun SwapWarnings(warnings: List) { config = warning.notificationConfig, ) } + is SwapWarning.TooSmallAmountWarning -> { + Notification( + config = warning.notificationConfig, + iconTint = TangemTheme.colors.icon.warning, + ) + } + is SwapWarning.UnableToCoverFeeWarning -> { + Notification( + config = warning.notificationConfig, + ) + } else -> {} } SpacerH8() @@ -379,7 +391,7 @@ private val state = SwapStateHolder( notificationConfig = NotificationConfig( title = stringReference("No tokens"), subtitle = stringReference("Swap tokens not available"), - iconResId = R.drawable.ic_alert_24, + iconResId = R.drawable.img_attention_20, ), ), ), 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 d3121006ab..21c8322d64 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 @@ -17,7 +17,6 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.swap.analytics.SwapEvents import com.tangem.feature.swap.domain.BlockchainInteractor import com.tangem.feature.swap.domain.SwapInteractor -import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.PermissionOptions import com.tangem.feature.swap.domain.models.domain.SwapDataModel import com.tangem.feature.swap.domain.models.domain.SwapProvider @@ -31,11 +30,14 @@ import com.tangem.feature.swap.ui.StateBuilder import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.Debouncer import com.tangem.utils.coroutines.runCatching +import com.tangem.utils.isNullOrZero import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber +import java.math.BigDecimal +import java.math.RoundingMode import java.text.DecimalFormat import java.text.NumberFormat import java.util.Locale @@ -287,29 +289,62 @@ internal class SwapViewModel @Inject constructor( } is SwapState.SwapError -> { Timber.e("SwapError when loading quotes ${state.error}") + // todo handle when change token and error uiState = stateBuilder.mapError(uiState, state.error) { startLoadingQuotesFromLastState() } } } } private fun updateLoadedQuotes(state: Map): Pair { - val selectedSwapProvider = selectProvider(state) + val nonEmptyStates = state.filter { it.value !is SwapState.EmptyAmountState } + val selectedSwapProvider = if (nonEmptyStates.isNotEmpty()) { + selectProvider(state) + } else { + null + } dataState = dataState.copy( selectedProvider = selectedSwapProvider, lastLoadedSwapStates = state, ) - return state.entries.first { it.key == selectedSwapProvider }.toPair() + selectedSwapProvider?.let { + return state.entries.first { it.key == selectedSwapProvider }.toPair() + } + return state.entries.first().toPair() } private fun selectProvider(state: Map): SwapProvider { - val currentSelected = dataState.selectedProvider - return if (currentSelected != null && state.keys.contains(currentSelected)) { - currentSelected + val stateSuccess = state + .filter { it.value is SwapState.QuotesLoadedState } + .mapValues { it.value as SwapState.QuotesLoadedState } + return if (stateSuccess.isNotEmpty()) { + val currentSelected = dataState.selectedProvider + if (currentSelected != null && state.keys.contains(currentSelected)) { + currentSelected + } else { + findBestQuoteProvider(stateSuccess) ?: stateSuccess.keys.first() + } } else { state.keys.first() } } + private fun findBestQuoteProvider(state: Map): SwapProvider? { + // finding best quotes + return state.mapValues { + if (!it.value.fromTokenInfo.amountFiat.isNullOrZero() && + !it.value.toTokenInfo.amountFiat.isNullOrZero() + ) { + it.value.fromTokenInfo.amountFiat.divide( + it.value.toTokenInfo.amountFiat, + it.value.toTokenInfo.cryptoCurrencyStatus.currency.decimals, + RoundingMode.HALF_UP, + ) + } else { + BigDecimal.ZERO + } + }.minByOrNull { it.value }?.key + } + private fun fillLoadedDataState( state: SwapState.QuotesLoadedState, permissionState: PermissionDataState, @@ -478,7 +513,6 @@ internal class SwapViewModel @Inject constructor( private fun onTokenSelect(id: String) { val tokens = dataState.tokensDataState ?: return - val foundToken = if (isOrderReversed) { tokens.fromGroup.available.firstOrNull { it.currencyStatus.currency.id.value == id @@ -505,6 +539,7 @@ internal class SwapViewModel @Inject constructor( dataState = dataState.copy( fromCryptoCurrency = fromToken, toCryptoCurrency = toToken, + selectedProvider = null, ) startLoadingQuotes( fromToken = fromToken, @@ -675,10 +710,17 @@ internal class SwapViewModel @Inject constructor( setupLoadedState(selectedProvider, updatedState, fromToken) } }, - onProviderClick = { + onProviderClick = { providerId -> + val states = dataState.lastLoadedSwapStates + .filter { it.value is SwapState.QuotesLoadedState } + .mapValues { it.value as SwapState.QuotesLoadedState } + val bestRatedProviderId = findBestQuoteProvider(states)?.providerId ?: providerId + val unavailableProviders = getUnavailableProvidersFor(dataState.lastLoadedSwapStates) uiState = stateBuilder.showSelectProviderBottomSheet( uiState = uiState, - selectedProviderId = it, + selectedProviderId = providerId, + bestRatedProviderId = bestRatedProviderId, + unavailableProviders = unavailableProviders, providersStates = dataState.lastLoadedSwapStates, ) { uiState = stateBuilder.dismissBottomSheet(uiState) } }, @@ -695,6 +737,7 @@ internal class SwapViewModel @Inject constructor( ) } }, + onBuyClick = {}, ) } @@ -736,6 +779,25 @@ internal class SwapViewModel @Inject constructor( return groupToFind.available.find { idToFind == it.currencyStatus.currency.id.value }?.providers ?: emptyList() } + private fun getAllProviders(): List { + dataState.tokensDataState?.let { data -> + val allProviders = + data.fromGroup.available.flatMap { it.providers } + data.toGroup.available.flatMap { it.providers } + return allProviders.distinct() + } ?: return emptyList() + } + + private fun getUnavailableProvidersFor(state: Map): List { + return getAllProviders() + .mapNotNull { + if (state.containsKey(it)) { + null + } else { + it + } + } + } + companion object { private const val loggingTag = "SwapViewModel" private const val INITIAL_AMOUNT = "" From ed053eb38f537bb8e4bd4f71e71ca853a9c2da71 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 28 Nov 2023 21:34:40 +0300 Subject: [PATCH 2/2] Updated on 2026-08-14 --- core/res/src/main/res/values-ru/strings.xml | 17 +++++-- core/res/src/main/res/values/strings.xml | 47 ++++++++++++------- .../data/tokens/utils/NetworkOperations.kt | 1 + .../com/tangem/domain/tokens/model/Network.kt | 1 + .../tangem/domain/tokens/mock/MockNetworks.kt | 6 +++ .../state/fields/SendAmountFieldConverter.kt | 2 +- .../feature/swap/ui/ChooseFeeBottomSheet.kt | 7 ++- .../swap/ui/ChooseProviderBottomSheet.kt | 6 ++- .../tangem/feature/swap/ui/ProviderItem.kt | 1 + .../tangem/feature/swap/ui/StateBuilder.kt | 37 +++++++++------ 10 files changed, 83 insertions(+), 42 deletions(-) diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 77fd07304f..194da3dfb2 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -66,6 +66,7 @@ Одобрение Внимание Баланс: %s + Баланс биометрическую аутентификацию биометрией Купить @@ -85,9 +86,11 @@ Включено Ошибка Обменять - Посмотреть историю транзакций Обозреватель + Посмотреть историю + Посмотреть историю транзакций Обозреватель + Комиссия Сетевые комиссии за транзакции используются для поддержки безопасности сети, поощрения валидаторов, выделения ресурсов и определения приоритета транзакции. Свое Быстро @@ -126,7 +129,6 @@ Отправить Успешно Обмен - Комиссия условия участия Ошибка транзакции Транзакции @@ -192,14 +194,18 @@ Мои токены У вас нет добавленных токенов. Добавьте токены для обмена Недоступен для обмена с %s + Статус Провайдеры проводят транзакции, обеспечивая плавный и эффективный обмен токенами Выберите провайдера Чтобы узнать причину, посетите сайт провайдера Операция не выполнена провайдером Посетите сайт провайдера для проверки Провайдер: требуется верификация + Получение наилучших курсов... Провайдер Лучший курс + Доступно с %s + Недоступно для этой пары Требуется разрешение Информация ниже не является обязательной. Вы можете стереть её, если хотите. Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. @@ -273,7 +279,6 @@ Введенные коды доступа не совпадают Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить? Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас. - Баланс Добавить резервную карту Сканировать карту #%d Создать резервную копию @@ -634,12 +639,18 @@ Нравится Понятно! Очень круто! + Обновить Вы находитесь в режиме демо Демо режим включен Отсканированная вами карта является картой разработчика. Не используйте ее для создания своего кошелька. Не для пользователя! Cеть %1$s использует концепцию экзистенциального депозита. Если баланс вашего счета будет ниже %2$s, то он будет деактивирован, а средства на счете уничтожены. Для работы с сетью необходим депозит + У вас в списке нет монет доступных для обмена с %s + Нет доступных токенов для обмена + Cервис временно недоступен + Пожалуйста, измените сумму для обмена + Сумма для обмена должна быть не менее %s Возможно, данная карта - образец или подделка Ошибка проверки подлинности На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index ffc1858e33..8d4ff0b882 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -64,6 +64,7 @@ Approval Attention Balance: %s + Balance biometric authentication biometrics Buy @@ -84,8 +85,10 @@ Error Exchange Explore + Explore history Explore transaction history Explorer + Fee Network transaction fees are used to support network security, incentivize validators, allocate resources, and determine transaction priority. Custom Fast @@ -125,7 +128,6 @@ Submit Success Swap - Fee terms and conditions Transaction failed Transactions @@ -186,14 +188,16 @@ 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 + You receive + You send My tokens - You don\'t have any added tokens yet. Add tokens via Market to swap - Unavailable for swap from %s - Providers facilitate transactions, ensuring smooth and efficient token exchanges - Choose Provider + You haven\'t added any tokens yet. Add tokens via Market to swap + Cannot be swapped for %s + Status + Providers facilitate transactions, ensuring smooth and efficient token swaps + Choose provider Estimated amount + Exchange by %s Visit provider’s website to see why Operation failed by provider Visit provider’s website for verification @@ -216,7 +220,8 @@ Go to provider Provider Best rate - Exchange by %s + Available from %s + Unavailable for this pair Permission Needed 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. @@ -291,7 +296,6 @@ Entered access code didn\'t match the initial access code You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process? The backup process is partly complete. You can\'t exit it now. - Balance Add a backup card Scan the card #%d Backup now @@ -437,7 +441,6 @@ Invalid Memo. It won\'t be added to the transaction. Tag Memo - Insufficient funds for transfer Include fee Low Normal @@ -513,7 +516,7 @@ Meet Tangem Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services Web 3.0 Compatible - Approvals are considered an industry standard across all decentralized exchanges and protect your wallet from being accessed by a smart contract without your permission. By design, smart contracts can\'t access your tokens unless you approve access from your end. By \"unlocking\" your tokens, you are give permission to the 1inch smart contract to spend your assets. The miners of the network are compensated with a gas fee (paid by you) to record this action on the blockchain. Once permission has been granted you will be able to swap your token. + 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 There was an error. Please try again. @@ -521,25 +524,25 @@ High price impact! Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. Insufficient funds - Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first. + Insufficient funds in your %1$s wallet to cover fees. Top up your %2$s wallet first. Transaction in progress... Waiting Approve Current transaction - The token approval network fee will be charged to confirm that you are the one allowing your token to be used for the exchange. + The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. Give Permission Specify the approve limit for the selected token Amount %s Spender - Your Wallet - To continue you need to allow 1inch smart contracts to use your %s + Your wallet + To continue, grant 1inch smart contracts permission to use your %s Unlimited - View in Explorer - In progress You swap You receive + View in Explorer + In progress Swap - Swap of %s to + Swap %s for Quotes include an additional Tangem commission of %s. This helps us deliver a top-of-the-line product. Other tokens Choose token @@ -661,12 +664,20 @@ Like it Ok, Got it! Really cool! + Refresh You are currently in the Demo mode Demo mode active The card you scanned is a developer card. Do not use it to create your wallet. 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 + 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 + Unable to cover %s fee + Service temporary unavailable + Please change the amount to swap + The amount to swap must be at least %s This card might be a production sample or counterfeit Authenticity check failed Only %s signatures are left on this card. You must withdraw all of your funds. diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt index f5a1bf13df..ae8d8d9373 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/NetworkOperations.kt @@ -26,6 +26,7 @@ internal fun getNetwork( name = blockchain.fullName, isTestnet = blockchain.isTestnet(), derivationPath = getNetworkDerivationPath(blockchain, extraDerivationPath, derivationStyleProvider), + currencySymbol = blockchain.currency, standardType = getNetworkStandardType(blockchain), ) } diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Network.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Network.kt index c57d5beee8..17274cfbe9 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Network.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Network.kt @@ -22,6 +22,7 @@ data class Network( val id: ID, val backendId: String, val name: String, + val currencySymbol: String, val derivationPath: DerivationPath, val isTestnet: Boolean, val standardType: StandardType, diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt index 979479e010..7b61592137 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt @@ -18,6 +18,8 @@ internal object MockNetworks { name = "Network One", isTestnet = false, standardType = Network.StandardType.ERC20, + backendId = "network1", + currencySymbol = "ETH", derivationPath = Network.DerivationPath.None, ) @@ -26,6 +28,8 @@ internal object MockNetworks { name = "Network Two", isTestnet = false, standardType = Network.StandardType.ERC20, + backendId = "network1", + currencySymbol = "ETH", derivationPath = Network.DerivationPath.None, ) @@ -34,6 +38,8 @@ internal object MockNetworks { name = "Network Three", isTestnet = false, standardType = Network.StandardType.ERC20, + backendId = "network1", + currencySymbol = "ETH", derivationPath = Network.DerivationPath.None, ) diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt index 4b027a624d..9ab26bd83d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fields/SendAmountFieldConverter.kt @@ -24,7 +24,7 @@ internal class SendAmountFieldConverter( ), placeholder = TextReference.Str(DEFAULT_VALUE), isError = false, - error = TextReference.Res(R.string.send_insufficient_funds), + error = TextReference.Res(R.string.swapping_insufficient_funds), ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt index 4556f2d785..d0f47a62a2 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/ChooseFeeBottomSheet.kt @@ -7,6 +7,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.SpacerH24 @@ -35,7 +36,7 @@ private fun ChooseFeeBottomSheetContent(content: ChooseFeeBottomSheetConfig) { modifier = Modifier.background(TangemTheme.colors.background.primary), ) { Text( - text = "Choose fee", // todo replace with strings + text = stringResource(R.string.common_fee_selector_title), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.primary1, modifier = Modifier @@ -53,9 +54,7 @@ private fun ChooseFeeBottomSheetContent(content: ChooseFeeBottomSheetConfig) { FeeItemsBlock(content) } Text( - text = "Network transaction fees are small charges paid to support network security, incentivize " + - "validators," + - " allocate resources, and determine transaction priority.", // todo replace with strings + text = stringResource(R.string.common_fee_selector_footer), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, modifier = Modifier 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 c0743867bd..f11e395923 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 @@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet @@ -18,6 +19,7 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig import com.tangem.feature.swap.models.states.ProviderState +import com.tangem.feature.swap.presentation.R import kotlinx.collections.immutable.toImmutableList @Composable @@ -33,7 +35,7 @@ private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetC modifier = Modifier.background(TangemTheme.colors.background.primary), ) { Text( - text = "Choose provider", + text = stringResource(R.string.express_choose_providers_title), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.primary1, modifier = Modifier @@ -41,7 +43,7 @@ private fun ChooseProviderBottomSheetContent(content: ChooseProviderBottomSheetC .align(Alignment.CenterHorizontally), ) Text( - text = "Providers facilitate transactions, ensuring smooth and efficient token exchanges", + text = stringResource(R.string.express_choose_providers_subtitle), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, modifier = Modifier 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 8e51119147..99595d6398 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 @@ -76,6 +76,7 @@ fun ProviderItem(state: ProviderState, modifier: Modifier = Modifier, isSelected } } +@Suppress("LongMethod") @Composable private fun ProviderContentState( state: ProviderState.Content, 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 dd313d462a..43cb364d2c 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 @@ -120,8 +120,11 @@ internal class StateBuilder( warnings = listOf( SwapWarning.NoAvailableTokensToSwap( notificationConfig = NotificationConfig( - title = stringReference("No tokens"), - subtitle = stringReference("Swap tokens not available"), + title = resourceReference(R.string.warning_express_no_exchangeable_coins_title), + subtitle = resourceReference( + id = R.string.warning_express_no_exchangeable_coins_description, + formatArgs = wrappedList(fromToken.currency.name), + ), iconResId = R.drawable.img_attention_20, ), ), @@ -210,8 +213,9 @@ internal class StateBuilder( ), ) } - if (quoteModel.permissionState !is PermissionDataState.PermissionReadyForRequest && - !quoteModel.preparedSwapConfigState.isFeeEnough + if (quoteModel.preparedSwapConfigState.isAllowedToSpend && + !quoteModel.preparedSwapConfigState.isFeeEnough && + quoteModel.preparedSwapConfigState.isBalanceEnough ) { warnings.add( SwapWarning.UnableToCoverFeeWarning( @@ -439,7 +443,7 @@ internal class StateBuilder( return FeeItemState.Content( feeType = feeType, - title = stringReference("Fee"), // todo replace with string + title = resourceReference(R.string.common_fee_label), amountCrypto = fee.feeCryptoFormatted, symbolCrypto = fee.cryptoSymbol, amountFiatFormatted = fee.feeFiatFormatted, @@ -611,6 +615,7 @@ internal class StateBuilder( ) } + @Suppress("LongParameterList") fun showSelectProviderBottomSheet( uiState: SwapStateHolder, selectedProviderId: String, @@ -624,7 +629,7 @@ internal class StateBuilder( } val unavailableProviderStates = unavailableProviders.map { it.convertToUnavailableProviderState( - alertText = stringReference("Unavailable for pair"), + alertText = resourceReference(R.string.express_provider_not_available), selectionType = ProviderState.SelectionType.NONE, ) } @@ -701,7 +706,7 @@ internal class StateBuilder( return listOf( FeeItemState.Content( feeType = this.normalFee.feeType, - title = stringReference("Fee"), // todo replace with string + title = resourceReference(R.string.common_fee_label), amountCrypto = this.normalFee.feeCryptoFormatted, symbolCrypto = this.normalFee.cryptoSymbol, amountFiatFormatted = this.normalFee.feeFiatFormatted, @@ -710,7 +715,7 @@ internal class StateBuilder( ), FeeItemState.Content( feeType = this.priorityFee.feeType, - title = stringReference("Fee"), // todo replace with string + title = resourceReference(R.string.common_fee_label), amountCrypto = this.priorityFee.feeCryptoFormatted, symbolCrypto = this.priorityFee.cryptoSymbol, amountFiatFormatted = this.priorityFee.feeFiatFormatted, @@ -733,8 +738,9 @@ internal class StateBuilder( onProviderClick = onProviderSelect, selectionType = ProviderState.SelectionType.SELECT, ) + // todo handle error is SwapState.SwapError -> provider.convertToUnavailableProviderState( - alertText = stringReference("Available from"), + alertText = resourceReference(R.string.express_provider_min_amount, wrappedList("10")), selectionType = ProviderState.SelectionType.NONE, onProviderClick = onProviderSelect, ) @@ -765,15 +771,18 @@ internal class StateBuilder( fromToken: CryptoCurrency, onBuyClick: () -> Unit, ): NotificationConfig { - // todo add strings return NotificationConfig( - title = stringReference("Unable to cover ${fromToken.name} fee"), - subtitle = stringReference( - "To make transaction you need to deposit some ${fromToken.name} ${fromToken.symbol}", + title = resourceReference( + R.string.warning_express_not_enough_fee_for_token_tx_title, + wrappedList(fromToken.network.name), + ), + subtitle = resourceReference( + R.string.warning_express_not_enough_fee_for_token_tx_description, + wrappedList(fromToken.network.name, fromToken.network.currencySymbol), ), iconResId = fromToken.networkIconResId, buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( - text = stringReference("Buy ${fromToken.name}"), + text = resourceReference(R.string.common_buy_currency, wrappedList(fromToken.name)), onClick = onBuyClick, ), )