Updated on 2026-08-14
This commit is contained in:
parent
dbbbdd2d33
commit
d6f8549bd2
7 changed files with 118 additions and 48 deletions
|
|
@ -57,6 +57,7 @@ internal class SwapQuoteUMConverter(
|
|||
quote.toTokenAmount.toQuoteValue(),
|
||||
),
|
||||
rate = annotatedReference(rateString),
|
||||
isSingleProvider = false,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -68,6 +69,7 @@ internal class SwapQuoteUMConverter(
|
|||
quote.toTokenAmount.toQuoteValue(),
|
||||
),
|
||||
rate = annotatedReference(rateString),
|
||||
isSingleProvider = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM.Content.Differ
|
|||
import com.tangem.features.swap.v2.impl.common.isRestrictedByFCA
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.extensions.isPositive
|
||||
import com.tangem.utils.extensions.isSingleItem
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
|
@ -26,12 +27,20 @@ internal class SwapAmountSetQuotesTransformer(
|
|||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
if (prevState !is SwapAmountUM.Content) return prevState
|
||||
|
||||
val isSingleProvider = quotes.filter {
|
||||
it is SwapQuoteUM.Content || it is SwapQuoteUM.Allowance ||
|
||||
(it as? SwapQuoteUM.Error)?.expressError is ExpressError.AmountError
|
||||
}.isSingleItem()
|
||||
|
||||
val sortedQuotes = quotes.sortedWith(SwapQuotesComparator)
|
||||
val bestQuote = findBestQuote(quotes) ?: SwapQuoteUM.Empty
|
||||
val selectedQuote = if (isSilentReload && prevState.selectedQuote !is SwapQuoteUM.Loading) {
|
||||
prevState.selectedQuote
|
||||
} else {
|
||||
(bestQuote as? SwapQuoteUM.Content)?.copy(diffPercent = DifferencePercent.Best) ?: bestQuote
|
||||
(bestQuote as? SwapQuoteUM.Content)?.copy(
|
||||
diffPercent = DifferencePercent.Best,
|
||||
isSingleProvider = isSingleProvider,
|
||||
) ?: bestQuote
|
||||
}
|
||||
|
||||
val selectQuoteTransformer = SwapAmountSelectQuoteTransformer(
|
||||
|
|
@ -47,16 +56,23 @@ internal class SwapAmountSetQuotesTransformer(
|
|||
|
||||
return updatedState.copy(
|
||||
isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled && quotes.isNotEmpty(),
|
||||
swapQuotes = getQuotesWithDiff(sortedQuotes, bestQuote),
|
||||
swapQuotes = getQuotesWithDiff(sortedQuotes, bestQuote, isSingleProvider),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getQuotesWithDiff(sortedQuotes: List<SwapQuoteUM>, bestQuote: SwapQuoteUM): ImmutableList<SwapQuoteUM> {
|
||||
private fun getQuotesWithDiff(
|
||||
sortedQuotes: List<SwapQuoteUM>,
|
||||
bestQuote: SwapQuoteUM,
|
||||
isSingleProvider: Boolean,
|
||||
): ImmutableList<SwapQuoteUM> {
|
||||
return sortedQuotes.sortedWith(SwapQuotesComparator)
|
||||
.map { quote ->
|
||||
if (quote is SwapQuoteUM.Content && bestQuote is SwapQuoteUM.Content) {
|
||||
if (quote.provider.providerId == bestQuote.provider.providerId) {
|
||||
quote.copy(diffPercent = DifferencePercent.Best)
|
||||
quote.copy(
|
||||
diffPercent = DifferencePercent.Best,
|
||||
isSingleProvider = isSingleProvider,
|
||||
)
|
||||
} else {
|
||||
// current / selected - 1
|
||||
val percent = quote.quoteAmount / bestQuote.quoteAmount - BigDecimal.ONE
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
|||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ConstrainedLayoutReference
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.ConstraintLayoutScope
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.ui.AmountBlockV2
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -34,6 +36,7 @@ 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.amount.entity.SwapAmountFieldUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.amount.ui.preview.SwapAmountContentPreview
|
||||
import com.tangem.features.swap.v2.impl.chooseprovider.ui.SwapChooseProviderContent
|
||||
|
|
@ -63,33 +66,11 @@ internal fun SwapAmountBlockContent(
|
|||
),
|
||||
) {
|
||||
val (from, to, separator, provider) = createRefs()
|
||||
AmountBlockV2(
|
||||
amountState = amountUM.primaryAmount.amountField,
|
||||
isClickDisabled = true,
|
||||
isEditingDisabled = false,
|
||||
modifier = Modifier.constrainAs(from) {
|
||||
top.linkTo(parent.top)
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
extraContent = { SwapPriceImpact(amountFieldUM = amountUM.primaryAmount, onInfoClick = onInfoClick) },
|
||||
)
|
||||
AmountBlockV2(
|
||||
amountState = (amountUM.secondaryAmount.amountField as? AmountState.Data)?.copy(
|
||||
title = resourceReference(R.string.send_with_swap_recipient_amount_title),
|
||||
availableBalance = TextReference.EMPTY,
|
||||
availableBalanceShort = TextReference.EMPTY,
|
||||
) ?: amountUM.secondaryAmount.amountField,
|
||||
isClickDisabled = true,
|
||||
isEditingDisabled = false,
|
||||
modifier = Modifier.constrainAs(to) {
|
||||
top.linkTo(from.bottom, 8.dp)
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
extraContent = {
|
||||
SwapPriceImpact(amountFieldUM = amountUM.secondaryAmount, onInfoClick = onInfoClick)
|
||||
},
|
||||
SwapAmountBlock(
|
||||
amountUM = amountUM,
|
||||
fromAmountRef = from,
|
||||
toAmountRef = to,
|
||||
onInfoClick = onInfoClick,
|
||||
)
|
||||
SwapAmountDivider(
|
||||
modifier = Modifier.constrainAs(separator) {
|
||||
|
|
@ -103,6 +84,7 @@ internal fun SwapAmountBlockContent(
|
|||
val isBestRate = quoteContent?.diffPercent is SwapQuoteUM.Content.DifferencePercent.Best
|
||||
SwapChooseProviderContent(
|
||||
isBestRate = isBestRate,
|
||||
isSingleProvider = quoteContent?.isSingleProvider == true,
|
||||
showBestRateAnimation = amountUM.showBestRateAnimation,
|
||||
expressProvider = amountUM.selectedQuote.provider,
|
||||
onClick = onProviderSelectClick,
|
||||
|
|
@ -119,29 +101,88 @@ internal fun SwapAmountBlockContent(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapPriceImpact(amountFieldUM: SwapAmountFieldUM, onInfoClick: () -> Unit) {
|
||||
private fun ConstraintLayoutScope.SwapAmountBlock(
|
||||
amountUM: SwapAmountUM.Content,
|
||||
fromAmountRef: ConstrainedLayoutReference,
|
||||
toAmountRef: ConstrainedLayoutReference,
|
||||
onInfoClick: () -> Unit,
|
||||
) {
|
||||
AmountBlockV2(
|
||||
amountState = amountUM.primaryAmount.amountField,
|
||||
isClickDisabled = true,
|
||||
isEditingDisabled = false,
|
||||
modifier = Modifier.constrainAs(fromAmountRef) {
|
||||
top.linkTo(parent.top)
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
extraContent = {
|
||||
SwapPriceImpact(
|
||||
amountFieldUM = amountUM.primaryAmount,
|
||||
selectedAmountType = amountUM.selectedAmountType,
|
||||
onInfoClick = onInfoClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
AmountBlockV2(
|
||||
amountState = (amountUM.secondaryAmount.amountField as? AmountState.Data)?.copy(
|
||||
title = resourceReference(R.string.send_with_swap_recipient_amount_title),
|
||||
availableBalance = TextReference.EMPTY,
|
||||
availableBalanceShort = TextReference.EMPTY,
|
||||
) ?: amountUM.secondaryAmount.amountField,
|
||||
isClickDisabled = true,
|
||||
isEditingDisabled = false,
|
||||
modifier = Modifier.constrainAs(toAmountRef) {
|
||||
top.linkTo(fromAmountRef.bottom, 8.dp)
|
||||
start.linkTo(parent.start)
|
||||
end.linkTo(parent.end)
|
||||
},
|
||||
extraContent = {
|
||||
SwapPriceImpact(
|
||||
amountFieldUM = amountUM.secondaryAmount,
|
||||
selectedAmountType = amountUM.selectedAmountType,
|
||||
onInfoClick = onInfoClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwapPriceImpact(
|
||||
amountFieldUM: SwapAmountFieldUM,
|
||||
selectedAmountType: SwapAmountType,
|
||||
onInfoClick: () -> Unit,
|
||||
) {
|
||||
if (amountFieldUM.amountType == selectedAmountType) return
|
||||
|
||||
val priceImpact = (amountFieldUM as? SwapAmountFieldUM.Content)?.priceImpact
|
||||
val iconColor = if (priceImpact != null) {
|
||||
TangemTheme.colors.icon.attention
|
||||
} else {
|
||||
TangemTheme.colors.icon.informative
|
||||
}
|
||||
|
||||
if (priceImpact != null) {
|
||||
Text(
|
||||
text = priceImpact.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.attention,
|
||||
)
|
||||
Icon(
|
||||
painter = rememberVectorPainter(
|
||||
ImageVector.vectorResource(R.drawable.ic_information_24),
|
||||
),
|
||||
tint = TangemTheme.colors.icon.attention,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(bounded = false),
|
||||
onClick = onInfoClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
painter = rememberVectorPainter(
|
||||
ImageVector.vectorResource(R.drawable.ic_information_24),
|
||||
),
|
||||
tint = iconColor,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(bounded = false),
|
||||
onClick = onInfoClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ internal data object SwapAmountContentPreview {
|
|||
quoteAmountValue = stringReference("123"),
|
||||
rate = stringReference("1 USD ≈ 123.123 POL"),
|
||||
diffPercent = SwapQuoteUM.Content.DifferencePercent.Best,
|
||||
isSingleProvider = false,
|
||||
)
|
||||
|
||||
val emptyState = SwapAmountUM.Content(
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import kotlinx.coroutines.delay
|
|||
@Composable
|
||||
fun SwapChooseProviderContent(
|
||||
expressProvider: ExpressProvider?,
|
||||
isSingleProvider: Boolean,
|
||||
isBestRate: Boolean,
|
||||
showBestRateAnimation: Boolean,
|
||||
onClick: () -> Unit,
|
||||
|
|
@ -62,6 +63,7 @@ fun SwapChooseProviderContent(
|
|||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(),
|
||||
onClick = onClick,
|
||||
enabled = !isSingleProvider,
|
||||
),
|
||||
) {
|
||||
HorizontalDivider(
|
||||
|
|
@ -88,6 +90,7 @@ fun SwapChooseProviderContent(
|
|||
ProviderInfo(
|
||||
expressProvider = expressProvider,
|
||||
isBestRate = isBestRate,
|
||||
isSingleProvider = isSingleProvider,
|
||||
showBestRateAnimation = showBestRateAnimation,
|
||||
onFinishAnimation = onFinishAnimation,
|
||||
)
|
||||
|
|
@ -125,6 +128,7 @@ private fun FcaProviderWarning(modifier: Modifier = Modifier) {
|
|||
private fun ProviderInfo(
|
||||
expressProvider: ExpressProvider?,
|
||||
isBestRate: Boolean,
|
||||
isSingleProvider: Boolean,
|
||||
showBestRateAnimation: Boolean,
|
||||
onFinishAnimation: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -166,6 +170,7 @@ private fun ProviderInfo(
|
|||
start.linkTo(imageRef.end)
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
end.linkTo(iconRef.start, goneMargin = 12.dp)
|
||||
},
|
||||
)
|
||||
Icon(
|
||||
|
|
@ -180,12 +185,13 @@ private fun ProviderInfo(
|
|||
start.linkTo(nameRef.end)
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
end.linkTo(parent.end, 12.dp)
|
||||
end.linkTo(parent.end, margin = 12.dp)
|
||||
visibility = if (isSingleProvider) Visibility.Gone else Visibility.Visible
|
||||
},
|
||||
)
|
||||
BestRateBadge(
|
||||
showBestRateAnimation = showBestRateAnimation,
|
||||
isBestRate = isBestRate,
|
||||
isBestRate = isBestRate && !isSingleProvider,
|
||||
ref = imageRef,
|
||||
onFinishAnimation = onFinishAnimation,
|
||||
)
|
||||
|
|
@ -325,6 +331,7 @@ private fun SwapChooseProviderContent_Preview() {
|
|||
modifier = Modifier.background(TangemTheme.colors.background.tertiary),
|
||||
) {
|
||||
SwapChooseProviderContent(
|
||||
isSingleProvider = false,
|
||||
isBestRate = true,
|
||||
showBestRateAnimation = true,
|
||||
expressProvider = ExpressProvider(
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ internal object SwapChooseProviderContentPreview {
|
|||
quoteAmountValue = stringReference("123"),
|
||||
rate = stringReference("1 USD ≈ 123.123 POL"),
|
||||
diffPercent = SwapQuoteUM.Content.DifferencePercent.Best,
|
||||
isSingleProvider = false,
|
||||
)
|
||||
|
||||
private val quote2 = SwapQuoteUM.Content(
|
||||
|
|
@ -47,6 +48,7 @@ internal object SwapChooseProviderContentPreview {
|
|||
quoteAmountValue = stringReference("13.12"),
|
||||
rate = stringReference("1 USD ≈ 12.123 POL"),
|
||||
diffPercent = SwapQuoteUM.Content.DifferencePercent.Empty,
|
||||
isSingleProvider = false,
|
||||
)
|
||||
|
||||
val state = SwapChooseProviderBottomSheetContent(
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ internal sealed class SwapQuoteUM {
|
|||
val quoteAmount: BigDecimal,
|
||||
val quoteAmountValue: TextReference,
|
||||
val diffPercent: DifferencePercent,
|
||||
val isSingleProvider: Boolean,
|
||||
val rate: TextReference,
|
||||
) : SwapQuoteUM() {
|
||||
sealed class DifferencePercent {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue