Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-31 12:40:48 +05:00
parent 4cac26e628
commit 8f69b1dce3
12 changed files with 157 additions and 35 deletions

View file

@ -49,10 +49,10 @@ fun SendingText(footerText: TextReference, modifier: Modifier = Modifier) {
text = footerText.resolveAnnotatedReference(),
textAlign = TextAlign.Center,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
.padding(16.dp),
)
}
}

View file

@ -9,6 +9,7 @@ object StringsSigns {
const val LOWER_SIGN = "<"
const val TILDE_SIGN = "~"
const val COMA_SIGN = ","
const val POINT_SIGN = "."
const val INFINITY_SIGN = ""
const val NON_BREAKING_SPACE = '\u00A0'
const val PERCENT = "%"

View file

@ -1,4 +1,4 @@
package com.tangem.features.send.v2.common.utils
package com.tangem.features.send.v2.api.utils
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.transaction.Fee
@ -8,10 +8,10 @@ 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.domain.appcurrency.model.AppCurrency
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.api.R
import com.tangem.utils.StringsSigns.COMA_SIGN
internal fun getTronTokenFeeSendingText(fee: Fee.Tron, fiatFee: String, fiatSending: TextReference): TextReference {
fun getTronTokenFeeSendingText(fee: Fee.Tron, fiatFee: String, fiatSending: TextReference): TextReference {
val suffix = when {
fee.remainingEnergy == 0L -> {
resourceReference(
@ -40,7 +40,7 @@ internal fun getTronTokenFeeSendingText(fee: Fee.Tron, fiatFee: String, fiatSend
return combinedReference(prefix, stringReference("$COMA_SIGN "), suffix)
}
internal fun formatFooterFiatFee(
fun formatFooterFiatFee(
amount: Amount?,
isFeeConvertibleToFiat: Boolean,
isFeeApproximate: Boolean,

View file

@ -16,8 +16,8 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.common.utils.formatFooterFiatFee
import com.tangem.features.send.v2.common.utils.getTronTokenFeeSendingText
import com.tangem.features.send.v2.api.utils.formatFooterFiatFee
import com.tangem.features.send.v2.api.utils.getTronTokenFeeSendingText
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.fee.model.checkIfFeeTooHigh
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM

View file

@ -16,8 +16,8 @@ import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.common.utils.formatFooterFiatFee
import com.tangem.features.send.v2.common.utils.getTronTokenFeeSendingText
import com.tangem.features.send.v2.api.utils.formatFooterFiatFee
import com.tangem.features.send.v2.api.utils.getTronTokenFeeSendingText
import com.tangem.features.send.v2.impl.R
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.toPersistentList

View file

@ -12,8 +12,8 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.common.utils.formatFooterFiatFee
import com.tangem.features.send.v2.common.utils.getTronTokenFeeSendingText
import com.tangem.features.send.v2.api.utils.formatFooterFiatFee
import com.tangem.features.send.v2.api.utils.getTronTokenFeeSendingText
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.fee.model.checkIfFeeTooHigh
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM

View file

@ -18,7 +18,18 @@ internal sealed class ConfirmUM {
val showTapHelp: Boolean,
val sendingFooter: TextReference,
val notifications: ImmutableList<NotificationUM>,
) : ConfirmUM()
val tosUM: TosUM?,
) : ConfirmUM() {
data class TosUM(
val tosLink: LegalUM?,
val policyLink: LegalUM?,
)
data class LegalUM(
val title: TextReference,
val link: String,
)
}
data class Success(
override val isPrimaryButtonEnabled: Boolean = true,

View file

@ -8,6 +8,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@ -41,6 +42,7 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
sendDestinationBlockComponent: SendDestinationBlockComponent.Factory,
feeSelectorBlockComponentFactory: FeeSelectorBlockComponent.Factory,
sendNotificationsComponentFactory: SendNotificationsComponent.Factory,
private val urlOpener: UrlOpener,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: SendWithSwapConfirmModel = getOrCreateModel(params = params)
@ -151,6 +153,7 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
sendNotificationsUM = sendNotificationsUM,
swapNotificationsComponent = swapNotificationsComponent,
swapNotificationsUM = swapNotificationsUM,
onLinkClick = urlOpener::openUrl,
modifier = modifier,
)
}

View file

@ -15,6 +15,7 @@ internal class SendWithSwapConfirmInitialStateTransformer(
showTapHelp = isShowTapHelp,
sendingFooter = TextReference.EMPTY,
notifications = persistentListOf(),
tosUM = null,
)
}
}

View file

@ -5,6 +5,7 @@ import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
@ -12,6 +13,8 @@ import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooHigh
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkIfCustomFeeTooLow
import com.tangem.features.send.v2.api.utils.formatFooterFiatFee
import com.tangem.features.send.v2.api.utils.getTronTokenFeeSendingText
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
@ -27,6 +30,7 @@ internal class SendWithSwapConfirmationNotificationsTransformer : Transformer<Se
return prevState.copy(
confirmUM = confirmUM.copy(
sendingFooter = getSendingFooterText(feeSelectorUM, prevState.amountUM),
tosUM = createTosUM(prevState.amountUM),
notifications = buildList {
addTooHighNotification(feeSelectorUM = feeSelectorUM)
addTooLowNotification(feeSelectorUM = feeSelectorUM)
@ -75,23 +79,20 @@ internal class SendWithSwapConfirmationNotificationsTransformer : Transformer<Se
fiatCurrencySymbol = appCurrency.symbol,
)
}
val fiatFee = TextReference.EMPTY
// todo send with swap footer
// formatFooterFiatFee(
// amount = feeItem.fee.amount.copy(value = fiatFeeValue),
// isFeeConvertibleToFiat = feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat,
// isFeeApproximate = feeSelectorUM.feeExtraInfo.isFeeApproximate,
// appCurrency = appCurrency,
// )
val fee = feeItem.fee
val fiatFee = formatFooterFiatFee(
amount = fee.amount.copy(value = fiatFeeValue),
isFeeConvertibleToFiat = feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat,
isFeeApproximate = feeSelectorUM.feeExtraInfo.isFeeApproximate,
appCurrency = appCurrency,
)
return if (feeSelectorUM.feeExtraInfo.isTronToken && feeItem.fee is Fee.Tron) {
// todo send with swap footer
// getTronTokenFeeSendingText(
// fee = feeItem.fee,
// fiatFee = fiatFee,
// fiatSending = stringReference(fiatSending),
// )
TextReference.EMPTY
return if (feeSelectorUM.feeExtraInfo.isTronToken && fee is Fee.Tron) {
getTronTokenFeeSendingText(
fee = fee,
fiatFee = fiatFee,
fiatSending = stringReference(fiatSending),
)
} else {
resourceReference(
id = if (feeSelectorUM.feeExtraInfo.isFeeConvertibleToFiat) {
@ -103,4 +104,23 @@ internal class SendWithSwapConfirmationNotificationsTransformer : Transformer<Se
)
}
}
private fun createTosUM(swapAmountUM: SwapAmountUM): ConfirmUM.Content.TosUM? {
val expressProvider = (swapAmountUM as? SwapAmountUM.Content)?.selectedQuote?.provider ?: return null
return ConfirmUM.Content.TosUM(
tosLink = expressProvider.termsOfUse?.let {
ConfirmUM.Content.LegalUM(
title = resourceReference(R.string.common_terms_of_use),
link = it,
)
},
policyLink = expressProvider.privacyPolicy?.let {
ConfirmUM.Content.LegalUM(
title = resourceReference(R.string.common_privacy_policy),
link = it,
)
},
)
}
}

View file

@ -8,19 +8,23 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.withLink
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.footers.SendingText
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.common.ui.notifications.notifications
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.SendNotificationsComponent
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
import com.tangem.features.swap.v2.impl.R
import com.tangem.features.swap.v2.impl.amount.SwapAmountBlockComponent
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
import com.tangem.utils.StringsSigns
import kotlinx.collections.immutable.ImmutableList
@Suppress("LongParameterList")
@ -34,13 +38,16 @@ internal fun SendWithSwapConfirmContent(
sendNotificationsUM: ImmutableList<NotificationUM>,
swapNotificationsComponent: SwapNotificationsComponent,
swapNotificationsUM: ImmutableList<NotificationUM>,
onLinkClick: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val confirmUM = sendWithSwapUM.confirmUM as? ConfirmUM.Content
Column(modifier = modifier) {
LazyColumn(
modifier = Modifier.padding(horizontal = 12.dp),
modifier = Modifier
.weight(1f)
.padding(horizontal = 12.dp),
) {
item(key = "SendWithSwapBlocks") {
Column(
@ -71,7 +78,80 @@ internal fun SendWithSwapConfirmContent(
)
}
}
SpacerHMax()
SendingText(footerText = confirmUM?.sendingFooter ?: TextReference.EMPTY)
val sendFooter = confirmUM?.sendingFooter ?: TextReference.EMPTY
val legalFooter = getAnnotatedStringForLegals(confirmUM?.tosUM, onClick = onLinkClick)
SendingText(
footerText = if (sendFooter != TextReference.EMPTY || legalFooter != TextReference.EMPTY) {
combinedReference(sendFooter, legalFooter)
} else {
TextReference.EMPTY
},
)
}
}
@Composable
private fun getAnnotatedStringForLegals(tosUM: ConfirmUM.Content.TosUM?, onClick: (String) -> Unit): TextReference {
if (tosUM == null) return TextReference.EMPTY
val tos = tosUM.tosLink
val policy = tosUM.policyLink
return if (tos != null && policy != null) {
val tosTitle = tos.title.resolveReference()
val policyTitle = policy.title.resolveReference()
val fullString = stringResourceSafe(id = R.string.express_legal_two_placeholders, tosTitle, policyTitle)
val tosIndex = fullString.indexOf(tosTitle)
val policyIndex = fullString.indexOf(policyTitle)
annotatedReference {
append(StringsSigns.POINT_SIGN)
appendSpace()
append(fullString.substring(0, tosIndex))
withLink(
link = LinkAnnotation.Clickable(
tag = "TOS_TAG",
linkInteractionListener = { onClick(tos.link) },
),
block = {
appendColored(
text = fullString.substring(tosIndex, tosIndex + tosTitle.length),
color = TangemTheme.colors.text.accent,
)
},
)
append(fullString.substring(tosIndex + tosTitle.length, policyIndex))
withLink(
link = LinkAnnotation.Clickable(
tag = "POLICY_TAG",
linkInteractionListener = { onClick(policy.link) },
),
block = {
appendColored(
text = fullString.substring(policyIndex, policyIndex + policyTitle.length),
color = TangemTheme.colors.text.accent,
)
},
)
}
} else {
val legal = requireNotNull(tos ?: policy) { "tos or policy must not be null" }
val legalTitle = legal.title.resolveReference()
val fullString = stringResourceSafe(id = R.string.express_legal_one_placeholder, legalTitle)
val legalIndex = fullString.indexOf(legalTitle)
annotatedReference {
append(fullString.substring(0, legalIndex))
withLink(
link = LinkAnnotation.Clickable(
tag = "LEGAL_TAG",
linkInteractionListener = { onClick(legal.link) },
),
block = {
appendColored(
text = fullString.substring(legalIndex, legalIndex + legalTitle.length),
color = TangemTheme.colors.text.accent,
)
},
)
}
}
}

View file

@ -59,7 +59,13 @@ internal fun SendWithSwapContent(
}
// TODO refactor [REDACTED_TASK_KEY]
val primaryButton = navigationUM.primaryButton
Row(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
) {
TangemButton(
modifier = Modifier.fillMaxWidth(),
text = primaryButton.textReference.resolveReference(),