Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-07 16:39:48 +05:00
parent d369d87c2f
commit 1d47d83d70
12 changed files with 64 additions and 2 deletions

View file

@ -1268,8 +1268,8 @@
<string name="warning_matic_migration_message">Actualmente, MATIC está migrando a POL. Sin embargo, no se ha fijado ninguna fecha límite y MATIC aún no está obsoleto. Puede seguir usando el token MATIC de forma segura o utilizar intercambios para cambiarlo por POL.</string> <string name="warning_matic_migration_message">Actualmente, MATIC está migrando a POL. Sin embargo, no se ha fijado ninguna fecha límite y MATIC aún no está obsoleto. Puede seguir usando el token MATIC de forma segura o utilizar intercambios para cambiarlo por POL.</string>
<string name="warning_matic_migration_title">Migración de MATIC a POL</string> <string name="warning_matic_migration_title">Migración de MATIC a POL</string>
<plurals name="warning_missing_derivation_message"> <plurals name="warning_missing_derivation_message">
<item quantity="one">Use su tarjeta o anillo para obtener una dirección para la red</item> <item quantity="one">Use su tarjeta o anillo para obtener una dirección para la red %d</item>
<item quantity="other">Use su tarjeta o anillo para obtener direcciónes para la red</item> <item quantity="other">Use su tarjeta o anillo para obtener direcciónes para las redes %d</item>
</plurals> </plurals>
<string name="warning_missing_derivation_title">Faltan algunas direcciones</string> <string name="warning_missing_derivation_title">Faltan algunas direcciones</string>
<string name="warning_network_unreachable_message">La red no está disponible actualmente. Por favor, inténtalo de nuevo más tarde.</string> <string name="warning_network_unreachable_message">La red no está disponible actualmente. Por favor, inténtalo de nuevo más tarde.</string>

View file

@ -400,6 +400,7 @@
<string name="express_provider">Provider</string> <string name="express_provider">Provider</string>
<string name="express_provider_best_rate">Best rate</string> <string name="express_provider_best_rate">Best rate</string>
<string name="express_provider_fca_warning_list">FCA Warning List</string> <string name="express_provider_fca_warning_list">FCA Warning List</string>
<string name="express_provider_in_fca_warning_list">Provider in FCA warning list</string>
<string name="express_provider_max_amount">Available up to %s</string> <string name="express_provider_max_amount">Available up to %s</string>
<string name="express_provider_min_amount">Available from %s</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_not_available">Unavailable for this pair</string>

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M8.003,9.316C7.646,9.316 7.447,9.11 7.44,8.746L7.35,5.007C7.344,4.642 7.612,4.381 7.997,4.381C8.368,4.381 8.656,4.649 8.65,5.014L8.546,8.746C8.54,9.117 8.34,9.316 8.003,9.316ZM8.003,11.619C7.591,11.619 7.234,11.289 7.234,10.883C7.234,10.471 7.584,10.141 8.003,10.141C8.416,10.141 8.766,10.464 8.766,10.883C8.766,11.296 8.409,11.619 8.003,11.619Z"
android:fillColor="#919191"
android:fillType="evenOdd"/>
<path
android:pathData="M8,1.164C4.225,1.164 1.164,4.225 1.164,8C1.164,11.775 4.225,14.836 8,14.836C11.775,14.836 14.836,11.775 14.836,8C14.836,4.225 11.775,1.164 8,1.164ZM2.164,8C2.164,4.777 4.777,2.164 8,2.164C11.223,2.164 13.836,4.777 13.836,8C13.836,11.223 11.223,13.836 8,13.836C4.777,13.836 2.164,11.223 2.164,8Z"
android:fillColor="#919191"
android:fillType="evenOdd"/>
</vector>

View file

