Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-28 19:29:43 +05:00
parent 34d43ad7c9
commit 2f767f566a
10 changed files with 81 additions and 8 deletions

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M18.656,15.224C18.898,14.92 19.328,14.854 19.648,15.059L19.71,15.104L19.875,15.245C20.247,15.585 20.532,16.024 20.532,16.6C20.532,17.432 19.947,17.969 19.336,18.362C18.868,18.663 18.238,18.97 17.483,19.319L16.688,19.683L15.213,20.355C13.847,20.978 12.961,21.399 12,21.399C11.28,21.399 10.601,21.162 9.73,20.781L8.787,20.355L7.312,19.683C6.2,19.177 5.288,18.763 4.664,18.362C4.053,17.969 3.468,17.432 3.468,16.6C3.468,15.941 3.84,15.462 4.29,15.104L4.352,15.059C4.672,14.854 5.102,14.92 5.344,15.224C5.602,15.548 5.548,16.02 5.224,16.278L5.135,16.353C5.059,16.423 5.017,16.477 4.996,16.513C4.972,16.552 4.968,16.575 4.968,16.6C4.968,16.617 4.951,16.763 5.475,17.1C5.986,17.428 6.776,17.79 7.934,18.318L9.409,18.99L10.364,19.418C11.171,19.769 11.581,19.899 12,19.899C12.559,19.899 13.101,19.669 14.591,18.99L16.066,18.318L16.867,17.952C17.601,17.612 18.142,17.346 18.525,17.1C19.049,16.763 19.032,16.617 19.032,16.6C19.032,16.575 19.028,16.552 19.004,16.513C18.983,16.477 18.941,16.423 18.865,16.353L18.776,16.278L18.718,16.227C18.446,15.962 18.414,15.528 18.656,15.224Z"
android:fillColor="#1E1E1E"/>
<path
android:pathData="M3.791,10.784C4.031,10.446 4.499,10.367 4.836,10.607C5.174,10.847 5.253,11.315 5.013,11.653C4.972,11.711 4.968,11.741 4.968,11.766C4.968,11.783 4.951,11.929 5.475,12.266C5.986,12.594 6.776,12.956 7.934,13.484L9.409,14.156L10.364,14.584C11.171,14.936 11.581,15.065 12,15.065C12.559,15.065 13.101,14.835 14.591,14.156L16.066,13.484L16.867,13.118C17.601,12.778 18.142,12.512 18.525,12.266C19.049,11.929 19.032,11.783 19.032,11.766C19.032,11.753 19.031,11.74 19.025,11.722L18.987,11.653L18.946,11.588C18.758,11.258 18.847,10.832 19.164,10.607C19.48,10.382 19.911,10.437 20.162,10.723L20.209,10.784L20.282,10.893C20.44,11.152 20.532,11.444 20.532,11.766C20.532,12.598 19.947,13.135 19.336,13.528C18.868,13.829 18.238,14.137 17.483,14.486L16.688,14.849L15.213,15.521C13.847,16.143 12.961,16.565 12,16.565C11.28,16.565 10.601,16.328 9.73,15.947L8.787,15.521L7.312,14.849C6.2,14.342 5.288,13.929 4.664,13.528C4.053,13.135 3.468,12.598 3.468,11.766C3.468,11.398 3.588,11.069 3.791,10.784Z"
android:fillColor="#1E1E1E"/>
<path
android:pathData="M12,2.604C12.964,2.604 13.852,3.032 15.217,3.664L16.691,4.345L17.487,4.714C18.242,5.068 18.872,5.381 19.34,5.686C19.954,6.086 20.532,6.628 20.532,7.461C20.532,8.294 19.954,8.837 19.34,9.237C18.872,9.542 18.242,9.854 17.487,10.208L16.691,10.577L15.217,11.258C13.852,11.889 12.964,12.318 12,12.318C11.278,12.318 10.597,12.077 9.726,11.69L8.783,11.258L7.309,10.577C6.197,10.063 5.284,9.644 4.66,9.237C4.046,8.837 3.468,8.294 3.468,7.461C3.468,6.628 4.046,6.086 4.66,5.686C5.284,5.279 6.197,4.859 7.309,4.345L8.783,3.664L9.726,3.232C10.597,2.845 11.278,2.604 12,2.604ZM12,4.104C11.583,4.104 11.175,4.234 10.368,4.59L9.412,5.025L7.938,5.706C6.78,6.242 5.99,6.609 5.479,6.942C4.958,7.282 4.968,7.433 4.968,7.461C4.968,7.49 4.96,7.642 5.479,7.98C5.99,8.313 6.78,8.68 7.938,9.215L9.412,9.897L10.368,10.332C11.175,10.688 11.583,10.818 12,10.818C12.556,10.818 13.097,10.586 14.588,9.897L16.062,9.215L16.863,8.843C17.598,8.498 18.138,8.23 18.521,7.98C19.04,7.642 19.032,7.49 19.032,7.461C19.032,7.433 19.042,7.282 18.521,6.942C18.138,6.692 17.598,6.423 16.863,6.079L16.062,5.706L14.588,5.025C13.097,4.336 12.556,4.104 12,4.104Z"
android:fillColor="#1E1E1E"
android:fillType="evenOdd"/>
</vector>

