Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-30 17:23:07 +05:00
parent e07cd92e27
commit 5cea27c160
19 changed files with 439 additions and 307 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.common.ui.amountScreen
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
@ -8,13 +9,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.preview.AmountScreenClickIntentsStub
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
import com.tangem.common.ui.amountScreen.ui.amountField
import com.tangem.common.ui.amountScreen.ui.amountFieldV2
import com.tangem.common.ui.amountScreen.ui.buttons
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
@ -29,15 +30,13 @@ fun AmountScreenContent(
isBalanceHidden: Boolean,
clickIntents: AmountScreenClickIntents,
modifier: Modifier = Modifier,
extraContent: (@Composable () -> Unit)? = null,
) {
// Do not put fillMaxSize() in here
LazyColumn(
modifier = modifier
.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing16,
),
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
if (amountState.isRedesignEnabled) {
amountFieldV2(
@ -47,6 +46,11 @@ fun AmountScreenContent(
onCurrencyChange = clickIntents::onCurrencyChangeClick,
onMaxAmountClick = clickIntents::onMaxValueClick,
)
if (extraContent != null) {
item("EXTRA_CONTENT_KEY") {
extraContent()
}
}
} else if (amountState is AmountState.Data) {
amountField(
amountState = amountState,

View file

@ -38,9 +38,7 @@ internal fun LazyListScope.buttons(
key = AMOUNT_BUTTONS_KEY,
) {
val hapticFeedback = LocalHapticFeedback.current
Row(
modifier = Modifier.padding(top = TangemTheme.dimens.spacing12),
) {
Row {
if (segmentedButtonConfig.isNotEmpty()) {
SegmentedButtons(
modifier = Modifier

View file

@ -7,6 +7,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
/**
@ -30,4 +32,30 @@ fun BottomFade(modifier: Modifier = Modifier, backgroundColor: Color = TangemThe
),
),
)
}
/**
* A composable that draws a fade effect. Used on screens with a list of repeating
* elements and floating button at the bottom of the screen.
*/
@Composable
fun Fade(
modifier: Modifier = Modifier,
backgroundColor: Color = TangemTheme.colors.background.secondary,
height: Dp = 40.dp,
) {
Box(
modifier = modifier
.fillMaxWidth()
.height(height)
.background(
brush = Brush.verticalGradient(
endY = 100f,
colors = listOf(
Color.Transparent,
backgroundColor,
),
),
),
)
}

View file

@ -502,9 +502,8 @@ internal class DefaultWalletManagersFacade @Inject constructor(
) ?: error("Wallet manager not found")
val destination = when (amount.type) {
is AmountType.Token -> estimationFeeAddressFactory.makeAddress(blockchain)
is AmountType.TokenYieldSupply -> walletManager.getYieldModuleAddress()
else -> return@withContext null
else -> estimationFeeAddressFactory.makeAddress(blockchain)
}
val callData = when (val amountType = amount.type) {

View file

@ -1,5 +1,6 @@
package com.tangem.features.send.v2.common.ui
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
@ -11,17 +12,22 @@ import com.arkivanov.decompose.extensions.compose.stack.animation.fade
import com.arkivanov.decompose.extensions.compose.stack.animation.slide
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.common.ui.footers.SendingText
import com.tangem.common.ui.navigationButtons.NavigationButtonsBlockV2
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.ui.components.Fade
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
@Composable
internal fun SendContent(
navigationUM: NavigationUM,
confirmUM: ConfirmUM,
stackState: ChildStack<CommonSendRoute, ComposableContentComponent>,
) {
Column(
@ -44,17 +50,35 @@ internal fun SendContent(
},
modifier = Modifier.weight(1f),
) {
it.instance.Content(Modifier.weight(1f))
Box(modifier = Modifier.fillMaxHeight()) {
it.instance.Content(Modifier.fillMaxSize(1f))
if (stackState.active.configuration != CommonSendRoute.ConfirmSuccess) {
Fade(
backgroundColor = TangemTheme.colors.background.tertiary,
modifier = Modifier.align(Alignment.BottomCenter),
)
}
}
}
if (stackState.active.configuration != CommonSendRoute.ConfirmSuccess) {
NavigationButtonsBlockV2(
navigationUM = navigationUM,
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
)
Column {
AnimatedVisibility(
visible = stackState.active.configuration == CommonSendRoute.Confirm,
enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(),
) {
SendingText(footerText = (confirmUM as? ConfirmUM.Content)?.sendingFooter ?: TextReference.EMPTY)
}
NavigationButtonsBlockV2(
navigationUM = navigationUM,
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
)
}
}
}
}

View file

@ -144,6 +144,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
)
SendContent(
navigationUM = state.navigationUM,
confirmUM = state.confirmUM,
stackState = stackState,
)
}

View file

@ -13,10 +13,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.footers.SendingText
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
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.res.TangemTheme
@ -77,8 +76,10 @@ internal fun SendConfirmContent(
isClickDisabled = confirmUM.isSending,
)
}
item("SPACER_KEY") {
SpacerH(16.dp)
}
}
SendingText(footerText = confirmUM?.sendingFooter ?: TextReference.EMPTY)
}
}

View file

@ -11,7 +11,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.amountScreen.ui.AmountBlock
import com.tangem.common.ui.navigationButtons.NavigationButtonsBlockV2
import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.Fade
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
@ -45,54 +46,70 @@ internal fun SendConfirmSuccessContent(sendUM: SendUM, destinationBlockComponent
exit = slideOutVertically().plus(fadeOut()),
label = "Animate success content",
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(TangemTheme.colors.background.tertiary),
) {
Column(
Column {
Box(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp),
.weight(1f)
.background(TangemTheme.colors.background.tertiary),
) {
if (sendUM.confirmUM is ConfirmUM.Success) {
TransactionDoneTitle(
title = resourceReference(R.string.sent_transaction_sent_title),
subtitle = resourceReference(
R.string.send_date_format,
wrappedList(
sendUM.confirmUM.transactionDate.toTimeFormat(DateTimeFormatters.dateFormatter),
sendUM.confirmUM.transactionDate.toTimeFormat(),
),
),
modifier = Modifier.padding(vertical = 12.dp),
)
}
AmountBlock(
amountState = sendUM.amountUM,
isClickDisabled = true,
isEditingDisabled = true,
onClick = {},
SuccessContent(
sendUM = sendUM,
destinationBlockComponent = destinationBlockComponent,
modifier = Modifier.fillMaxHeight(),
)
Fade(
modifier = Modifier.align(Alignment.BottomCenter),
backgroundColor = TangemTheme.colors.background.tertiary,
)
destinationBlockComponent.Content(modifier = Modifier)
FeeBlock(feeSelectorUM = sendUM.feeSelectorUM)
Spacer(Modifier.height(128.dp))
}
BottomFade(Modifier.align(Alignment.BottomCenter), TangemTheme.colors.background.tertiary)
NavigationButtonsBlockV2(
navigationUM = sendUM.navigationUM,
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
)
}
}
}
@Composable
private fun SuccessContent(
sendUM: SendUM,
destinationBlockComponent: SendDestinationBlockComponent,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
if (sendUM.confirmUM is ConfirmUM.Success) {
TransactionDoneTitle(
title = resourceReference(R.string.sent_transaction_sent_title),
subtitle = resourceReference(
R.string.send_date_format,
wrappedList(
sendUM.confirmUM.transactionDate.toTimeFormat(DateTimeFormatters.dateFormatter),
sendUM.confirmUM.transactionDate.toTimeFormat(),
),
),
modifier = Modifier.padding(vertical = 12.dp),
)
}
AmountBlock(
amountState = sendUM.amountUM,
isClickDisabled = true,
isEditingDisabled = true,
onClick = {},
)
destinationBlockComponent.Content(modifier = Modifier)
FeeBlock(feeSelectorUM = sendUM.feeSelectorUM)
SpacerH(16.dp)
}
}
private const val ANIMATION_DELAY = 600L
private val ANIMATION_OFFSET = (-40).dp

View file

@ -124,6 +124,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
BackHandler(onBack = model::onBackClick)
SendContent(
navigationUM = state.navigationUM,
confirmUM = state.confirmUM,
stackState = stackState,
)
}

View file

@ -12,11 +12,8 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.footers.SendingText
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
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.res.TangemTheme
@ -49,33 +46,29 @@ internal fun NFTSendConfirmContent(
) {
val confirmUM = nftSendUM.confirmUM as? ConfirmUM.Content
Column {
LazyColumn(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
) {
blocks(
nftSendUM = nftSendUM,
destinationBlockComponent = destinationBlockComponent,
nftDetailsBlockComponent = nftDetailsBlockComponent,
feeBlockComponent = feeBlockComponent,
feeSelectorBlockComponent = feeSelectorBlockComponent,
)
if (confirmUM != null) {
tapHelp(isDisplay = confirmUM.showTapHelp)
with(notificationsComponent) {
content(
state = notificationsUM,
isClickDisabled = confirmUM.isSending,
)
}
notifications(
notifications = confirmUM.notifications,
LazyColumn(
modifier = Modifier.padding(horizontal = 16.dp),
) {
blocks(
nftSendUM = nftSendUM,
destinationBlockComponent = destinationBlockComponent,
nftDetailsBlockComponent = nftDetailsBlockComponent,
feeBlockComponent = feeBlockComponent,
feeSelectorBlockComponent = feeSelectorBlockComponent,
)
if (confirmUM != null) {
tapHelp(isDisplay = confirmUM.showTapHelp)
with(notificationsComponent) {
content(
state = notificationsUM,
isClickDisabled = confirmUM.isSending,
)
}
notifications(
notifications = confirmUM.notifications,
isClickDisabled = confirmUM.isSending,
)
}
SpacerHMax()
SendingText(footerText = confirmUM?.sendingFooter ?: TextReference.EMPTY)
}
}

View file

@ -2,16 +2,16 @@ package com.tangem.features.send.v2.sendnft.success.ui
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.navigationButtons.NavigationButtonsBlockV2
import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.Fade
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
@ -52,52 +52,67 @@ internal fun NFTSendSuccessContent(
label = "Animate success content",
modifier = modifier,
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(TangemTheme.colors.background.tertiary),
) {
Column(
Column {
Box(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.scrollable(
state = rememberScrollState(),
orientation = Orientation.Vertical,
),
verticalArrangement = Arrangement.spacedBy(12.dp),
.weight(1f)
.background(TangemTheme.colors.background.tertiary),
) {
if (nftSendUM.confirmUM is ConfirmUM.Success) {
TransactionDoneTitle(
title = resourceReference(R.string.sent_transaction_sent_title),
subtitle = resourceReference(
R.string.send_date_format,
wrappedList(
nftSendUM.confirmUM.transactionDate.toTimeFormat(DateTimeFormatters.dateFormatter),
nftSendUM.confirmUM.transactionDate.toTimeFormat(),
),
),
modifier = Modifier.padding(vertical = 12.dp),
)
}
nftDetailsBlockComponent.Content(modifier = Modifier)
destinationBlockComponent.Content(modifier = Modifier)
FeeBlock(feeSelectorUM = nftSendUM.feeSelectorUM)
Spacer(Modifier.height(128.dp))
SuccessContent(
nftSendUM = nftSendUM,
nftDetailsBlockComponent = nftDetailsBlockComponent,
destinationBlockComponent = destinationBlockComponent,
modifier = Modifier.fillMaxHeight(),
)
Fade(
modifier = Modifier.align(Alignment.BottomCenter),
backgroundColor = TangemTheme.colors.background.tertiary,
)
}
BottomFade(Modifier.align(Alignment.BottomCenter), TangemTheme.colors.background.tertiary)
NavigationButtonsBlockV2(
navigationUM = nftSendUM.navigationUM,
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
)
}
}
}
@Composable
private fun SuccessContent(
nftSendUM: NFTSendUM,
destinationBlockComponent: SendDestinationBlockComponent,
nftDetailsBlockComponent: NFTDetailsBlockComponent,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
if (nftSendUM.confirmUM is ConfirmUM.Success) {
TransactionDoneTitle(
title = resourceReference(R.string.sent_transaction_sent_title),
subtitle = resourceReference(
R.string.send_date_format,
wrappedList(
nftSendUM.confirmUM.transactionDate.toTimeFormat(DateTimeFormatters.dateFormatter),
nftSendUM.confirmUM.transactionDate.toTimeFormat(),
),
),
modifier = Modifier.padding(vertical = 12.dp),
)
}
nftDetailsBlockComponent.Content(modifier = Modifier)
destinationBlockComponent.Content(modifier = Modifier)
FeeBlock(feeSelectorUM = nftSendUM.feeSelectorUM)
SpacerH(16.dp)
}
}
private const val ANIMATION_DELAY = 600L
private val ANIMATION_OFFSET = (-40).dp

View file

@ -19,6 +19,7 @@ import androidx.compose.ui.unit.dp
import com.tangem.common.ui.amountScreen.AmountScreenContent
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -39,12 +40,13 @@ fun SendAmountContent(
amountState = amountState,
isBalanceHidden = isBalanceHidden,
clickIntents = clickIntents,
extraContent = {
SendConvertTokenButton(
onConvertToAnother = clickIntents::onConvertToAnotherToken,
).takeIf { isSendWithSwapAvailable }
},
)
if (isSendWithSwapAvailable) {
SendConvertTokenButton(
onConvertToAnother = clickIntents::onConvertToAnotherToken,
)
}
SpacerH(16.dp)
}
}

View file

@ -21,6 +21,7 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.containers.FooterContainer
import com.tangem.core.ui.components.inputrow.InputRowRecipient
import com.tangem.core.ui.extensions.*
@ -52,11 +53,7 @@ internal fun SendDestinationContent(
LazyColumn(
modifier = Modifier // Do not put fillMaxSize() in here
.background(TangemTheme.colors.background.tertiary)
.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
.padding(horizontal = 16.dp),
) {
addressItem(
address = address,
@ -106,6 +103,9 @@ internal fun SendDestinationContent(
)
},
)
item("SPACER_KEY") {
SpacerH(16.dp)
}
}
}

View file

@ -7,7 +7,9 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@ -24,6 +26,7 @@ import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.ui.AmountFieldV2
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH2
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.atoms.text.EllipsisText
@ -44,6 +47,7 @@ import com.tangem.features.swap.v2.impl.amount.ui.preview.SwapAmountClickIntents
import com.tangem.features.swap.v2.impl.amount.ui.preview.SwapAmountContentPreview
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
@Suppress("DestructuringDeclarationWithTooManyEntries")
@Composable
internal fun SwapAmountContent(
amountUM: SwapAmountUM,
@ -53,9 +57,9 @@ internal fun SwapAmountContent(
val swapAmountContent = amountUM as? SwapAmountUM.Content
val isFixedRate = swapAmountContent?.swapRateType == ExpressRateType.Fixed
ConstraintLayout(
modifier = modifier,
modifier = modifier.verticalScroll(rememberScrollState()),
) {
val (amountFromRef, amountToRef, middleButtonRef) = createRefs()
val (amountFromRef, amountToRef, middleButtonRef, spacerRef) = createRefs()
SwapAmountBlock(
amountFieldUM = amountUM.primaryAmount,
selectedAmountType = amountUM.selectedAmountType,
@ -90,6 +94,14 @@ internal fun SwapAmountContent(
end.linkTo(parent.end)
},
)
SpacerH16(
modifier = Modifier.constrainAs(spacerRef) {
top.linkTo(amountToRef.bottom)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
},
)
}
}

View file

@ -17,6 +17,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.swap.models.R
@ -47,6 +48,7 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
private val sendDestinationComponentFactory: SendDestinationComponent.Factory,
private val confirmComponentFactory: SendWithSwapConfirmComponent.Factory,
private val analyticsEventHandler: AnalyticsEventHandler,
private val urlOpener: UrlOpener,
) : SendWithSwapComponent, AppComponentContext by appComponentContext {
private val stackNavigation = StackNavigation<SendWithSwapRoute>()
@ -127,7 +129,12 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
(state.navigationUM as? NavigationUM.Content)?.backIconClick() ?: onChildBack()
},
)
SendWithSwapContent(navigationUM = state.navigationUM, stackState = stackState)
SendWithSwapContent(
navigationUM = state.navigationUM,
confirmUM = state.confirmUM,
stackState = stackState,
onLinkClick = urlOpener::openUrl,
)
}
private fun createChild(route: SendWithSwapRoute, childContext: AppComponentContext) = when (route) {

View file

@ -8,7 +8,6 @@ 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.CryptoCurrency
@ -44,7 +43,6 @@ 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)
@ -162,7 +160,6 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
sendNotificationsUM = sendNotificationsUM,
swapNotificationsComponent = swapNotificationsComponent,
swapNotificationsUM = swapNotificationsUM,
onLinkClick = urlOpener::openUrl,
modifier = modifier,
)
}