@ -48,6 +48,7 @@ internal sealed class SwapAmountUM {
val swapCurrencies: SwapCurrencies, val swapCurrencies: SwapCurrencies,
val swapQuotes: ImmutableList<SwapQuoteUM>, val swapQuotes: ImmutableList<SwapQuoteUM>,
val selectedQuote: SwapQuoteUM, val selectedQuote: SwapQuoteUM,
val showFCAWarning: Boolean,
// extra data // extra data
val appCurrency: AppCurrency?, val appCurrency: AppCurrency?,

View file

@ -24,6 +24,7 @@ import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.swap.models.SwapCurrencies import com.tangem.domain.swap.models.SwapCurrencies
import com.tangem.domain.notifications.ShouldShowNotificationUseCase import com.tangem.domain.notifications.ShouldShowNotificationUseCase
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.domain.swap.models.SwapDirection import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
import com.tangem.domain.swap.models.SwapQuoteModel import com.tangem.domain.swap.models.SwapQuoteModel
@ -150,6 +151,7 @@ internal class SwapAmountModel @Inject constructor(
quoteUM = quoteUM, quoteUM = quoteUM,
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary, secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary, secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
), ),
) )
} }
@ -533,6 +535,7 @@ internal class SwapAmountModel @Inject constructor(
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary, secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary, secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
isSilentReload = isSilentReload, isSilentReload = isSilentReload,
needApplyFcaRestrictions = userCountry.needApplyFCARestrictions(),
), ),
) )

View file

@ -54,6 +54,7 @@ internal class SwapAmountPrimaryReadyStateTransformer(
selectedQuote = SwapQuoteUM.Empty, selectedQuote = SwapQuoteUM.Empty,
appCurrency = appCurrency, appCurrency = appCurrency,
showBestRateAnimation = showBestRateAnimation, showBestRateAnimation = showBestRateAnimation,
showFCAWarning = false,
) )
} }
} }

View file

@ -53,6 +53,7 @@ internal class SwapAmountSecondaryReadyStateTransformer(
selectedQuote = SwapQuoteUM.Empty, selectedQuote = SwapQuoteUM.Empty,
appCurrency = appCurrency, appCurrency = appCurrency,
showBestRateAnimation = showBestRateAnimation, showBestRateAnimation = showBestRateAnimation,
showFCAWarning = false,
) )
} }
} }

View file

@ -11,6 +11,7 @@ import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.calculatePriceImpact import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.calculatePriceImpact
import com.tangem.features.swap.v2.impl.amount.model.converter.SwapAmountErrorConverter import com.tangem.features.swap.v2.impl.amount.model.converter.SwapAmountErrorConverter
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.common.isRestrictedByFCA
import com.tangem.utils.extensions.orZero import com.tangem.utils.extensions.orZero
import com.tangem.utils.transformer.Transformer import com.tangem.utils.transformer.Transformer
@ -18,6 +19,7 @@ internal class SwapAmountSelectQuoteTransformer(
private val quoteUM: SwapQuoteUM, private val quoteUM: SwapQuoteUM,
private val secondaryMaximumAmountBoundary: EnterAmountBoundary?, private val secondaryMaximumAmountBoundary: EnterAmountBoundary?,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary?, private val secondaryMinimumAmountBoundary: EnterAmountBoundary?,
private val needApplyFCARestrictions: Boolean,
) : Transformer<SwapAmountUM> { ) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM { override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState if (prevState !is SwapAmountUM.Content) return prevState
@ -29,6 +31,7 @@ internal class SwapAmountSelectQuoteTransformer(
return prevState.copy( return prevState.copy(
isPrimaryButtonEnabled = quoteUM is SwapQuoteUM.Content, isPrimaryButtonEnabled = quoteUM is SwapQuoteUM.Content,
selectedQuote = quoteUM, selectedQuote = quoteUM,
showFCAWarning = needApplyFCARestrictions && quoteUM.provider?.isRestrictedByFCA() == true,
primaryAmount = if (prevState.selectedAmountType == SwapAmountType.From) { primaryAmount = if (prevState.selectedAmountType == SwapAmountType.From) {
val swapAmountField = prevState.primaryAmount as? SwapAmountFieldUM.Content val swapAmountField = prevState.primaryAmount as? SwapAmountFieldUM.Content
val amountField = swapAmountField?.amountField as? AmountState.Data val amountField = swapAmountField?.amountField as? AmountState.Data

View file

@ -8,6 +8,7 @@ import com.tangem.domain.express.models.ExpressError
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM.Content.DifferencePercent import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM.Content.DifferencePercent
import com.tangem.features.swap.v2.impl.common.isRestrictedByFCA
import com.tangem.utils.StringsSigns import com.tangem.utils.StringsSigns
import com.tangem.utils.extensions.isPositive import com.tangem.utils.extensions.isPositive
import com.tangem.utils.transformer.Transformer import com.tangem.utils.transformer.Transformer
@ -20,6 +21,7 @@ internal class SwapAmountSetQuotesTransformer(
private val secondaryMaximumAmountBoundary: EnterAmountBoundary?, private val secondaryMaximumAmountBoundary: EnterAmountBoundary?,
private val secondaryMinimumAmountBoundary: EnterAmountBoundary?, private val secondaryMinimumAmountBoundary: EnterAmountBoundary?,
private val isSilentReload: Boolean, private val isSilentReload: Boolean,
private val needApplyFcaRestrictions: Boolean,
) : Transformer<SwapAmountUM> { ) : Transformer<SwapAmountUM> {
override fun transform(prevState: SwapAmountUM): SwapAmountUM { override fun transform(prevState: SwapAmountUM): SwapAmountUM {
if (prevState !is SwapAmountUM.Content) return prevState if (prevState !is SwapAmountUM.Content) return prevState
@ -36,6 +38,8 @@ internal class SwapAmountSetQuotesTransformer(
quoteUM = selectedQuote, quoteUM = selectedQuote,
secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary, secondaryMaximumAmountBoundary = secondaryMaximumAmountBoundary,
secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary, secondaryMinimumAmountBoundary = secondaryMinimumAmountBoundary,
needApplyFCARestrictions = needApplyFcaRestrictions &&
selectedQuote.provider?.isRestrictedByFCA() == true,
) )
val updatedState = selectQuoteTransformer.transform(prevState = prevState) val updatedState = selectQuoteTransformer.transform(prevState = prevState)

View file

@ -114,6 +114,7 @@ internal fun SwapAmountBlockContent(
start.linkTo(parent.start) start.linkTo(parent.start)
end.linkTo(parent.end) end.linkTo(parent.end)
}, },
showFCAWarning = amountUM.showFCAWarning,
) )
} }
} }

View file

@ -89,6 +89,7 @@ internal data object SwapAmountContentPreview {
swapRateType = ExpressRateType.Float, swapRateType = ExpressRateType.Float,
appCurrency = AppCurrency.Default, appCurrency = AppCurrency.Default,
showBestRateAnimation = false, showBestRateAnimation = false,
showFCAWarning = false,
) )
val defaultState = SwapAmountUM.Content( val defaultState = SwapAmountUM.Content(
@ -126,5 +127,6 @@ internal data object SwapAmountContentPreview {
swapRateType = ExpressRateType.Float, swapRateType = ExpressRateType.Float,
isPrimaryButtonEnabled = true, isPrimaryButtonEnabled = true,
showBestRateAnimation = false, showBestRateAnimation = false,
showFCAWarning = true,
) )
} }

View file

@ -23,6 +23,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -33,6 +34,7 @@ import androidx.constraintlayout.compose.Visibility
import coil.compose.SubcomposeAsyncImage import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemColorPalette
@ -44,6 +46,7 @@ import com.tangem.domain.express.models.ExpressRateType
import com.tangem.features.swap.v2.impl.R import com.tangem.features.swap.v2.impl.R
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@Suppress("LongParameterList")
@Composable @Composable
fun SwapChooseProviderContent( fun SwapChooseProviderContent(
expressProvider: ExpressProvider?, expressProvider: ExpressProvider?,
@ -51,6 +54,7 @@ fun SwapChooseProviderContent(
showBestRateAnimation: Boolean, showBestRateAnimation: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
onFinishAnimation: () -> Unit, onFinishAnimation: () -> Unit,
showFCAWarning: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Column( Column(
@ -83,6 +87,32 @@ fun SwapChooseProviderContent(
SpacerWMax() SpacerWMax()
ProviderInfo(expressProvider, isBestRate, showBestRateAnimation, onFinishAnimation) ProviderInfo(expressProvider, isBestRate, showBestRateAnimation, onFinishAnimation)
} }
if (showFCAWarning) {
FcaProviderWarning(
modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 4.dp, bottom = 12.dp),
)
}
}
}
@Composable
private fun FcaProviderWarning(modifier: Modifier = Modifier) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_warning_16),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
SpacerW8()
Text(
text = stringResourceSafe(R.string.express_provider_in_fca_warning_list),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
} }
} }
@ -281,6 +311,7 @@ private fun SwapChooseProviderContent_Preview() {
slippage = null, slippage = null,
), ),
onClick = {}, onClick = {},
showFCAWarning = true,
onFinishAnimation = {}, onFinishAnimation = {},
) )
} }