View file

@ -17,6 +17,7 @@ import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams.AmountBlockParams
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountModel
@ -77,6 +78,7 @@ internal class SwapAmountBlockComponent(
providers = amountUM.swapQuotes,
cryptoCurrency = cryptoCurrency,
selectedProvider = selectedProvider,
userCountry = model.userCountry,
),
)
},
@ -95,6 +97,7 @@ internal class SwapAmountBlockComponent(
providers = config.providers,
cryptoCurrency = config.cryptoCurrency,
selectedProvider = config.selectedProvider,
userCountry = config.userCountry,
callback = model,
onDismiss = { model.bottomSheetNavigation.dismiss() },
),
@ -105,5 +108,6 @@ internal class SwapAmountBlockComponent(
val providers: ImmutableList<SwapQuoteUM>,
val cryptoCurrency: CryptoCurrency,
val selectedProvider: ExpressProvider,
val userCountry: UserCountry,
)
}

View file

@ -17,6 +17,8 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.express.models.ExpressError
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.swap.models.SwapCurrencies
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
@ -54,6 +56,7 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import java.math.BigDecimal
import java.util.Locale
import javax.inject.Inject
import kotlin.properties.Delegates
import com.tangem.utils.transformer.update as transformerUpdate
@ -69,6 +72,7 @@ internal class SwapAmountModel @Inject constructor(
private val swapChooseTokenNetworkListener: SwapChooseTokenNetworkListener,
private val getAllowanceUseCase: GetAllowanceUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getUserCountryUseCase: GetUserCountryUseCase,
private val appRouter: AppRouter,
private val swapAmountAlertFactory: SwapAmountAlertFactory,
private val swapAlertFactory: SwapAlertFactory,
@ -89,6 +93,7 @@ internal class SwapAmountModel @Inject constructor(
private var secondaryMaximumAmountBoundary: EnterAmountBoundary? = null
private var secondaryMinimumAmountBoundary: EnterAmountBoundary? = null
var userCountry: UserCountry = UserCountry.Other(Locale.getDefault().country)
val bottomSheetNavigation: SlotNavigation<SwapChooseProviderConfig> = SlotNavigation()
val uiState: StateFlow<SwapAmountUM>
@ -100,6 +105,8 @@ internal class SwapAmountModel @Inject constructor(
init {
modelScope.launch {
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
userCountry = getUserCountryUseCase.invokeSync().getOrNull()
?: UserCountry.Other(Locale.getDefault().country)
}
configAmountNavigation()
subscribeOnCryptoCurrencyStatusFlow()

View file

@ -10,6 +10,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.features.swap.v2.impl.chooseprovider.model.SwapChooseProviderModel
import com.tangem.features.swap.v2.impl.chooseprovider.ui.SwapChooseProviderBottomSheet
import com.tangem.features.swap.v2.impl.chooseprovider.ui.SwapChooseProviderContent
@ -41,7 +42,7 @@ internal class SwapChooseProviderComponent(
SwapChooseProviderBottomSheet(config = bottomSheetConfig) {
SwapChooseProviderContent(
providerList = state.value.providerList,
contentUM = state.value,
onProviderClick = model::onProviderClick,
)
}
@ -51,6 +52,7 @@ internal class SwapChooseProviderComponent(
val cryptoCurrency: CryptoCurrency,
val selectedProvider: ExpressProvider,
val providers: ImmutableList<SwapQuoteUM>,
val userCountry: UserCountry,
val callback: ModelCallback,
val onDismiss: () -> Unit,
)

View file

@ -7,6 +7,7 @@ import kotlinx.collections.immutable.ImmutableList
internal data class SwapChooseProviderBottomSheetContent(
val providerList: ImmutableList<SwapProviderListItem>,
val isApplyFCARestrictions: Boolean,
val selectedProvider: ExpressProvider,
)

View file

@ -3,10 +3,12 @@ package com.tangem.features.swap.v2.impl.chooseprovider.model
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderComponent
import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapChooseProviderBottomSheetContent
import com.tangem.features.swap.v2.impl.chooseprovider.model.converter.SwapProviderListItemConverter
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.common.isRestrictedByFCA
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.MutableStateFlow
@ -21,10 +23,13 @@ internal class SwapChooseProviderModel @Inject constructor(
private val params: SwapChooseProviderComponent.Params = paramsContainer.require()
private val needApplyFCARestrictions = params.userCountry.needApplyFCARestrictions()
private val swapProviderListItemConverter by lazy(LazyThreadSafetyMode.NONE) {
SwapProviderListItemConverter(
cryptoCurrency = params.cryptoCurrency,
selectedProvider = params.selectedProvider,
needApplyFCARestrictions = needApplyFCARestrictions,
)
}
@ -38,6 +43,7 @@ internal class SwapChooseProviderModel @Inject constructor(
private fun getInitialState(): SwapChooseProviderBottomSheetContent {
return SwapChooseProviderBottomSheetContent(
isApplyFCARestrictions = needApplyFCARestrictions && params.selectedProvider.isRestrictedByFCA(),
providerList = swapProviderListItemConverter.convertList(params.providers)
.filterNotNull()
.toPersistentList(),

View file

@ -15,11 +15,13 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapProviderListItem
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.common.isRestrictedByFCA
import com.tangem.utils.converter.Converter
internal class SwapProviderListItemConverter(
private val cryptoCurrency: CryptoCurrency,
private val selectedProvider: ExpressProvider,
private val needApplyFCARestrictions: Boolean,
) : Converter<SwapQuoteUM, SwapProviderListItem?> {
override fun convert(value: SwapQuoteUM): SwapProviderListItem? {
val provider = value.provider ?: return null
@ -66,9 +68,15 @@ internal class SwapProviderListItemConverter(
},
)
}
is SwapQuoteUM.Content -> if (needApplyFCARestrictions && value.provider.isRestrictedByFCA()) {
ProviderChooseUM.ExtraUM.Action(
text = resourceReference(R.string.express_provider_fca_warning_list),
)
} else {
ProviderChooseUM.ExtraUM.Empty
}
SwapQuoteUM.Empty,
SwapQuoteUM.Loading,
is SwapQuoteUM.Content,
-> ProviderChooseUM.ExtraUM.Empty
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.swap.v2.impl.chooseprovider.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
@ -19,6 +20,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.provider.ProviderChooseCrypto
import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.resourceReference
@ -27,10 +29,9 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapChooseProviderBottomSheetContent
import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapProviderListItem
import com.tangem.features.swap.v2.impl.chooseprovider.ui.preview.SwapChooseProviderContentPreview
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import kotlinx.collections.immutable.ImmutableList
import com.tangem.features.swap.v2.impl.notifications.entity.SwapNotificationUM
@Composable
internal fun SwapChooseProviderBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) {
@ -50,7 +51,7 @@ internal fun SwapChooseProviderBottomSheet(config: TangemBottomSheetConfig, cont
@Composable
internal fun SwapChooseProviderContent(
providerList: ImmutableList<SwapProviderListItem>,
contentUM: SwapChooseProviderBottomSheetContent,
onProviderClick: (SwapQuoteUM) -> Unit,
modifier: Modifier = Modifier,
) {
@ -64,7 +65,17 @@ internal fun SwapChooseProviderContent(
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
providerList.fastForEachIndexed { index, provider ->
AnimatedVisibility(
modifier = Modifier.padding(top = 12.dp),
visible = contentUM.isApplyFCARestrictions,
) {
Notification(
config = SwapNotificationUM.Error.FCAWarningList.config,
containerColor = TangemTheme.colors.button.disabled,
iconTint = TangemTheme.colors.icon.warning,
)
}
contentUM.providerList.fastForEachIndexed { index, provider ->
ProviderChooseCrypto(
providerChooseUM = provider.providerUM,
onClick = { onProviderClick(provider.quote) },
@ -114,7 +125,11 @@ private fun SwapChooseProviderContent_Preview(
),
) {
SwapChooseProviderContent(
providerList = params.providerList,
contentUM = SwapChooseProviderBottomSheetContent(
providerList = params.providerList,
isApplyFCARestrictions = true,
selectedProvider = SwapChooseProviderContentPreview.provider1,
),
onProviderClick = {},
)
}

View file

@ -16,7 +16,7 @@ import java.math.BigDecimal
internal object SwapChooseProviderContentPreview {
private val provider1 = ExpressProvider(
val provider1 = ExpressProvider(
providerId = "changenow",
rateTypes = listOf(ExpressRateType.Float),
name = "ChangeNow",
@ -88,5 +88,6 @@ internal object SwapChooseProviderContentPreview {
),
),
selectedProvider = provider1,
isApplyFCARestrictions = false,
)
}

View file

@ -0,0 +1,13 @@
package com.tangem.features.swap.v2.impl.common
import com.tangem.domain.express.models.ExpressProvider
private val FCA_RESTRICTED_PROVIDER_IDS = setOf(
"changelly",
"changenow",
"okx-cross-chain",
"okx-on-chain",
"simpleswap",
)
fun ExpressProvider.isRestrictedByFCA() = FCA_RESTRICTED_PROVIDER_IDS.contains(providerId)