From cf559c1a11aa1ae17ba231e9ea88c3bd2df5602d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Jul 2025 20:46:15 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../provider/ProviderChooseCrypto.kt | 16 +- .../SwapAmountSetQuotesTransformer.kt | 1 + .../SwapChooseProviderBottomSheetContent.kt | 1 + .../entity/SwapProviderState.kt | 34 +++ .../model/SwapChooseProviderModel.kt | 10 +- .../SwapProviderListItemConverter.kt | 10 + .../converter/SwapProviderStateConverter.kt | 92 +++++++ .../ui/SwapChooseProviderBottomSheet.kt | 26 +- .../ui/SwapChooseProviderContent.kt | 21 +- .../chooseprovider/ui/SwapProviderItem.kt | 232 ++++++++++++++++++ .../SwapChooseProviderContentPreview.kt | 19 ++ .../swap/v2/impl/common/entity/SwapQuoteUM.kt | 1 + 12 files changed, 443 insertions(+), 20 deletions(-) create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapProviderState.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderStateConverter.kt create mode 100644 features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapProviderItem.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderChooseCrypto.kt b/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderChooseCrypto.kt index aa7f0f5d08..0370272dcc 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderChooseCrypto.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/provider/ProviderChooseCrypto.kt @@ -23,6 +23,7 @@ import androidx.constraintlayout.compose.Visibility 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.audits.AuditLabel import com.tangem.core.ui.components.audits.AuditLabelUM import com.tangem.core.ui.components.badge.Badge @@ -50,7 +51,6 @@ private const val DISABLED_ICON_ALPHA = 0.4f fun ProviderChooseCrypto(providerChooseUM: ProviderChooseUM, onClick: () -> Unit, modifier: Modifier = Modifier) { ConstraintLayout( modifier = modifier - .background(TangemTheme.colors.background.action) .clip(RoundedCornerShape(14.dp)) .selectedBorder(isSelected = providerChooseUM.isSelected) .clickable( @@ -133,13 +133,23 @@ private fun IconContent(iconUrl: String, modifier: Modifier = Modifier) { SubcomposeAsyncImage( modifier = modifier .size(40.dp) - .clip(RoundedCornerShape(8.dp)) - .background(TangemColorPalette.Light1), + .clip(RoundedCornerShape(8.dp)), model = ImageRequest.Builder(context = LocalContext.current) .data(iconUrl) .crossfade(enable = true) .allowHardware(false) .build(), + loading = { + RectangleShimmer(radius = 8.dp) + }, + error = { + Box( + modifier = Modifier.background( + color = TangemColorPalette.Light1, + shape = RoundedCornerShape(8.dp), + ), + ) + }, contentDescription = null, ) } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSetQuotesTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSetQuotesTransformer.kt index e6b6c96434..614f1d8b53 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSetQuotesTransformer.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountSetQuotesTransformer.kt @@ -58,6 +58,7 @@ internal class SwapAmountSetQuotesTransformer( val percent = quote.quoteAmount / bestQuote.quoteAmount - BigDecimal.ONE quote.copy( diffPercent = DifferencePercent.Diff( + isPositive = percent.isPositive(), percent = stringReference( if (percent.isPositive()) { "${StringsSigns.PLUS}${percent.format { percent() }}" diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapChooseProviderBottomSheetContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapChooseProviderBottomSheetContent.kt index 289afe2a26..7be1b3c0c7 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapChooseProviderBottomSheetContent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapChooseProviderBottomSheetContent.kt @@ -13,5 +13,6 @@ internal data class SwapChooseProviderBottomSheetContent( internal data class SwapProviderListItem( val providerUM: ProviderChooseUM, + val swapProviderState: SwapProviderState, val quote: SwapQuoteUM, ) \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapProviderState.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapProviderState.kt new file mode 100644 index 0000000000..24e18c0fb1 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/entity/SwapProviderState.kt @@ -0,0 +1,34 @@ +package com.tangem.features.swap.v2.impl.chooseprovider.entity + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.extensions.TextReference +import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM.Content.DifferencePercent + +@Deprecated("Use ProviderChooseUM with new design") +@Immutable +internal sealed class SwapProviderState { + + abstract val isSelected: Boolean + + data object Empty : SwapProviderState() { + override val isSelected = false + } + + data class Content( + override val isSelected: Boolean, + val name: String, + val type: String, + val iconUrl: String, + val subtitle: TextReference, + val additionalBadge: AdditionalBadge, + val diffPercent: DifferencePercent, + ) : SwapProviderState() + + @Immutable + sealed class AdditionalBadge { + data object FCAWarningList : AdditionalBadge() + data object BestTrade : AdditionalBadge() + data object Empty : AdditionalBadge() + data object PermissionRequired : AdditionalBadge() + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/SwapChooseProviderModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/SwapChooseProviderModel.kt index c4bf3d574e..08511759a0 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/SwapChooseProviderModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/SwapChooseProviderModel.kt @@ -3,6 +3,7 @@ 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.express.models.ExpressError 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 @@ -10,6 +11,7 @@ import com.tangem.features.swap.v2.impl.chooseprovider.model.converter.SwapProvi 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 com.tangem.utils.extensions.isSingleItem import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -30,6 +32,7 @@ internal class SwapChooseProviderModel @Inject constructor( cryptoCurrency = params.cryptoCurrency, selectedProvider = params.selectedProvider, needApplyFCARestrictions = needApplyFCARestrictions, + needBestRateBadge = params.providers.filterIsInstance().isSingleItem().not(), ) } @@ -42,9 +45,14 @@ internal class SwapChooseProviderModel @Inject constructor( } private fun getInitialState(): SwapChooseProviderBottomSheetContent { + val filteredProviderList = params.providers.filter { + it is SwapQuoteUM.Content || + it is SwapQuoteUM.Allowance || + (it as? SwapQuoteUM.Error)?.expressError is ExpressError.AmountError + } return SwapChooseProviderBottomSheetContent( isApplyFCARestrictions = needApplyFCARestrictions && params.selectedProvider.isRestrictedByFCA(), - providerList = swapProviderListItemConverter.convertList(params.providers) + providerList = swapProviderListItemConverter.convertList(filteredProviderList) .filterNotNull() .toPersistentList(), selectedProvider = params.selectedProvider, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt index e7f45e0726..045d882f8f 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderListItemConverter.kt @@ -22,11 +22,21 @@ internal class SwapProviderListItemConverter( private val cryptoCurrency: CryptoCurrency, private val selectedProvider: ExpressProvider, private val needApplyFCARestrictions: Boolean, + needBestRateBadge: Boolean, ) : Converter { + + private val providerStateConverter = SwapProviderStateConverter( + cryptoCurrency = cryptoCurrency, + selectedProvider = selectedProvider, + needApplyFCARestrictions = needApplyFCARestrictions, + isNeedBestRateBadge = needBestRateBadge, + ) + override fun convert(value: SwapQuoteUM): SwapProviderListItem? { val provider = value.provider ?: return null return SwapProviderListItem( + swapProviderState = providerStateConverter.convert(value), providerUM = ProviderChooseUM( title = stringReference(provider.name), subtitle = stringReference(provider.type.typeName), diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderStateConverter.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderStateConverter.kt new file mode 100644 index 0000000000..4a7a127735 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/model/converter/SwapProviderStateConverter.kt @@ -0,0 +1,92 @@ +package com.tangem.features.swap.v2.impl.chooseprovider.model.converter + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.domain.express.models.ExpressError +import com.tangem.domain.express.models.ExpressProvider +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.features.swap.v2.impl.R +import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapProviderState +import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapProviderState.AdditionalBadge +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 + +@Deprecated("Remove with new design") +internal class SwapProviderStateConverter( + private val cryptoCurrency: CryptoCurrency, + private val selectedProvider: ExpressProvider, + private val isNeedBestRateBadge: Boolean, + private val needApplyFCARestrictions: Boolean, +) : Converter { + + override fun convert(value: SwapQuoteUM): SwapProviderState { + return when (value) { + is SwapQuoteUM.Content -> value.convertToContent() + is SwapQuoteUM.Error -> value.convertToErrorContent() + is SwapQuoteUM.Allowance, + SwapQuoteUM.Empty, + SwapQuoteUM.Loading, + -> SwapProviderState.Empty + } + } + + private fun SwapQuoteUM.Content.convertToContent(): SwapProviderState { + val isBestRate = when (diffPercent) { + SwapQuoteUM.Content.DifferencePercent.Best -> true + else -> false + } + + val additionalBadge = when { + needApplyFCARestrictions && provider.isRestrictedByFCA() -> AdditionalBadge.FCAWarningList + isNeedBestRateBadge && isBestRate && !needApplyFCARestrictions -> AdditionalBadge.BestTrade + else -> AdditionalBadge.Empty + } + + return SwapProviderState.Content( + name = provider.name, + iconUrl = provider.imageLarge, + type = provider.type.typeName, + subtitle = quoteAmountValue, + additionalBadge = additionalBadge, + diffPercent = diffPercent, + isSelected = provider == selectedProvider, + ) + } + + private fun SwapQuoteUM.Error.convertToErrorContent(): SwapProviderState { + val additionalBadge = when { + needApplyFCARestrictions && provider.isRestrictedByFCA() -> AdditionalBadge.FCAWarningList + else -> AdditionalBadge.Empty + } + + return SwapProviderState.Content( + name = provider.name, + iconUrl = provider.imageLarge, + type = provider.type.typeName, + subtitle = when (val error = expressError) { + is ExpressError.AmountError.TooSmallError -> resourceReference( + id = R.string.express_provider_min_amount, + formatArgs = wrappedList( + error.amount.format { crypto(cryptoCurrency) }, + ), + ) + is ExpressError.AmountError.NotEnoughAllowanceError, + is ExpressError.AmountError.TooBigError, + -> resourceReference( + id = R.string.express_provider_max_amount, + formatArgs = wrappedList( + error.amount.format { crypto(cryptoCurrency) }, + ), + ) + else -> TextReference.EMPTY + }, + additionalBadge = additionalBadge, + diffPercent = SwapQuoteUM.Content.DifferencePercent.Empty, + isSelected = false, + ) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderBottomSheet.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderBottomSheet.kt index 11d0e32879..dcd529b3f9 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderBottomSheet.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderBottomSheet.kt @@ -2,13 +2,16 @@ package com.tangem.features.swap.v2.impl.chooseprovider.ui import android.content.res.Configuration import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.Text 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.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -16,14 +19,14 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed +import com.tangem.core.ui.components.SpacerH12 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 +import com.tangem.core.ui.extensions.selectedBorder import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -57,13 +60,14 @@ internal fun SwapChooseProviderContent( ) { Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(horizontal = 13.dp), + modifier = modifier.padding(horizontal = 13.dp), ) { Text( text = stringResourceSafe(id = R.string.onramp_choose_provider_title_hint), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 4.dp), ) AnimatedVisibility( modifier = Modifier.padding(top = 12.dp), @@ -75,12 +79,18 @@ internal fun SwapChooseProviderContent( iconTint = TangemTheme.colors.icon.warning, ) } + SpacerH12() contentUM.providerList.fastForEachIndexed { index, provider -> - ProviderChooseCrypto( - providerChooseUM = provider.providerUM, - onClick = { onProviderClick(provider.quote) }, - modifier = modifier - .conditional(index == 0) { padding(top = 24.dp) }, + SwapProviderItem( + state = provider.swapProviderState, + modifier = Modifier + .clip(RoundedCornerShape(14.dp)) + .selectedBorder(isSelected = provider.swapProviderState.isSelected) + .clickable( + enabled = provider.quote !is SwapQuoteUM.Error, + onClick = { onProviderClick(provider.quote) }, + ) + .padding(12.dp), ) } Icon( diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderContent.kt index 43c2d3c8f0..38a3b4718c 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderContent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapChooseProviderContent.kt @@ -4,11 +4,7 @@ import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -27,6 +23,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest +import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemColorPalette @@ -57,7 +54,7 @@ fun SwapChooseProviderContent(expressProvider: ExpressProvider?, onClick: () -> ) { Icon( painter = rememberVectorPainter( - ImageVector.vectorResource(R.drawable.ic_exchange_horizontal_24), + ImageVector.vectorResource(R.drawable.ic_stack_new_24), ), tint = TangemTheme.colors.icon.accent, contentDescription = null, @@ -72,13 +69,21 @@ fun SwapChooseProviderContent(expressProvider: ExpressProvider?, onClick: () -> SubcomposeAsyncImage( modifier = modifier .size(20.dp) - .clip(RoundedCornerShape(4.dp)) - .background(TangemColorPalette.Light1), + .clip(RoundedCornerShape(4.dp)), model = ImageRequest.Builder(context = LocalContext.current) .data(expressProvider?.imageLarge) .crossfade(enable = true) .allowHardware(false) .build(), + loading = { RectangleShimmer(radius = 4.dp) }, + error = { + Box( + modifier = Modifier.background( + color = TangemColorPalette.Light1, + shape = RoundedCornerShape(4.dp), + ), + ) + }, contentDescription = null, ) Text( diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapProviderItem.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapProviderItem.kt new file mode 100644 index 0000000000..4b8df6d2b6 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/SwapProviderItem.kt @@ -0,0 +1,232 @@ +package com.tangem.features.swap.v2.impl.chooseprovider.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +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.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +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.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.swap.v2.impl.chooseprovider.entity.SwapProviderState +import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM + +@Deprecated("Use ProviderChooseCrypto with new design") +@Composable +internal fun SwapProviderItem(state: SwapProviderState, modifier: Modifier = Modifier) { + when (state) { + is SwapProviderState.Content -> ProviderContentState( + state = state, + modifier = modifier, + ) + is SwapProviderState.Empty -> { /* no-op */ + } + } +} + +@Suppress("LongMethod") +@Composable +private fun ProviderContentState(state: SwapProviderState.Content, modifier: Modifier = Modifier) { + Row( + modifier = modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + SubcomposeAsyncImage( + modifier = Modifier + .size(size = 40.dp) + .clip(TangemTheme.shapes.roundedCorners8), + model = ImageRequest.Builder(context = LocalContext.current).data(state.iconUrl) + .crossfade(enable = true).allowHardware(false).build(), + loading = { RectangleShimmer(radius = 8.dp) }, + error = { + ErrorProviderIcon(Modifier.size(size = 40.dp)) + }, + contentDescription = null, + ) + + Column(modifier = Modifier.padding(start = 12.dp)) { + Row { + Text( + text = state.name, + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.primary1, + ) + Text( + text = state.type, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.padding(start = 4.dp), + ) + val badgeModifier = Modifier.padding(start = 4.dp) + when (state.additionalBadge) { + SwapProviderState.AdditionalBadge.FCAWarningList -> FCABadgeItem(badgeModifier) + SwapProviderState.AdditionalBadge.BestTrade -> BestTradeItem(badgeModifier) + SwapProviderState.AdditionalBadge.PermissionRequired -> PermissionBadgeItem(badgeModifier) + SwapProviderState.AdditionalBadge.Empty -> Unit + } + } + Row( + modifier = Modifier.padding(top = 2.dp), + ) { + Text( + text = state.subtitle.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + + if (state.diffPercent is SwapQuoteUM.Content.DifferencePercent.Diff) { + val textColor = if (state.diffPercent.isPositive) { + TangemTheme.colors.icon.accent + } else { + TangemTheme.colors.text.warning + } + + Text( + text = state.diffPercent.percent.resolveReference(), + style = TangemTheme.typography.body2, + color = textColor, + modifier = Modifier.padding(start = 4.dp), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + } + } + } + } +} + +@Composable +private fun ErrorProviderIcon(modifier: Modifier = Modifier) { + Box( + modifier = modifier.background( + color = TangemTheme.colors.background.secondary, + shape = TangemTheme.shapes.roundedCorners8, + ), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier.matchParentSize(), + painter = painterResource(id = R.drawable.ic_custom_token_44), + contentDescription = null, + ) + } +} + +@Composable +private fun BestTradeItem(modifier: Modifier = Modifier) { + Box( + modifier = modifier.background( + color = TangemTheme.colors.icon.accent.copy(alpha = 0.1f), + shape = TangemTheme.shapes.roundedCornersLarge, + ), + ) { + Text( + text = stringResourceSafe(R.string.express_provider_best_rate), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.icon.accent, + modifier = Modifier.padding(horizontal = 6.dp), + maxLines = 1, + ) + } +} + +@Composable +private fun PermissionBadgeItem(modifier: Modifier = Modifier) { + Box( + modifier = modifier.background( + color = TangemTheme.colors.background.secondary, + shape = TangemTheme.shapes.roundedCornersLarge, + ), + ) { + Text( + text = stringResourceSafe(id = R.string.express_provider_permission_needed), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.padding(horizontal = 6.dp), + maxLines = 1, + ) + } +} + +@Composable +private fun FCABadgeItem(modifier: Modifier = Modifier) { + Box( + modifier = modifier.background( + color = TangemTheme.colors.background.secondary, + shape = TangemTheme.shapes.roundedCornersLarge, + ), + ) { + Text( + text = stringResourceSafe(id = R.string.express_provider_fca_warning_list), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.padding(horizontal = 6.dp), + maxLines = 1, + ) + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun ProviderItemPreview( + @PreviewParameter(ProviderItemParameterProvider::class) state: Pair, +) { + TangemThemePreview { + SwapProviderItem( + modifier = Modifier.background(TangemTheme.colors.background.action), + state = state.first, + ) + } +} + +private class ProviderItemParameterProvider : CollectionPreviewParameterProvider>( + collection = buildList { + val contentState = SwapProviderState.Content( + name = "1inch", + type = "DEX", + iconUrl = "", + subtitle = stringReference(value = "0,64554846 DAI ≈ 1 MATIC"), + additionalBadge = SwapProviderState.AdditionalBadge.Empty, + diffPercent = SwapQuoteUM.Content.DifferencePercent.Diff( + isPositive = false, + percent = stringReference("-10%"), + ), + isSelected = true, + ) + val contentState2 = contentState.copy( + subtitle = stringReference(value = "1 132,46 MATIC"), + additionalBadge = SwapProviderState.AdditionalBadge.PermissionRequired, + diffPercent = SwapQuoteUM.Content.DifferencePercent.Diff( + isPositive = true, + percent = stringReference("+10%"), + ), + ) + add(contentState to true) + add(contentState to false) + + add(contentState2 to true) + add(contentState2 to false) + }, +) +// endregion Preview \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt index dd75bbf62c..0975e02bdf 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/chooseprovider/ui/preview/SwapChooseProviderContentPreview.kt @@ -10,6 +10,7 @@ import com.tangem.domain.express.models.ExpressRateType 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.entity.SwapProviderState import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM import kotlinx.collections.immutable.persistentListOf import java.math.BigDecimal @@ -65,6 +66,15 @@ internal object SwapChooseProviderContentPreview { ), ), ), + swapProviderState = SwapProviderState.Content( + name = provider1.name, + type = provider1.type.typeName, + iconUrl = "", + subtitle = stringReference("1800 POL"), + additionalBadge = SwapProviderState.AdditionalBadge.BestTrade, + diffPercent = SwapQuoteUM.Content.DifferencePercent.Best, + isSelected = true, + ), quote = quote1, ), SwapProviderListItem( @@ -85,6 +95,15 @@ internal object SwapChooseProviderContentPreview { ), ), quote = quote2, + swapProviderState = SwapProviderState.Content( + name = provider1.name, + type = provider1.type.typeName, + iconUrl = "", + subtitle = stringReference("1800 POL"), + additionalBadge = SwapProviderState.AdditionalBadge.BestTrade, + diffPercent = SwapQuoteUM.Content.DifferencePercent.Best, + isSelected = true, + ), ), ), selectedProvider = provider1, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt index ec6aaf74e7..ff661ba3ee 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/common/entity/SwapQuoteUM.kt @@ -40,6 +40,7 @@ internal sealed class SwapQuoteUM { data object Empty : DifferencePercent() data object Best : DifferencePercent() data class Diff( + val isPositive: Boolean, val percent: TextReference, ) : DifferencePercent() }