Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-18 17:00:05 +05:00
commit f08991988d
16 changed files with 722 additions and 8 deletions

View file

@ -250,6 +250,7 @@ internal class SwapAmountModel @Inject constructor(
.onEach { currency ->
uiState.update { amountUM ->
if (amountUM is SwapAmountUM.Content) {
initPairs(currency)
amountUM.copy(
isPrimaryButtonEnabled = false,
secondaryAmount = SwapAmountFieldUM.Loading(
@ -260,7 +261,6 @@ internal class SwapAmountModel @Inject constructor(
amountUM
}
}
initPairs(currency)
}
.launchIn(modelScope)
}
@ -400,6 +400,7 @@ internal class SwapAmountModel @Inject constructor(
swapDirection = swapDirection,
allowanceContract = quote.allowanceContract,
isApprovalNeeded = checkAllowance(state, quote),
fromAmount = fromAmountValue,
).convert(
SwapQuoteUMConverter.Data(
quote = quote,

View file

@ -12,9 +12,13 @@ import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.utils.extensions.isZero
import java.math.BigDecimal
import java.math.RoundingMode
import kotlin.math.min
internal object SwapAmountQuoteUtils {
private const val MAX_DECIMALS = 8
private const val MIN_DECIMALS = 2
fun calculatePriceImpact(
fromTokenAmount: BigDecimal,
toTokenAmount: BigDecimal,
@ -41,6 +45,11 @@ internal object SwapAmountQuoteUtils {
}
}
fun calculateRate(fromAmount: BigDecimal, toAmount: BigDecimal, toAmountDecimals: Int): BigDecimal {
val rateDecimals = if (toAmountDecimals == 0) MIN_DECIMALS else toAmountDecimals
return toAmount.divide(fromAmount, min(rateDecimals, MAX_DECIMALS), RoundingMode.HALF_UP)
}
fun SwapAmountUM.updateAmount(
onPrimaryAmount: SwapAmountFieldUM.Content.(CryptoCurrencyStatus) -> SwapAmountFieldUM,
onSecondaryAmount: SwapAmountFieldUM.Content.(CryptoCurrencyStatus) -> SwapAmountFieldUM,

View file

@ -1,14 +1,20 @@
package com.tangem.features.swap.v2.impl.amount.model.converter
import androidx.compose.ui.text.buildAnnotatedString
import com.tangem.core.ui.extensions.annotatedReference
import com.tangem.core.ui.extensions.appendSpace
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.anyDecimals
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.swap.models.SwapDirection
import com.tangem.domain.swap.models.SwapQuoteModel
import com.tangem.features.swap.v2.impl.amount.model.SwapAmountQuoteUtils.calculateRate
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.utils.StringsSigns
import com.tangem.utils.converter.Converter
import java.math.BigDecimal
@ -18,10 +24,24 @@ internal class SwapQuoteUMConverter(
private val isApprovalNeeded: Boolean,
private val primaryCurrency: CryptoCurrency,
private val secondaryCurrency: CryptoCurrency,
private val fromAmount: BigDecimal,
) : Converter<SwapQuoteUMConverter.Data, SwapQuoteUM> {
override fun convert(value: Data): SwapQuoteUM {
val (quote, provider) = value
val rate = calculateRate(
fromAmount = fromAmount,
toAmount = quote.toTokenAmount,
toAmountDecimals = secondaryCurrency.decimals,
)
val rateString = buildAnnotatedString {
append(BigDecimal.ONE.format { crypto(symbol = primaryCurrency.symbol, decimals = 0).anyDecimals() })
appendSpace()
append(StringsSigns.APPROXIMATE)
appendSpace()
append(rate.format { crypto(secondaryCurrency) })
}
return if (allowanceContract != null) {
if (isApprovalNeeded) {
SwapQuoteUM.Allowance(
@ -36,6 +56,7 @@ internal class SwapQuoteUMConverter(
quoteAmountValue = stringReference(
quote.toTokenAmount.toQuoteValue(),
),
rate = annotatedReference(rateString),
)
}
} else {
@ -46,6 +67,7 @@ internal class SwapQuoteUMConverter(
quoteAmountValue = stringReference(
quote.toTokenAmount.toQuoteValue(),
),
rate = annotatedReference(rateString),
)
}
}

View file

@ -5,6 +5,8 @@ import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.express.models.ExpressProviderType
import com.tangem.domain.express.models.ExpressRateType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
@ -16,10 +18,11 @@ 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.common.entity.SwapQuoteUM
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
internal data object SwapAmountContentPreview {
private val cryptoCurrencyStatus = CryptoCurrencyStatus(
val cryptoCurrencyStatus = CryptoCurrencyStatus(
currency = CryptoCurrency.Coin(
id = CryptoCurrency.ID.fromValue("coin⟨BITCOIN⟩bitcoin"),
network = Network(
@ -41,12 +44,32 @@ internal data object SwapAmountContentPreview {
name = "Bitcoin",
symbol = "BTC",
decimals = 8,
iconUrl = "",
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/medium/bitcoin.png",
isCustom = false,
),
value = CryptoCurrencyStatus.Loading,
)
private val provider = ExpressProvider(
providerId = "changenow",
rateTypes = listOf(ExpressRateType.Float),
name = "ChangeNow",
type = ExpressProviderType.CEX,
imageLarge = "",
termsOfUse = "",
privacyPolicy = "",
isRecommended = true,
slippage = BigDecimal.ZERO,
)
private val quote = SwapQuoteUM.Content(
provider = provider,
quoteAmount = "123".toBigDecimal(),
quoteAmountValue = stringReference("123"),
rate = stringReference("1 USD ≈ 123.123 POL"),
diffPercent = SwapQuoteUM.Content.DifferencePercent.Best,
)
val emptyState = SwapAmountUM.Content(
isPrimaryButtonEnabled = false,
primaryAmount = SwapAmountFieldUM.Empty(
@ -95,7 +118,7 @@ internal data object SwapAmountContentPreview {
selectedAmountType = SwapAmountType.From,
swapCurrencies = SwapCurrencies.EMPTY,
swapQuotes = persistentListOf(),
selectedQuote = SwapQuoteUM.Empty,
selectedQuote = quote,
primaryCryptoCurrencyStatus = cryptoCurrencyStatus,
secondaryCryptoCurrencyStatus = cryptoCurrencyStatus,
swapRateType = ExpressRateType.Float,

View file

@ -36,6 +36,7 @@ internal object SwapChooseProviderContentPreview {
provider = provider1,
quoteAmount = "123".toBigDecimal(),
quoteAmountValue = stringReference("123"),
rate = stringReference("1 USD ≈ 123.123 POL"),
diffPercent = SwapQuoteUM.Content.DifferencePercent.Best,
)
@ -43,6 +44,7 @@ internal object SwapChooseProviderContentPreview {
provider = provider2,
quoteAmount = "13.12".toBigDecimal(),
quoteAmountValue = stringReference("13.12"),
rate = stringReference("1 USD ≈ 12.123 POL"),
diffPercent = SwapQuoteUM.Content.DifferencePercent.Empty,
)

View file

@ -3,6 +3,8 @@ package com.tangem.features.swap.v2.impl.common.entity
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.swap.models.SwapDataModel
import kotlinx.collections.immutable.ImmutableList
@Immutable
@ -18,6 +20,14 @@ internal sealed class ConfirmUM {
val notifications: ImmutableList<NotificationUM>,
) : ConfirmUM()
data class Success(
override val isPrimaryButtonEnabled: Boolean = true,
val transactionDate: Long,
val txUrl: String,
val swapDataModel: SwapDataModel,
val provider: ExpressProvider,
) : ConfirmUM()
data object Empty : ConfirmUM() {
override val isPrimaryButtonEnabled: Boolean = false
}

View file

@ -34,6 +34,7 @@ internal sealed class SwapQuoteUM {
val quoteAmount: BigDecimal,
val quoteAmountValue: TextReference,
val diffPercent: DifferencePercent,
val rate: TextReference,
) : SwapQuoteUM() {
sealed class DifferencePercent {
data object Empty : DifferencePercent()

View file

@ -0,0 +1,37 @@
package com.tangem.features.swap.v2.impl.sendviaswap.success
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.navigationButtons.NavigationModelCallback
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
import com.tangem.features.swap.v2.impl.sendviaswap.success.model.SendWithSwapSuccessModel
import com.tangem.features.swap.v2.impl.sendviaswap.success.ui.SendWithSwapSuccessContent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
internal class SendWithSwapSuccessComponent(
appComponentContext: AppComponentContext,
params: Params,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: SendWithSwapSuccessModel = getOrCreateModel(params = params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
SendWithSwapSuccessContent(sendWithSwapUM = state)
}
data class Params(
val sendWithSwapUMFlow: StateFlow<SendWithSwapUM>,
val analyticsCategoryName: String,
val currentRoute: Flow<SendWithSwapRoute.Success>,
val callback: NavigationModelCallback,
)
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.swap.v2.impl.sendviaswap.success.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.swap.v2.impl.sendviaswap.success.model.SendWithSwapSuccessModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(ModelComponent::class)
internal interface SendWithSwapSuccessModule {
@Binds
@IntoMap
@ClassKey(SendWithSwapSuccessModel::class)
fun bindSendWithSwapSuccessModel(impl: SendWithSwapSuccessModel): Model
}

View file

@ -0,0 +1,81 @@
package com.tangem.features.swap.v2.impl.sendviaswap.success.model
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.navigationButtons.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
import com.tangem.features.swap.v2.impl.sendviaswap.success.SendWithSwapSuccessComponent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ModelScoped
internal class SendWithSwapSuccessModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val urlOpener: UrlOpener,
private val shareManager: ShareManager,
private val appRouter: AppRouter,
paramsContainer: ParamsContainer,
) : Model() {
private val params: SendWithSwapSuccessComponent.Params = paramsContainer.require()
val uiState: StateFlow<SendWithSwapUM> = params.sendWithSwapUMFlow
val confirmUM = uiState.value.confirmUM as? ConfirmUM.Success
init {
configConfirmSuccessNavigation()
}
private fun configConfirmSuccessNavigation() {
params.callback.onNavigationResult(
NavigationUM.Content(
title = TextReference.EMPTY,
subtitle = null,
backIconRes = R.drawable.ic_close_24,
backIconClick = appRouter::pop,
primaryButton = NavigationButton(
textReference = resourceReference(R.string.common_close),
iconRes = null,
isEnabled = true,
isHapticClick = false,
onClick = appRouter::pop,
),
prevButton = null,
secondaryPairButtonsUM = NavigationButton(
textReference = resourceReference(R.string.common_explore),
iconRes = R.drawable.ic_web_24,
onClick = ::onExploreClick,
) to NavigationButton(
textReference = resourceReference(R.string.common_share),
iconRes = R.drawable.ic_share_24,
onClick = ::onShareClick,
),
),
)
}
private fun onExploreClick() {
if (confirmUM == null) return
// analyticsEventHandler.send(CommonSendAnalyticEvents.ExploreButtonClicked(params.analyticsCategoryName))
urlOpener.openUrl(confirmUM.txUrl)
}
private fun onShareClick() {
if (confirmUM == null) return
// analyticsEventHandler.send(CommonSendAnalyticEvents.ShareButtonClicked(params.analyticsCategoryName))
shareManager.shareText(confirmUM.txUrl)
}
}

View file

@ -0,0 +1,441 @@
package com.tangem.features.swap.v2.impl.sendviaswap.success.ui
import android.content.res.Configuration
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.utils.getFiatReference
import com.tangem.common.ui.navigationButtons.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.icons.identicon.IdentIcon
import com.tangem.core.ui.components.inputrow.InputRowBestRate
import com.tangem.core.ui.components.rows.SelectorRowItem
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fee
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.core.ui.utils.singleEvent
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.domain.express.models.ExpressProvider
import com.tangem.domain.express.models.ExpressProviderType
import com.tangem.domain.swap.models.SwapDataModel
import com.tangem.domain.swap.models.SwapDataTransactionModel
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.features.send.v2.api.entity.FeeExtraInfo
import com.tangem.features.send.v2.api.entity.FeeItem
import com.tangem.features.send.v2.api.entity.FeeNonce
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationTextFieldUM
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
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.SwapAmountUM
import com.tangem.features.swap.v2.impl.amount.ui.preview.SwapAmountContentPreview
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
private const val GRADIENT_ALPHA = 0.3f
@Composable
internal fun SendWithSwapSuccessContent(sendWithSwapUM: SendWithSwapUM) {
if (sendWithSwapUM.navigationUM !is NavigationUM.Content) return
val confirmUM = sendWithSwapUM.confirmUM as? ConfirmUM.Success ?: return
val amountUM = sendWithSwapUM.amountUM as? SwapAmountUM.Content ?: return
val quoteUM = amountUM.selectedQuote as? SwapQuoteUM.Content ?: return
val destinationUM = sendWithSwapUM.destinationUM as? DestinationUM.Content ?: return
val feeSelectorUM = sendWithSwapUM.feeSelectorUM as? FeeSelectorUM.Content ?: return
Box(
modifier = Modifier
.fillMaxSize()
.background(TangemTheme.colors.background.tertiary),
) {
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
TransactionDoneTitle(
title = resourceReference(R.string.sent_transaction_sent_title),
subtitle = resourceReference(
id = R.string.send_date_format,
formatArgs = wrappedList(
confirmUM.transactionDate.toTimeFormat(DateTimeFormatters.dateFormatter),
confirmUM.transactionDate.toTimeFormat(),
),
),
modifier = Modifier.padding(vertical = 12.dp),
)
SwapAmountBlock(amountUM = amountUM)
InputRowBestRate(
imageUrl = confirmUM.provider.imageLarge,
title = stringReference(confirmUM.provider.name),
titleExtra = stringReference(confirmUM.provider.type.typeName),
subtitle = quoteUM.rate,
modifier = Modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action),
)
DestinationBlock(destinationUM.addressTextField)
FeeBlock(feeSelectorUM = feeSelectorUM)
Spacer(Modifier.height(60.dp))
}
DoneButtons(
pairButtonsUM = sendWithSwapUM.navigationUM.secondaryPairButtonsUM,
modifier = Modifier
.align(Alignment.BottomCenter)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
TangemTheme.colors.background.tertiary.copy(GRADIENT_ALPHA),
TangemTheme.colors.background.tertiary,
),
),
)
.padding(top = 24.dp)
.padding(horizontal = 16.dp),
)
}
}
@Composable
private fun SwapAmountBlock(amountUM: SwapAmountUM.Content) {
AmountBlock(
title = resourceReference(
id = R.string.send_from_wallet_name,
formatArgs = wrappedList(
(amountUM.primaryAmount.amountField as? AmountState.Data)?.title
?: TextReference.EMPTY,
),
),
amountFieldUM = amountUM.primaryAmount,
)
AmountBlock(
title = resourceReference(R.string.send_with_swap_recipient_amount_success_title),
amountFieldUM = amountUM.secondaryAmount,
)
}
@Composable
private fun AmountBlock(title: TextReference, amountFieldUM: SwapAmountFieldUM, modifier: Modifier = Modifier) {
val amountFieldData = amountFieldUM.amountField as? AmountState.Data ?: return
val cryptoAmount = amountFieldData.amountTextField.cryptoAmount
val fiatAmount = amountFieldData.amountTextField.fiatAmount
Column(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)
.padding(12.dp),
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
) {
CurrencyIcon(
state = amountFieldData.tokenIconState,
)
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = cryptoAmount.value?.format {
crypto(
symbol = cryptoAmount.currencySymbol,
decimals = cryptoAmount.decimals,
)
}.orEmpty(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = fiatAmount.value.format {
fiat(
fiatCurrencyCode = amountFieldData.appCurrency.code,
fiatCurrencySymbol = amountFieldData.appCurrency.symbol,
)
},
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
}
@Composable
private fun FeeBlock(feeSelectorUM: FeeSelectorUM.Content) {
val feeExtraInfo = feeSelectorUM.feeExtraInfo
val feeFiatRateUM = feeSelectorUM.feeFiatRateUM
Column(
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)
.padding(TangemTheme.dimens.spacing12),
) {
Text(
text = stringResourceSafe(com.tangem.common.ui.R.string.common_network_fee_title),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
Box(modifier = Modifier.padding(top = TangemTheme.dimens.spacing8)) {
val feeAmount = feeSelectorUM.selectedFeeItem.fee.amount
SelectorRowItem(
titleRes = com.tangem.common.ui.R.string.common_fee_selector_option_market,
iconRes = com.tangem.common.ui.R.drawable.ic_bird_24,
preDot = stringReference(
feeAmount.value.format {
crypto(
symbol = feeAmount.currencySymbol,
decimals = feeAmount.decimals,
).fee(canBeLower = feeExtraInfo.isFeeApproximate)
},
),
postDot = if (feeExtraInfo.isFeeConvertibleToFiat && feeFiatRateUM != null) {
getFiatReference(feeAmount.value, feeFiatRateUM.rate, feeFiatRateUM.appCurrency)
} else {
null
},
ellipsizeOffset = feeAmount.currencySymbol.length,
isSelected = true,
showDivider = false,
showSelectedAppearance = false,
paddingValues = PaddingValues(),
)
}
}
}
@Composable
private fun DestinationBlock(address: DestinationTextFieldUM.RecipientAddress, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action)
.padding(12.dp),
) {
Text(
text = stringResourceSafe(R.string.send_recipient),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(24.dp),
modifier = Modifier.padding(top = 8.dp),
) {
Text(
modifier = Modifier.weight(1f),
text = address.value,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
IdentIcon(
address = address.value,
modifier = Modifier
.size(TangemTheme.dimens.size36)
.clip(RoundedCornerShape(18.dp))
.background(TangemTheme.colors.background.tertiary),
)
}
}
}
// TODO remove [REDACTED_TASK_KEY]
@Composable
private fun DoneButtons(pairButtonsUM: Pair<NavigationButton, NavigationButton>?, modifier: Modifier = Modifier) {
val hapticFeedback = LocalHapticFeedback.current
AnimatedVisibility(
visible = pairButtonsUM != null,
modifier = modifier,
enter = slideInVertically().plus(fadeIn()),
exit = slideOutVertically().plus(fadeOut()),
label = "Animate show sent state buttons",
) {
val (leftButton, rightButton) = remember(this) { requireNotNull(pairButtonsUM) }
Row {
SecondaryButtonIconStart(
text = leftButton.textReference.resolveReference(),
iconResId = requireNotNull(leftButton.iconRes),
onClick = {
singleEvent {
leftButton.onClick()
}
},
modifier = Modifier.weight(1f),
)
SpacerW12()
SecondaryButtonIconStart(
text = rightButton.textReference.resolveReference(),
iconResId = requireNotNull(rightButton.iconRes),
onClick = {
singleEvent {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
rightButton.onClick()
}
},
modifier = Modifier.weight(1f),
)
}
}
}
// region Preview
@Suppress("LongMethod")
@Composable
@Preview(showBackground = true, widthDp = 360, heightDp = 720)
@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun SendWithSwapSuccessContent_Preview() {
TangemThemePreview {
SendWithSwapSuccessContent(
sendWithSwapUM = SendWithSwapUM(
amountUM = SwapAmountContentPreview.defaultState,
destinationUM = DestinationUM.Content(
isPrimaryButtonEnabled = false,
addressTextField = DestinationTextFieldUM.RecipientAddress(
value = "0x391316d97a07027a0702c8A002c8A0C25d8470",
keyboardOptions = KeyboardOptions(),
placeholder = TextReference.EMPTY,
label = resourceReference(R.string.send_recipient),
isError = false,
error = null,
isValuePasted = false,
blockchainAddress = "0x391316d97a07027a0702c8A002c8A0C25d8470",
),
memoTextField = null,
recent = persistentListOf(),
wallets = persistentListOf(),
networkName = "Polygon",
isValidating = false,
isInitialized = false,
isRedesignEnabled = false,
),
feeSelectorUM = FeeSelectorUM.Content(
fees = TransactionFee.Single(
normal = Fee.Common(
BigDecimal.ONE.convertToSdkAmount(
SwapAmountContentPreview.cryptoCurrencyStatus.currency,
),
),
),
feeItems = persistentListOf(),
selectedFeeItem = FeeItem.Market(
Fee.Common(
BigDecimal.ONE.convertToSdkAmount(
SwapAmountContentPreview.cryptoCurrencyStatus.currency,
),
),
),
feeExtraInfo = FeeExtraInfo(
isFeeApproximate = false,
isFeeConvertibleToFiat = false,
isTronToken = false,
),
feeFiatRateUM = null,
feeNonce = FeeNonce.None,
),
confirmUM = ConfirmUM.Success(
isPrimaryButtonEnabled = true,
transactionDate = 8960,
txUrl = "https://tangem.com",
provider = ExpressProvider(
providerId = "changelly",
rateTypes = listOf(),
name = "Changelly",
type = ExpressProviderType.CEX,
imageLarge = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/changelly-1024.png",
termsOfUse = "",
privacyPolicy = "",
isRecommended = false,
slippage = null,
),
swapDataModel = SwapDataModel(
toTokenAmount = BigDecimal.TEN,
transaction = SwapDataTransactionModel.CEX(
fromAmount = BigDecimal.TEN,
toAmount = BigDecimal.TEN,
txValue = "cetero",
txId = "oporteat",
txTo = "graecis",
txExtraId = "expetendis",
externalTxId = "atomorum",
externalTxUrl = "https://tangem.com",
txExtraIdName = "Jeffry Blackwell",
),
),
),
navigationUM = NavigationUM.Content(
title = TextReference.EMPTY,
subtitle = null,
backIconRes = R.drawable.ic_close_24,
backIconClick = {},
additionalIconRes = null,
additionalIconClick = null,
primaryButton = NavigationButton(
textReference = resourceReference(R.string.common_close),
isEnabled = true,
onClick = {},
),
secondaryPairButtonsUM = NavigationButton(
textReference = resourceReference(R.string.common_explore),
iconRes = R.drawable.ic_web_24,
isEnabled = true,
onClick = {},
) to NavigationButton(
textReference = resourceReference(R.string.common_share),
iconRes = R.drawable.ic_share_24,
isEnabled = true,
onClick = {},
),
),
),
)
}
}
// endregion