View file

@ -8,23 +8,18 @@ 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.SpacerH16
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")
@ -38,120 +33,44 @@ 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
.weight(1f)
.padding(horizontal = 12.dp),
) {
item(key = "SendWithSwapBlocks") {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
amountBlockComponent.Content(Modifier)
sendDestinationBlockComponent.Content(Modifier)
feeSelectorBlockComponent.Content(Modifier.clip(RoundedCornerShape(16.dp)))
}
LazyColumn(
modifier = modifier
.padding(horizontal = 12.dp),
) {
item(key = "SendWithSwapBlocks") {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
amountBlockComponent.Content(Modifier)
sendDestinationBlockComponent.Content(Modifier)
feeSelectorBlockComponent.Content(Modifier.clip(RoundedCornerShape(16.dp)))
}
if (confirmUM != null) {
// tapHelp(isDisplay = confirmUM.showTapHelp) // todo
with(swapNotificationsComponent) {
content(
state = swapNotificationsUM,
isClickDisabled = confirmUM.isTransactionInProcess,
)
}
with(sendNotificationsComponent) {
content(
state = sendNotificationsUM,
isClickDisabled = confirmUM.isTransactionInProcess,
)
}
notifications(
notifications = confirmUM.notifications,
}
if (confirmUM != null) {
// tapHelp(isDisplay = confirmUM.showTapHelp) // todo
with(swapNotificationsComponent) {
content(
state = swapNotificationsUM,
isClickDisabled = confirmUM.isTransactionInProcess,
)
}
}
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,
)
},
with(sendNotificationsComponent) {
content(
state = sendNotificationsUM,
isClickDisabled = confirmUM.isTransactionInProcess,
)
}
notifications(
notifications = confirmUM.notifications,
isClickDisabled = confirmUM.isTransactionInProcess,
)
}
} 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,
)
},
)
item("SPACER_KEY") {
SpacerH16()
}
}
}

View file

@ -21,7 +21,7 @@ import com.tangem.common.ui.amountScreen.utils.getFiatReference
import com.tangem.common.ui.navigationButtons.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationButtonsBlockV2
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.Fade
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
@ -61,59 +61,70 @@ import java.math.BigDecimal
internal fun SendWithSwapSuccessContent(sendWithSwapUM: SendWithSwapUM) {
if (sendWithSwapUM.navigationUM !is NavigationUM.Content) return
Column {
Box(
modifier = Modifier
.weight(1f)
.background(TangemTheme.colors.background.tertiary),
) {
SuccessContent(
sendWithSwapUM = sendWithSwapUM,
modifier = Modifier.fillMaxHeight(),
)
Fade(
modifier = Modifier.align(Alignment.BottomCenter),
backgroundColor = TangemTheme.colors.background.tertiary,
)
}
NavigationButtonsBlockV2(
navigationUM = sendWithSwapUM.navigationUM,
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
)
}
}
@Composable
private fun SuccessContent(sendWithSwapUM: SendWithSwapUM, modifier: Modifier = Modifier) {
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),
) {
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(128.dp))
}
BottomFade(Modifier.align(Alignment.BottomCenter), TangemTheme.colors.background.tertiary)
NavigationButtonsBlockV2(
navigationUM = sendWithSwapUM.navigationUM,
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.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(16.dp))
}
}

View file

@ -1,11 +1,14 @@
package com.tangem.features.swap.v2.impl.sendviaswap.ui
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.withLink
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.stack.animation.fade
@ -13,18 +16,25 @@ import com.arkivanov.decompose.extensions.compose.stack.animation.plus
import com.arkivanov.decompose.extensions.compose.stack.animation.slide
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.common.ui.footers.SendingText
import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.ui.components.Fade
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
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.SendWithSwapRoute
import com.tangem.utils.StringsSigns
@Composable
internal fun SendWithSwapContent(
navigationUM: NavigationUM,
confirmUM: ConfirmUM,
stackState: ChildStack<SendWithSwapRoute, ComposableContentComponent>,
onLinkClick: (String) -> Unit,
) {
val navigationUM = navigationUM as? NavigationUM.Content ?: return
@ -53,16 +63,109 @@ internal fun SendWithSwapContent(
},
modifier = Modifier.weight(1f),
) {
it.instance.Content(Modifier.weight(1f))
Box(modifier = Modifier.fillMaxHeight()) {
it.instance.Content(Modifier.fillMaxSize(1f))
if (stackState.active.configuration != SendWithSwapRoute.Success) {
Fade(
backgroundColor = TangemTheme.colors.background.tertiary,
modifier = Modifier.align(Alignment.BottomCenter),
)
}
}
}
if (stackState.active.configuration != SendWithSwapRoute.Success) {
NavigationPrimaryButton(
navigationUM.primaryButton,
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
Column {
AnimatedVisibility(
visible = stackState.active.configuration == SendWithSwapRoute.Confirm,
enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(),
) {
val confirmContentUM = confirmUM as? ConfirmUM.Content
val sendFooter = confirmContentUM?.sendingFooter ?: TextReference.EMPTY
val legalFooter = getAnnotatedStringForLegals(confirmContentUM?.tosUM, onClick = onLinkClick)
SendingText(
footerText = if (sendFooter != TextReference.EMPTY || legalFooter != TextReference.EMPTY) {
combinedReference(sendFooter, legalFooter)
} else {
TextReference.EMPTY
},
)
}
NavigationPrimaryButton(
navigationUM.primaryButton,
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
)
}
}
}
}
@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,
)
},
)
}
}