Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-21 12:43:59 +03:00
commit fe2cfac54d
1140 changed files with 23493 additions and 8579 deletions

View file

@ -8,6 +8,8 @@ internal class DefaultSendFeatureToggles(
) : SendFeatureToggles {
override val isSendRedesignEnabled: Boolean
get() = featureToggles.isFeatureEnabled("SEND_REDESIGN_ENABLED")
override val isNFTSendRedesignEnabled: Boolean
get() = featureToggles.isFeatureEnabled("NFT_SEND_REDESIGN_ENABLED")
override val isSendWithSwapEnabled: Boolean
get() = featureToggles.isFeatureEnabled("SEND_VIA_SWAP_ENABLED")
}

View file

@ -1,128 +0,0 @@
package com.tangem.features.send.v2.common.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
/**
* Send screen analytics
*/
internal sealed class CommonSendAnalyticEvents(
category: String,
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category = category, event = event, params = params) {
/** Recipient address screen opened */
data class AddressScreenOpened(
val categoryName: String,
) : CommonSendAnalyticEvents(category = categoryName, event = "Address Screen Opened")
/** Amount screen opened */
data class AmountScreenOpened(
val categoryName: String,
) : CommonSendAnalyticEvents(category = categoryName, event = "Amount Screen Opened")
/** Fee screen opened */
data class FeeScreenOpened(
val categoryName: String,
) : CommonSendAnalyticEvents(category = categoryName, event = "Fee Screen Opened")
/** Confirmation screen opened */
data class ConfirmationScreenOpened(
val categoryName: String,
) : CommonSendAnalyticEvents(category = categoryName, event = "Confirm Screen Opened")
/** If transaction delays notification is present */
data class NoticeTransactionDelays(
val categoryName: String,
val token: String,
) : CommonSendAnalyticEvents(
category = categoryName,
event = "Notice - Transaction Delays Are Possible",
params = mapOf(TOKEN_PARAM to token),
)
/** If error occurs during send transactions */
data class TransactionError(
val categoryName: String,
val token: String,
val blockchain: String,
) : CommonSendAnalyticEvents(
category = categoryName,
event = "Error - Transaction Rejected",
params = mapOf(
TOKEN_PARAM to token,
BLOCKCHAIN to blockchain,
),
)
/** Close button clicked */
data class CloseButtonClicked(
val categoryName: String,
val source: SendScreenSource,
val isFromSummary: Boolean,
val isValid: Boolean,
) : CommonSendAnalyticEvents(
category = categoryName,
event = "Button - Close",
params = mapOf(
SOURCE to source.name,
"FromSummary" to if (isFromSummary) "Yes" else "No",
"isValid" to if (isValid) "Yes" else "No",
),
)
/** Share button clicked */
data class ShareButtonClicked(
val categoryName: String,
) : CommonSendAnalyticEvents(
category = categoryName,
event = "Button - Share",
)
/** Expore button clicked */
data class ExploreButtonClicked(
val categoryName: String,
) : CommonSendAnalyticEvents(
category = categoryName,
event = "Button - Explore",
)
/** Screen reopened from confirmation screen */
data class ScreenReopened(
val categoryName: String,
val source: SendScreenSource,
) : CommonSendAnalyticEvents(
category = categoryName,
event = "Screen Reopened",
params = mapOf(SOURCE to source.name),
)
/** Fee screen is closed with non empty nonce */
data class NonceInserted(
val categoryName: String,
val token: String,
val blockchain: String,
) : CommonSendAnalyticEvents(
category = categoryName,
event = "Nonce Inserted",
params = mapOf(
TOKEN_PARAM to token,
BLOCKCHAIN to blockchain,
),
)
companion object {
const val SEND_CATEGORY = "Token / Send"
const val NFT_SEND_CATEGORY = "NFT"
}
internal enum class SendScreenSource {
Address,
Amount,
Fee,
Confirm,
}
}

View file

@ -0,0 +1,70 @@
package com.tangem.features.send.v2.common.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import com.tangem.common.ui.amountScreen.utils.getFiatReference
import com.tangem.core.ui.components.rows.SelectorRowItem
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fee
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.impl.R
@Composable
internal fun FeeBlock(feeSelectorUM: FeeSelectorUM) {
if (feeSelectorUM !is FeeSelectorUM.Content) return
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(R.string.common_network_fee_title),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
Box(modifier = Modifier.padding(top = TangemTheme.dimens.spacing8)) {
val feeItemUM = feeSelectorUM.selectedFeeItem
val feeAmount = feeItemUM.fee.amount
SelectorRowItem(
title = feeItemUM.title,
iconRes = feeItemUM.iconRes,
preDot = remember {
stringReference(
feeAmount.value.format {
crypto(
symbol = feeAmount.currencySymbol,
decimals = feeAmount.decimals,
).fee(canBeLower = feeExtraInfo.isFeeApproximate)
},
)
},
postDot = remember {
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(),
)
}
}
}

View file

@ -5,17 +5,19 @@ import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.stack.animation.*
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.navigationButtons.NavigationButtonsBlockV2
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.decompose.ComposableContentComponent
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.send.confirm.SendConfirmComponent
import com.tangem.features.send.v2.send.success.SendConfirmSuccessComponent
@Composable
internal fun SendContent(
@ -34,9 +36,9 @@ internal fun SendContent(
Children(
stack = stackState,
animation = stackAnimation { child ->
when (child.instance) {
is SendConfirmSuccessComponent -> fade(minAlpha = 1.0f)
is SendConfirmComponent -> fade()
when (child.configuration) {
is CommonSendRoute.ConfirmSuccess -> fade(minAlpha = 1.0f)
is CommonSendRoute.Confirm -> fade()
else -> slide()
}
},
@ -45,7 +47,14 @@ internal fun SendContent(
it.instance.Content(Modifier.weight(1f))
}
if (stackState.active.configuration != CommonSendRoute.ConfirmSuccess) {
SendNavigationButtons(navigationUM = navigationUM)
NavigationButtonsBlockV2(
navigationUM = navigationUM,
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
)
}
}
}

View file

@ -1,135 +0,0 @@
package com.tangem.features.send.v2.common.ui
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
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.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.navigationButtons.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.buttons.common.TangemButton
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.singleEvent
@Composable
internal fun SendNavigationButtons(navigationUM: NavigationUM, modifier: Modifier = Modifier) {
val navigationUM = navigationUM as? NavigationUM.Content ?: return
Column(
modifier = modifier.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing16,
),
) {
SendDoneButtons(navigationUM.secondaryPairButtonsUM)
SendNavigationButton(
navigationUM = navigationUM,
)
}
}
@Composable
private fun SendNavigationButton(navigationUM: NavigationUM, modifier: Modifier = Modifier) {
val hapticFeedback = LocalHapticFeedback.current
val navigationUM = navigationUM as? NavigationUM.Content ?: return
val primaryButton = navigationUM.primaryButton
Row(modifier = modifier) {
AnimatedVisibility(
visible = navigationUM.prevButton != null,
enter = expandHorizontally(expandFrom = Alignment.End),
exit = shrinkHorizontally(shrinkTowards = Alignment.End),
) {
val wrappedNavigationUM = remember(this) { requireNotNull(navigationUM.prevButton) }
Row {
Icon(
painter = rememberVectorPainter(ImageVector.vectorResource(R.drawable.ic_back_24)),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(TangemTheme.colors.button.secondary)
.clickableSingle(onClick = wrappedNavigationUM.onClick)
.padding(12.dp),
)
SpacerW12()
}
}
TangemButton(
modifier = Modifier.fillMaxWidth(),
text = primaryButton.textReference.resolveReference(),
icon = primaryButton.iconRes?.let {
TangemButtonIconPosition.End(it)
} ?: TangemButtonIconPosition.None,
enabled = primaryButton.isEnabled,
onClick = {
if (primaryButton.isHapticClick) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
primaryButton.onClick()
},
showProgress = false,
colors = TangemButtonsDefaults.primaryButtonColors,
textStyle = TangemTheme.typography.subtitle1,
)
}
}
@Composable
private fun SendDoneButtons(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(modifier = Modifier.padding(bottom = 12.dp)) {
SecondaryButtonIconStart(
text = leftButton.textReference.resolveReference(),
iconResId = leftButton.iconRes!!,
onClick = {
singleEvent {
leftButton.onClick()
}
},
modifier = Modifier.weight(1f),
)
SpacerW12()
SecondaryButtonIconStart(
text = rightButton.textReference.resolveReference(),
iconResId = rightButton.iconRes!!,
onClick = {
singleEvent {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
rightButton.onClick()
}
},
modifier = Modifier.weight(1f),
)
}
}
}

View file

@ -1,51 +0,0 @@
package com.tangem.features.send.v2.common.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.Keyboard
import com.tangem.core.ui.components.keyboardAsState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun SendingText(footerText: TextReference, modifier: Modifier = Modifier) {
var isVisibleProxy by remember { mutableStateOf(footerText != TextReference.EMPTY) }
val keyboard by keyboardAsState()
// the text should appear when the keyboard is closed
LaunchedEffect(footerText != TextReference.EMPTY, keyboard) {
if (footerText != TextReference.EMPTY && keyboard is Keyboard.Opened) {
return@LaunchedEffect
}
isVisibleProxy = footerText != TextReference.EMPTY
}
AnimatedVisibility(
visible = isVisibleProxy,
modifier = modifier,
enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(),
exit = fadeOut(tween(durationMillis = 300)),
label = "Animate show sending state text",
) {
Text(
text = footerText.resolveAnnotatedReference(),
textAlign = TextAlign.Center,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
)
}
}

View file

@ -1,66 +0,0 @@
package com.tangem.features.send.v2.common.utils
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.transaction.Fee
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.domain.appcurrency.model.AppCurrency
import com.tangem.features.send.v2.impl.R
import com.tangem.utils.StringsSigns.COMA_SIGN
internal fun getTronTokenFeeSendingText(fee: Fee.Tron, fiatFee: String, fiatSending: TextReference): TextReference {
val suffix = when {
fee.remainingEnergy == 0L -> {
resourceReference(
R.string.send_summary_transaction_description_suffix_including,
wrappedList(fiatFee),
)
}
fee.feeEnergy <= fee.remainingEnergy -> {
resourceReference(
R.string.send_summary_transaction_description_suffix_fee_covered,
wrappedList(fee.feeEnergy),
)
}
else -> {
resourceReference(
R.string.send_summary_transaction_description_suffix_fee_reduced,
wrappedList(fee.remainingEnergy),
)
}
}
val prefix = resourceReference(
R.string.send_summary_transaction_description_prefix,
wrappedList(fiatSending),
)
return combinedReference(prefix, stringReference("$COMA_SIGN "), suffix)
}
internal fun formatFooterFiatFee(
amount: Amount?,
isFeeConvertibleToFiat: Boolean,
isFeeApproximate: Boolean,
appCurrency: AppCurrency,
): String {
return if (isFeeConvertibleToFiat) {
amount?.value.format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
}
} else {
amount?.value.format {
crypto(
decimals = amount?.decimals ?: 0,
symbol = amount?.currencySymbol.orEmpty(),
).fee(
canBeLower = isFeeApproximate,
)
}
}
}

View file

@ -1,18 +1,30 @@
package com.tangem.features.send.v2.entrypoint
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.extensions.compose.stack.animation.fade
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.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
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.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
import com.tangem.features.send.v2.api.SendComponent
import com.tangem.features.send.v2.api.SendEntryPointComponent
import com.tangem.features.send.v2.entrypoint.model.SendEntryPoint
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.entrypoint.model.SendEntryPointModel
import com.tangem.features.swap.v2.api.SendWithSwapComponent
import dagger.assisted.Assisted
@ -24,9 +36,17 @@ internal class DefaultSendEntryPointComponent @AssistedInject constructor(
@Assisted private val params: SendEntryPointComponent.Params,
sendWithSwapComponentFactory: SendWithSwapComponent.Factory,
sendComponentFactory: SendComponent.Factory,
private val chooseManagedTokensComponentFactory: ChooseManagedTokensComponent.Factory,
) : SendEntryPointComponent, AppComponentContext by appComponentContext {
private val model: SendEntryPointModel = getOrCreateModel(params = params)
private val stackNavigation = StackNavigation<SendEntryRoute>()
private val innerRouter = InnerRouter<SendEntryRoute>(
stackNavigation = stackNavigation,
popCallback = { onChildBack() },
)
private val model: SendEntryPointModel = getOrCreateModel(params = params, router = innerRouter)
private val sendWithSwapComponent = sendWithSwapComponentFactory.create(
context = child("sendEntrySendWithSwap"),
@ -46,18 +66,77 @@ internal class DefaultSendEntryPointComponent @AssistedInject constructor(
),
)
private val childStack = childStack(
key = "sendEntryStack",
source = stackNavigation,
serializer = null,
initialConfiguration = SendEntryRoute.Send,
handleBackButton = true,
childFactory = { configuration, factoryContext ->
getChildComponent(
configuration = configuration,
factoryContext = childByContext(
componentContext = factoryContext,
router = innerRouter,
),
)
},
)
@Composable
override fun Content(modifier: Modifier) {
val sendEntryState by model.sendEntryPointState.collectAsStateWithLifecycle()
val childStackValue by childStack.subscribeAsState()
sendComponent.Content(modifier)
Children(
stack = childStackValue,
animation = stackAnimation { child ->
when (child.configuration) {
SendEntryRoute.Send,
SendEntryRoute.SendWithSwap,
-> fade()
is SendEntryRoute.ChooseToken -> slide(orientation = Orientation.Vertical) + fade()
}
},
) { child ->
child.instance.Content(modifier.fillMaxSize())
}
}
AnimatedVisibility(
visible = sendEntryState == SendEntryPoint.SendWithSwap,
enter = fadeIn(),
exit = fadeOut(),
) {
sendWithSwapComponent.Content(modifier)
private fun getChildComponent(
configuration: SendEntryRoute,
factoryContext: AppComponentContext,
): ComposableContentComponent = when (configuration) {
is SendEntryRoute.ChooseToken -> getManagedTokensComponent(
componentContext = factoryContext,
showSendViaSwapNotification = configuration.showSendViaSwapNotification,
)
SendEntryRoute.Send -> sendComponent
SendEntryRoute.SendWithSwap -> sendWithSwapComponent
}
private fun getManagedTokensComponent(
componentContext: ComponentContext,
showSendViaSwapNotification: Boolean,
): ChooseManagedTokensComponent {
return chooseManagedTokensComponentFactory.create(
context = childByContext(componentContext),
params = ChooseManagedTokensComponent.Params(
userWalletId = params.userWalletId,
initialCurrency = params.cryptoCurrency,
source = ChooseManagedTokensComponent.Source.SendViaSwap,
selectedCurrency = null,
showSendViaSwapNotification = showSendViaSwapNotification,
callback = model,
analyticsCategoryName = CommonSendAnalyticEvents.SEND_CATEGORY,
),
)
}
private fun onChildBack() {
if (childStack.value.backStack.isEmpty()) {
router.pop()
} else {
stackNavigation.pop()
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.features.send.v2.entrypoint
import com.tangem.core.decompose.navigation.Route
internal sealed class SendEntryRoute : Route {
data object Send : SendEntryRoute()
data object SendWithSwap : SendEntryRoute()
data class ChooseToken(
val showSendViaSwapNotification: Boolean,
) : SendEntryRoute()
}

View file

@ -1,80 +1,74 @@
package com.tangem.features.send.v2.entrypoint.model
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.notifications.NotificationId
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.decompose.navigation.Router
import com.tangem.domain.notifications.ShouldShowNotificationUseCase
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
import com.tangem.features.send.v2.api.SendComponent
import com.tangem.features.send.v2.api.SendEntryPointComponent
import com.tangem.features.send.v2.entrypoint.SendEntryRoute
import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateTrigger
import com.tangem.features.swap.v2.api.SendWithSwapComponent
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkListener
import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import jakarta.inject.Inject
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Suppress("LongParameterList")
@ModelScoped
internal class SendEntryPointModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
val appRouter: AppRouter,
private val swapChooseTokenNetworkListener: SwapChooseTokenNetworkListener,
val router: Router,
private val sendAmountUpdateTrigger: SendAmountUpdateTrigger,
private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger,
) : Model(), SendComponent.ModelCallback, SendWithSwapComponent.ModelCallback {
private val shouldShowNotificationUseCase: ShouldShowNotificationUseCase,
) : Model(),
SendComponent.ModelCallback,
SendWithSwapComponent.ModelCallback,
ChooseManagedTokensComponent.ModelCallback {
private val params: SendEntryPointComponent.Params = paramsContainer.require()
val sendEntryPointState: StateFlow<SendEntryPoint>
field = MutableStateFlow(SendEntryPoint.SendVanilla)
private var swapChooseTokenListenerJobHolder = JobHolder()
private var lastSavedAmount = ""
override fun onConvertToAnotherToken(lastAmount: String) {
appRouter.push(
AppRoute.ChooseManagedTokens(
userWalletId = params.userWalletId,
initialCurrency = params.cryptoCurrency,
selectedCurrency = null,
source = AppRoute.ChooseManagedTokens.Source.SendViaSwap,
),
)
observeChooseSelectToken(lastAmount)
lastSavedAmount = lastAmount
modelScope.launch {
val showSendViaSwapNotification = shouldShowNotificationUseCase(
NotificationId.SendViaSwapTokenSelectorNotification.key,
)
router.push(
SendEntryRoute.ChooseToken(
showSendViaSwapNotification = showSendViaSwapNotification,
),
)
}
}
override fun onCloseSwap(lastAmount: String) {
lastSavedAmount = lastAmount
modelScope.launch {
if (lastAmount.isNotBlank()) {
sendAmountUpdateTrigger.triggerUpdateAmount(lastAmount)
}
triggerScreenUpdate(SendEntryPoint.SendVanilla)
router.replaceAll(SendEntryRoute.Send)
}
}
private fun observeChooseSelectToken(lastAmount: String) {
swapChooseTokenNetworkListener.swapChooseTokenNetworkResultFlow
.onEach { currency ->
if (lastAmount.isNotBlank()) {
swapAmountUpdateTrigger.triggerUpdateAmount(lastAmount)
}
triggerScreenUpdate(SendEntryPoint.SendWithSwap)
@Suppress("MagicNumber")
override fun onResult() {
modelScope.launch {
if (lastSavedAmount.isNotBlank()) {
swapAmountUpdateTrigger.triggerUpdateAmount(lastSavedAmount)
}
.launchIn(modelScope)
.saveIn(swapChooseTokenListenerJobHolder)
// Workaround in order to execute correct exit animation on SendEntryRoute.ChooseToken
router.pop()
delay(10L)
router.replaceAll(SendEntryRoute.SendWithSwap)
}
}
private fun triggerScreenUpdate(entry: SendEntryPoint) {
swapChooseTokenListenerJobHolder.cancel()
sendEntryPointState.update { entry }
override fun onBack() {
router.pop()
}
}
enum class SendEntryPoint {
SendVanilla,
SendWithSwap,
}

View file

@ -6,7 +6,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.core.decompose.context.AppComponentContext
@ -19,6 +18,7 @@ import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.send.v2.feeselector.model.FeeSelectorModel
import com.tangem.features.send.v2.feeselector.ui.FeeSelectorBlockContent
import com.tangem.utils.extensions.isSingleItem
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -50,6 +50,7 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
callback = model,
feeStateConfiguration = params.feeStateConfiguration,
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen,
analyticsCategoryName = params.analyticsCategoryName,
),
onDismiss = {
model.feeSelectorBottomSheet.dismiss()
@ -73,13 +74,15 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
val state by model.uiState.collectAsStateWithLifecycle()
val bottomSheet by bottomSheetSlot.subscribeAsState()
val isScreenSource = params.feeDisplaySource == FeeSelectorParams.FeeDisplaySource.Screen
val isNotSingleFee = (state as? FeeSelectorUM.Content)?.feeItems?.isSingleItem() == false
FeeSelectorBlockContent(
state = state,
onReadMoreClick = model::onReadMoreClicked,
modifier = modifier
.conditional(params.feeDisplaySource == FeeSelectorParams.FeeDisplaySource.Screen) {
.conditional(isScreenSource && isNotSingleFee) {
Modifier.clickable {
model.feeSelectorBottomSheet.activate(Unit)
model.showFeeSelector()
}
},
)

View file

@ -3,8 +3,10 @@ package com.tangem.features.send.v2.feeselector.model
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.blockchain.common.AmountType
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -12,13 +14,19 @@ import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.NonceInserted
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
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.params.FeeSelectorParams
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorCheckReloadListener
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorCheckReloadTrigger
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadListener
import com.tangem.features.send.v2.api.subcomponents.feeSelector.analytics.CommonSendFeeAnalyticEvents
import com.tangem.features.send.v2.api.subcomponents.feeSelector.analytics.CommonSendFeeAnalyticEvents.GasPriceInserter
import com.tangem.features.send.v2.feeselector.model.transformers.*
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.transformer.update
@ -43,6 +51,7 @@ internal class FeeSelectorModel @Inject constructor(
private val feeSelectorCheckReloadTrigger: FeeSelectorCheckReloadTrigger,
private val feeSelectorAlertFactory: FeeSelectorAlertFactory,
override val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model(), FeeSelectorIntents, FeeSelectorModelCallback {
private val params = paramsContainer.require<FeeSelectorParams>()
@ -112,6 +121,11 @@ internal class FeeSelectorModel @Inject constructor(
}
override fun onFeeItemSelected(feeItem: FeeItem) {
if (feeItem is FeeItem.Custom) {
analyticsEventHandler.send(
CommonSendFeeAnalyticEvents.CustomFeeButtonClicked(categoryName = params.analyticsCategoryName),
)
}
uiState.update(FeeItemSelectedTransformer(feeItem))
}
@ -132,6 +146,27 @@ internal class FeeSelectorModel @Inject constructor(
}
override fun onDoneClick() {
val feeSelectorUM = uiState.value as? FeeSelectorUM.Content ?: return
analyticsEventHandler.send(
CommonSendFeeAnalyticEvents.SelectedFee(
categoryName = params.analyticsCategoryName,
feeType = feeSelectorUM.toAnalyticType(),
),
)
val isCustomFeeEdited = feeSelectorUM.selectedFeeItem.fee.amount.value != feeSelectorUM.fees.normal.amount.value
if (feeSelectorUM.selectedFeeItem is FeeItem.Custom && isCustomFeeEdited) {
analyticsEventHandler.send(GasPriceInserter(categoryName = params.analyticsCategoryName))
}
if (feeSelectorUM.feeNonce is FeeNonce.Nonce) {
analyticsEventHandler.send(
NonceInserted(
categoryName = params.analyticsCategoryName,
token = params.feeCryptoCurrencyStatus.currency.symbol,
blockchain = params.feeCryptoCurrencyStatus.currency.network.name,
),
)
}
(params as? FeeSelectorParams.FeeSelectorDetailsParams)?.callback?.onFeeResult(uiState.value)
}
@ -140,6 +175,19 @@ internal class FeeSelectorModel @Inject constructor(
feeSelectorBottomSheet.dismiss()
}
fun showFeeSelector() {
analyticsEventHandler.send(
CommonSendAnalyticEvents.FeeScreenOpened(categoryName = params.analyticsCategoryName),
)
analyticsEventHandler.send(
CommonSendAnalyticEvents.ScreenReopened(
categoryName = params.analyticsCategoryName,
source = SendScreenSource.Fee,
),
)
feeSelectorBottomSheet.activate(Unit)
}
private fun subscribeOnFeeReloadTriggerUpdates() {
feeSelectorReloadListener.reloadTriggerFlow
.onEach { data ->

View file

@ -3,7 +3,7 @@ package com.tangem.features.send.v2.feeselector.model.transformers
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.FeeItem
import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents

View file

@ -4,7 +4,7 @@ import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents

View file

@ -2,7 +2,7 @@ package com.tangem.features.send.v2.feeselector.model.transformers
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.FeeItem
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents

View file

@ -4,7 +4,7 @@ import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.*
import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents

View file

@ -39,6 +39,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.features.send.v2.api.entity.*
import com.tangem.features.send.v2.impl.R
import com.tangem.utils.extensions.isSingleItem
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
@ -118,12 +119,11 @@ private fun FeeSelectorStaticPart(onReadMoreClick: () -> Unit, modifier: Modifie
text = annotatedString,
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing6)
.size(TangemTheme.dimens.size16),
.size(TangemTheme.dimens.size16)
.clip(CircleShape),
content = { contentModifier ->
Icon(
modifier = contentModifier
.size(TangemTheme.dimens.size16)
.clip(CircleShape),
modifier = contentModifier.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_token_info_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
@ -176,12 +176,14 @@ private fun FeeContent(state: FeeSelectorUM.Content, modifier: Modifier = Modifi
textAlign = TextAlign.End,
modifier = Modifier.padding(start = TangemTheme.dimens.spacing4),
)
Icon(
modifier = Modifier.size(width = 18.dp, height = 24.dp),
painter = painterResource(id = R.drawable.ic_select_18_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
if (!state.feeItems.isSingleItem()) {
Icon(
modifier = Modifier.size(width = 18.dp, height = 24.dp),
painter = painterResource(id = R.drawable.ic_select_18_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
}
@ -220,7 +222,7 @@ private class FeeSelectorUMProvider : PreviewParameterProvider<FeeSelectorUM> {
),
FeeSelectorUM.Content(
isPrimaryButtonEnabled = false,
feeItems = persistentListOf(maxFeeItem),
feeItems = persistentListOf(lowFeeItem, maxFeeItem),
selectedFeeItem = maxFeeItem,
feeExtraInfo = FeeExtraInfo(
isFeeApproximate = false,

View file

@ -79,7 +79,7 @@ internal fun FeeSelectorModalBottomSheet(
FeeSelectorItems(
state = state,
feeSelectorIntents = feeSelectorIntents,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 13.dp),
modifier = Modifier.padding(vertical = 4.dp, horizontal = 12.dp),
)
},
footer = {
@ -115,7 +115,6 @@ private fun FeeTitle(feeDisplaySource: FeeSelectorParams.FeeDisplaySource, onDis
}
}
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
private fun FeeSelectorItems(
state: FeeSelectorUM.Content,
@ -141,114 +140,9 @@ private fun FeeSelectorItems(
)
val itemModifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors.background.primary)
.selectedBorder(isSelected = isSelected)
.clickableSingle(onClick = { feeSelectorIntents.onFeeItemSelected(item) })
when (item) {
is FeeItem.Suggested -> RegularFeeItemContent(
modifier = itemModifier,
title = item.title,
iconRes = R.drawable.ic_star_mini_24,
iconBackgroundColor = iconBackgroundColor,
iconTint = iconTint,
preDot = stringReference(
item.fee.amount.value.format {
crypto(
symbol = item.fee.amount.currencySymbol,
decimals = item.fee.amount.decimals,
).fee(canBeLower = state.feeExtraInfo.isFeeApproximate)
},
),
postDot = if (feeFiatRateUM != null) {
getFiatReference(
value = item.fee.amount.value,
rate = feeFiatRateUM.rate,
appCurrency = feeFiatRateUM.appCurrency,
)
} else {
null
},
ellipsizeOffset = item.fee.amount.currencySymbol.length,
showDivider = !isSelected && !lastItem,
)
is FeeItem.Slow -> RegularFeeItemContent(
modifier = itemModifier,
title = resourceReference(R.string.common_fee_selector_option_slow),
iconRes = R.drawable.ic_tortoise_24,
iconBackgroundColor = iconBackgroundColor,
iconTint = iconTint,
preDot = stringReference(
item.fee.amount.value.format {
crypto(
symbol = item.fee.amount.currencySymbol,
decimals = item.fee.amount.decimals,
).fee(canBeLower = state.feeExtraInfo.isFeeApproximate)
},
),
postDot = if (feeFiatRateUM != null) {
getFiatReference(
value = item.fee.amount.value,
rate = feeFiatRateUM.rate,
appCurrency = feeFiatRateUM.appCurrency,
)
} else {
null
},
ellipsizeOffset = item.fee.amount.currencySymbol.length,
showDivider = !isSelected && !lastItem,
)
is FeeItem.Market -> RegularFeeItemContent(
modifier = itemModifier,
title = resourceReference(R.string.common_fee_selector_option_market),
iconRes = R.drawable.ic_bird_24,
iconBackgroundColor = iconBackgroundColor,
iconTint = iconTint,
preDot = stringReference(
item.fee.amount.value.format {
crypto(
symbol = item.fee.amount.currencySymbol,
decimals = item.fee.amount.decimals,
).fee(canBeLower = state.feeExtraInfo.isFeeApproximate)
},
),
postDot = if (feeFiatRateUM != null) {
getFiatReference(
value = item.fee.amount.value,
rate = feeFiatRateUM.rate,
appCurrency = feeFiatRateUM.appCurrency,
)
} else {
null
},
ellipsizeOffset = item.fee.amount.currencySymbol.length,
showDivider = !isSelected && !lastItem,
)
is FeeItem.Fast -> RegularFeeItemContent(
modifier = itemModifier,
title = resourceReference(R.string.common_fee_selector_option_fast),
iconRes = R.drawable.ic_hare_24,
iconBackgroundColor = iconBackgroundColor,
iconTint = iconTint,
preDot = stringReference(
item.fee.amount.value.format {
crypto(
symbol = item.fee.amount.currencySymbol,
decimals = item.fee.amount.decimals,
).fee(canBeLower = state.feeExtraInfo.isFeeApproximate)
},
),
postDot = if (feeFiatRateUM != null) {
getFiatReference(
value = item.fee.amount.value,
rate = feeFiatRateUM.rate,
appCurrency = feeFiatRateUM.appCurrency,
)
} else {
null
},
ellipsizeOffset = item.fee.amount.currencySymbol.length,
showDivider = !isSelected && !lastItem,
)
is FeeItem.Custom -> CustomFeeBlock(
modifier = itemModifier,
customFee = item,
@ -258,6 +152,32 @@ private fun FeeSelectorItems(
onValueChange = feeSelectorIntents::onCustomFeeValueChange,
nonce = state.feeNonce,
)
else -> RegularFeeItemContent(
modifier = itemModifier,
title = item.title,
iconRes = item.iconRes,
iconBackgroundColor = iconBackgroundColor,
iconTint = iconTint,
preDot = stringReference(
item.fee.amount.value.format {
crypto(
symbol = item.fee.amount.currencySymbol,
decimals = item.fee.amount.decimals,
).fee(canBeLower = state.feeExtraInfo.isFeeApproximate)
},
),
postDot = if (feeFiatRateUM != null) {
getFiatReference(
value = item.fee.amount.value,
rate = feeFiatRateUM.rate,
appCurrency = feeFiatRateUM.appCurrency,
)
} else {
null
},
ellipsizeOffset = item.fee.amount.currencySymbol.length,
showDivider = !isSelected && !lastItem,
)
}
}
}
@ -363,7 +283,7 @@ private fun ExpandedCustomFeeItems(
showDivider = false,
modifier = Modifier
.background(
color = TangemTheme.colors.background.action,
color = TangemTheme.colors.background.primary,
shape = TangemTheme.shapes.roundedCornersXMedium,
),
)
@ -495,7 +415,14 @@ private class FeeSelectorUMContentProvider : CollectionPreviewParameterProvider<
fee = Fee.Common(Amount(value = BigDecimal("0.1"), blockchain = Blockchain.Ethereum)),
),
FeeItem.Slow(fee = Fee.Common(Amount(value = BigDecimal("0.01"), blockchain = Blockchain.Ethereum))),
FeeItem.Market(fee = Fee.Common(Amount(value = BigDecimal("0.02"), blockchain = Blockchain.Ethereum))),
FeeItem.Market(
fee = Fee.Common(
Amount(
value = BigDecimal("0.02"),
blockchain = Blockchain.Ethereum,
),
),
),
FeeItem.Fast(fee = Fee.Common(Amount(value = BigDecimal("0.03"), blockchain = Blockchain.Ethereum))),
customFeeItem,
),

View file

@ -12,6 +12,7 @@ import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.value.ObserveLifecycleMode
import com.arkivanov.decompose.value.subscribe
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
@ -20,13 +21,13 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.SendComponent
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.common.ui.SendContent
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.impl.R
@ -37,7 +38,6 @@ import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponent
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponentParams
import com.tangem.features.send.v2.subcomponents.destination.DefaultSendDestinationBlockComponent
import com.tangem.features.send.v2.subcomponents.destination.DefaultSendDestinationComponent
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponent
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams
import dagger.assisted.Assisted
@ -89,13 +89,15 @@ internal class DefaultSendComponent @AssistedInject constructor(
) { stack ->
componentScope.launch {
when (val activeComponent = stack.active.instance) {
is SendConfirmComponent -> if (model.currentRoute.value.isEditMode) {
is SendConfirmComponent -> {
analyticsEventHandler.send(
CommonSendAnalyticEvents.ConfirmationScreenOpened(
categoryName = model.analyticCategoryName,
),
)
activeComponent.updateState(model.uiState.value)
if (model.currentRoute.value.isEditMode) {
activeComponent.updateState(model.uiState.value)
}
}
is SendAmountComponent -> {
analyticsEventHandler.send(
@ -125,7 +127,11 @@ internal class DefaultSendComponent @AssistedInject constructor(
val stackState by childStack.subscribeAsState()
val state by model.uiState.collectAsStateWithLifecycle()
BackHandler(onBack = ::onChildBack)
BackHandler(
onBack = {
(state.navigationUM as? NavigationUM.Content)?.backIconClick() ?: onChildBack()
},
)
SendContent(
navigationUM = state.navigationUM,
stackState = stackState,
@ -149,7 +155,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
currentRoute = model.currentRoute.filterIsInstance<CommonSendRoute.Destination>(),
isBalanceHidingFlow = model.isBalanceHiddenFlow,
analyticsCategoryName = model.analyticCategoryName,
title = resourceReference(R.string.send_recipient_label),
title = resourceReference(R.string.common_address),
userWalletId = params.userWalletId,
cryptoCurrency = params.currency,
callback = model,
@ -247,7 +253,6 @@ internal class DefaultSendComponent @AssistedInject constructor(
val destinationAddress = (state.destinationUM as? DestinationUM.Content)?.addressTextField?.value
val txUrl = (state.confirmUM as? ConfirmUM.Success)?.txUrl
val cryptoCurrencyStatus = model.cryptoCurrencyStatusFlow.value
val feeCryptoCurrencyStatus = model.feeCryptoCurrencyStatusFlow.value
if (sendAmount == null ||
destinationAddress == null ||
@ -272,29 +277,10 @@ internal class DefaultSendComponent @AssistedInject constructor(
onClick = {},
)
val feeBlockComponent = SendFeeBlockComponent(
appComponentContext = child("sendConfirmFeeBlock"),
params = SendFeeComponentParams.FeeBlockParams(
state = model.uiState.value.feeUM,
analyticsCategoryName = model.analyticCategoryName,
userWallet = model.userWallet,
cryptoCurrencyStatus = cryptoCurrencyStatus,
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
appCurrency = model.appCurrency,
sendAmount = sendAmount,
destinationAddress = destinationAddress,
blockClickEnableFlow = MutableStateFlow(true),
onLoadFee = model::loadFee,
),
onResult = { },
onClick = {},
)
return SendConfirmSuccessComponent(
appComponentContext = factoryContext,
params = SendConfirmSuccessComponent.Params(
sendUMFlow = model.uiState,
feeBlockComponent = feeBlockComponent,
destinationBlockComponent = destinationBlockComponent,
analyticsCategoryName = model.analyticCategoryName,
currentRoute = model.currentRoute,

View file

@ -7,7 +7,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
import com.tangem.core.analytics.models.AnalyticsParam.Key.NONCE
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
import com.tangem.core.ui.extensions.capitalize
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
/**
* Send screen analytics
@ -32,4 +32,15 @@ internal sealed class SendAnalyticEvents(
NONCE to nonceNotEmpty.toString().capitalize(),
),
)
data class ConvertTokenButtonClicked(
val token: String,
val blockchain: String,
) : SendAnalyticEvents(
event = "Button - Convert Token",
params = mapOf(
TOKEN_PARAM to token,
BLOCKCHAIN to blockchain,
),
)
}

View file

@ -11,9 +11,9 @@ import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.SendNotificationsComponent
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
@ -104,6 +104,7 @@ internal class SendConfirmComponent(
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
feeStateConfiguration = model.feeStateConfiguration,
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen,
analyticsCategoryName = params.analyticsCategoryName,
),
onResult = model::onFeeResult,
)

View file

@ -26,6 +26,7 @@ import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
import com.tangem.domain.settings.NeverShowTapHelpUseCase
@ -37,6 +38,8 @@ import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.features.send.v2.api.SendNotificationsComponent
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
import com.tangem.features.send.v2.api.entity.FeeNonce
import com.tangem.features.send.v2.api.params.FeeSelectorParams.FeeStateConfiguration
@ -47,8 +50,6 @@ import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificat
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.common.SendBalanceUpdater
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.send.analytics.SendAnalyticHelper
@ -325,8 +326,12 @@ internal class SendConfirmModel @Inject constructor(
),
)
if (userWallet is UserWallet.Hot) {
return // TODO [REDACTED_TASK_KEY] [Hot Wallet] Email feedback flow
}
val cardInfo =
getCardInfoUseCase(userWallet.requireColdWallet().scanResponse).getOrNull() ?: return // TODO [REDACTED_TASK_KEY]
getCardInfoUseCase(userWallet.requireColdWallet().scanResponse).getOrNull() ?: return
modelScope.launch {
sendFeedbackEmailUseCase(type = FeedbackEmailType.TransactionSendingProblem(cardInfo = cardInfo))
@ -560,7 +565,7 @@ internal class SendConfirmModel @Inject constructor(
state.copy(
navigationUM = NavigationUM.Content(
title = if (state.isRedesignEnabled) {
stringReference("")
resourceReference(id = R.string.common_send)
} else {
resourceReference(
id = R.string.send_summary_title,
@ -589,7 +594,11 @@ internal class SendConfirmModel @Inject constructor(
isValid = confirmUM.isPrimaryButtonEnabled,
),
)
appRouter.pop()
if (state.isRedesignEnabled) {
router.pop()
} else {
appRouter.pop()
}
},
primaryButton = primaryButtonUM(),
prevButton = null,
@ -623,7 +632,8 @@ internal class SendConfirmModel @Inject constructor(
}
else -> resourceReference(R.string.common_send)
},
iconRes = R.drawable.ic_tangem_24.takeIf { isReadyToSend },
iconRes = R.drawable.ic_tangem_24,
isIconVisible = isReadyToSend,
isEnabled = confirmUM.isPrimaryButtonEnabled,
isHapticClick = isReadyToSend,
onClick = {

View file

@ -14,10 +14,10 @@ import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.parseToBigDecimal
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.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.utils.formatFooterFiatFee
import com.tangem.features.send.v2.api.utils.getTronTokenFeeSendingText
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.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

@ -12,12 +12,12 @@ 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.domain.models.currency.CryptoCurrency
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
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.api.utils.formatFooterFiatFee
import com.tangem.features.send.v2.api.utils.getTronTokenFeeSendingText
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.impl.R
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.toPersistentList

View file

@ -4,6 +4,7 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
@ -12,8 +13,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
@ -22,7 +23,6 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.common.ui.SendingText
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.common.ui.tapHelp
import com.tangem.features.send.v2.impl.R
@ -49,9 +49,13 @@ internal fun SendConfirmContent(
) {
val confirmUM = sendUM.confirmUM as? ConfirmUM.Content
Column {
Column(
modifier = Modifier.fillMaxSize(),
) {
LazyColumn(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
modifier = Modifier
.weight(1f)
.padding(horizontal = TangemTheme.dimens.spacing16),
) {
blocks(
uiState = sendUM,
@ -74,7 +78,6 @@ internal fun SendConfirmContent(
)
}
}
SpacerHMax()
SendingText(footerText = confirmUM?.sendingFooter ?: TextReference.EMPTY)
}
}

View file

@ -23,6 +23,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.models.wallet.requireColdWallet
@ -32,7 +33,6 @@ import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
import com.tangem.domain.transaction.usecase.GetFeeUseCase
@ -41,6 +41,8 @@ import com.tangem.domain.wallets.models.GetUserWalletError
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.send.v2.api.SendComponent
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.entity.PredefinedValues
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponent
@ -48,9 +50,8 @@ import com.tangem.features.send.v2.api.subcomponents.destination.entity.Destinat
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.common.CommonSendRoute.*
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents
import com.tangem.features.send.v2.send.confirm.SendConfirmComponent
import com.tangem.features.send.v2.send.success.SendConfirmSuccessComponent
import com.tangem.features.send.v2.send.ui.state.SendUM
@ -227,6 +228,12 @@ internal class SendModel @Inject constructor(
}
override fun onConvertToAnotherToken(lastAmount: String) {
analyticsEventHandler.send(
SendAnalyticEvents.ConvertTokenButtonClicked(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
),
)
params.callback?.onConvertToAnotherToken(lastAmount = lastAmount)
}
@ -465,8 +472,12 @@ internal class SendModel @Inject constructor(
),
)
if (userWallet is UserWallet.Hot) {
return // TODO [REDACTED_TASK_KEY] [Hot Wallet] Email feedback flow
}
val cardInfo =
getCardInfoUseCase(userWallet.requireColdWallet().scanResponse).getOrNull() ?: return // TODO [REDACTED_TASK_KEY]
getCardInfoUseCase(userWallet.requireColdWallet().scanResponse).getOrNull() ?: return
modelScope.launch {
sendFeedbackEmailUseCase(type = FeedbackEmailType.TransactionSendingProblem(cardInfo = cardInfo))

View file

@ -12,7 +12,6 @@ import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.send.success.model.SendConfirmSuccessModel
import com.tangem.features.send.v2.send.success.ui.SendConfirmSuccessContent
import com.tangem.features.send.v2.send.ui.state.SendUM
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@ -23,7 +22,6 @@ internal class SendConfirmSuccessComponent(
private val model: SendConfirmSuccessModel = getOrCreateModel(params = params)
private val destinationBlockComponent: SendDestinationBlockComponent = params.destinationBlockComponent
private val feeBlockComponent: SendFeeBlockComponent = params.feeBlockComponent
@Composable
override fun Content(modifier: Modifier) {
@ -31,14 +29,12 @@ internal class SendConfirmSuccessComponent(
SendConfirmSuccessContent(
sendUM = state,
destinationBlockComponent = destinationBlockComponent,
feeBlockComponent = feeBlockComponent,
)
}
data class Params(
val sendUMFlow: StateFlow<SendUM>,
val destinationBlockComponent: SendDestinationBlockComponent,
val feeBlockComponent: SendFeeBlockComponent,
val analyticsCategoryName: String,
val currentRoute: Flow<CommonSendRoute>,
val txUrl: String,

View file

@ -12,14 +12,17 @@ import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.send.success.SendConfirmSuccessComponent
import com.tangem.features.send.v2.send.ui.state.SendUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
@Stable

View file

@ -1,17 +1,18 @@
package com.tangem.features.send.v2.send.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.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
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.amountScreen.ui.AmountBlock
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.common.ui.navigationButtons.NavigationButtonsBlockV2
import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
@ -20,19 +21,14 @@ import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.core.ui.utils.toPx
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
import com.tangem.features.send.v2.common.ui.SendNavigationButtons
import com.tangem.features.send.v2.common.ui.FeeBlock
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.send.ui.state.SendUM
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
import kotlinx.coroutines.delay
@Composable
internal fun SendConfirmSuccessContent(
sendUM: SendUM,
destinationBlockComponent: SendDestinationBlockComponent,
feeBlockComponent: SendFeeBlockComponent,
) {
internal fun SendConfirmSuccessContent(sendUM: SendUM, destinationBlockComponent: SendDestinationBlockComponent) {
var visible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
@ -50,13 +46,17 @@ internal fun SendConfirmSuccessContent(
exit = slideOutVertically().plus(fadeOut()),
label = "Animate success content",
) {
Column {
Box(
modifier = Modifier
.fillMaxSize()
.background(TangemTheme.colors.background.tertiary),
) {
Column(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.scrollable(
state = rememberScrollState(),
orientation = Orientation.Horizontal,
orientation = Orientation.Vertical,
),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
@ -80,10 +80,20 @@ internal fun SendConfirmSuccessContent(
onClick = {},
)
destinationBlockComponent.Content(modifier = Modifier)
feeBlockComponent.Content(modifier = Modifier)
FeeBlock(feeSelectorUM = sendUM.feeSelectorUM)
Spacer(Modifier.height(60.dp))
}
SpacerHMax()
SendNavigationButtons(navigationUM = sendUM.navigationUM)
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,
),
)
}
}
}

View file

@ -18,17 +18,17 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.nft.component.NFTDetailsBlockComponent
import com.tangem.features.send.v2.api.NFTSendComponent
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.common.ui.SendContent
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.sendnft.confirm.NFTSendConfirmComponent
import com.tangem.features.send.v2.sendnft.model.NFTSendModel
import com.tangem.features.send.v2.sendnft.success.NFTSendSuccessComponent
import com.tangem.features.send.v2.subcomponents.destination.DefaultSendDestinationComponent
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponent
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams
@ -42,7 +42,8 @@ import java.math.BigDecimal
internal class DefaultNFTSendComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val params: NFTSendComponent.Params,
private val nftDetailsBlockComponentFactory: NFTDetailsBlockComponent.Factory,
private val nftSendConfirmComponentFactory: NFTSendConfirmComponent.Factory,
private val nftSendSuccessComponentFactory: NFTSendSuccessComponent.Factory,
private val analyticsEventHandler: AnalyticsEventHandler,
) : NFTSendComponent, AppComponentContext by appComponentContext {
@ -121,6 +122,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
is CommonSendRoute.Destination -> getDestinationComponent(factoryContext)
is CommonSendRoute.Fee -> getFeeComponent(factoryContext)
CommonSendRoute.Confirm -> getConfirmComponent(factoryContext)
CommonSendRoute.ConfirmSuccess -> getSuccessComponent(factoryContext)
else -> getStubComponent()
}
@ -164,9 +166,8 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
}
}
private fun getConfirmComponent(factoryContext: AppComponentContext) = NFTSendConfirmComponent(
private fun getConfirmComponent(factoryContext: AppComponentContext) = nftSendConfirmComponentFactory.create(
appComponentContext = factoryContext,
nftDetailsBlockComponentFactory = nftDetailsBlockComponentFactory,
params = NFTSendConfirmComponent.Params(
state = model.uiState.value,
analyticsCategoryName = analyticsCategoryName,
@ -180,9 +181,34 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
currentRoute = model.currentRouteFlow.filterIsInstance<CommonSendRoute.Confirm>(),
isBalanceHidingFlow = model.isBalanceHiddenFlow,
onLoadFee = model::loadFee,
onSendTransaction = { innerRouter.replaceAll(CommonSendRoute.ConfirmSuccess) },
),
)
private fun getSuccessComponent(factoryContext: AppComponentContext): ComposableContentComponent {
val txUrl = (model.uiState.value.confirmUM as? ConfirmUM.Success)?.txUrl
if (txUrl == null) {
model.showAlertError()
return getStubComponent()
}
return nftSendSuccessComponentFactory.create(
appComponentContext = factoryContext,
params = NFTSendSuccessComponent.Params(
nftSendUMFlow = model.uiState,
analyticsCategoryName = analyticsCategoryName,
userWallet = model.userWallet,
cryptoCurrencyStatus = model.cryptoCurrencyStatus,
nftAsset = params.nftAsset,
nftCollectionName = params.nftCollectionName,
callback = model,
currentRoute = model.currentRouteFlow.filterIsInstance<CommonSendRoute.ConfirmSuccess>(),
txUrl = txUrl,
),
)
}
private fun getStubComponent() = ComposableContentComponent { }
private fun onChildBack() {

View file

@ -7,7 +7,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
import com.tangem.core.analytics.models.AnalyticsParam.Key.NONCE
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
import com.tangem.core.ui.extensions.capitalize
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
/**
* Send screen analytics

View file

@ -5,10 +5,10 @@ import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.Companion.NFT_SEND_CATEGORY
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.send.v2.common.analytics.CommonSendAnalyticEvents.Companion.NFT_SEND_CATEGORY
import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
import javax.inject.Inject

View file

@ -10,31 +10,41 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.nft.models.NFTAsset
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.nft.models.NFTAsset
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.features.nft.component.NFTDetailsBlockComponent
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.SendNotificationsComponent
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.api.entity.PredefinedValues
import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.send.v2.api.params.FeeSelectorParams.FeeStateConfiguration
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.sendnft.confirm.model.NFTSendConfirmModel
import com.tangem.features.send.v2.sendnft.confirm.ui.NFTSendConfirmContent
import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM
import com.tangem.features.send.v2.subcomponents.destination.DefaultSendDestinationBlockComponent
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams
import com.tangem.features.send.v2.subcomponents.notifications.DefaultSendNotificationsComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.*
import java.math.BigDecimal
internal class NFTSendConfirmComponent(
appComponentContext: AppComponentContext,
params: Params,
internal class NFTSendConfirmComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Params,
nftDetailsBlockComponentFactory: NFTDetailsBlockComponent.Factory,
feeSelectorComponentFactory: FeeSelectorBlockComponent.Factory,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: NFTSendConfirmModel = getOrCreateModel(params = params)
@ -74,12 +84,28 @@ internal class NFTSendConfirmComponent(
onClick = model::showEditFee,
)
private val feeSelectorBlockComponent = feeSelectorComponentFactory.create(
context = child("NFTSendConfirmFeeSelectorBlock"),
params = FeeSelectorParams.FeeSelectorBlockParams(
state = model.uiState.value.feeSelectorUM,
onLoadFee = params.onLoadFee,
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
feeStateConfiguration = FeeStateConfiguration.None,
feeDisplaySource = FeeSelectorParams.FeeDisplaySource.Screen,
analyticsCategoryName = params.analyticsCategoryName,
),
onResult = model::onFeeResult,
)
private val nftDetailsBlockComponent = nftDetailsBlockComponentFactory.create(
context = child("NFTDetailsBlock"),
params = NFTDetailsBlockComponent.Params(
userWalletId = params.userWallet.walletId,
nftAsset = params.nftAsset,
nftCollectionName = params.nftCollectionName,
isSuccessScreen = false,
title = resourceReference(R.string.send_from_wallet_name, wrappedList(params.userWallet.name)),
),
)
@ -126,6 +152,7 @@ internal class NFTSendConfirmComponent(
nftSendUM = state,
destinationBlockComponent = destinationBlockComponent,
feeBlockComponent = feeBlockComponent,
feeSelectorBlockComponent = feeSelectorBlockComponent,
nftDetailsBlockComponent = nftDetailsBlockComponent,
notificationsComponent = notificationsComponent,
notificationsUM = notificationState,
@ -145,9 +172,15 @@ internal class NFTSendConfirmComponent(
val currentRoute: Flow<CommonSendRoute.Confirm>,
val isBalanceHidingFlow: StateFlow<Boolean>,
val onLoadFee: suspend () -> Either<GetFeeError, TransactionFee>,
val onSendTransaction: () -> Unit,
)
interface ModelCallback {
fun onResult(nftSendUM: NFTSendUM)
}
@AssistedFactory
interface Factory {
fun create(appComponentContext: AppComponentContext, params: Params): NFTSendConfirmComponent
}
}

View file

@ -21,23 +21,25 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
import com.tangem.domain.settings.NeverShowTapHelpUseCase
import com.tangem.domain.transaction.usecase.CreateNFTTransferTransactionUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.features.nft.entity.NFTSendSuccessTrigger
import com.tangem.features.send.v2.api.SendNotificationsComponent
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificationsUpdateListener
import com.tangem.features.send.v2.api.subcomponents.notifications.SendNotificationsUpdateTrigger
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.common.SendBalanceUpdater
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.sendnft.analytics.NFTSendAnalyticHelper
@ -60,6 +62,7 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import java.math.BigDecimal
import javax.inject.Inject
import com.tangem.features.send.v2.api.entity.FeeSelectorUM as FeeSelectorUMRedesigned
@Suppress("LongParameterList", "LargeClass")
@ModelScoped
@ -88,7 +91,7 @@ internal class NFTSendConfirmModel @Inject constructor(
private val nftSendSuccessTrigger: NFTSendSuccessTrigger,
private val sendFeeReloadTrigger: SendFeeReloadTrigger,
sendBalanceUpdaterFactory: SendBalanceUpdater.Factory,
) : Model(), NFTSendConfirmClickIntents, SendNotificationsComponent.ModelCallback {
) : Model(), NFTSendConfirmClickIntents, SendNotificationsComponent.ModelCallback, FeeSelectorModelCallback {
private val params: NFTSendConfirmComponent.Params = paramsContainer.require()
@ -140,6 +143,12 @@ internal class NFTSendConfirmModel @Inject constructor(
updateConfirmNotifications()
}
override fun onFeeResult(feeSelectorUM: FeeSelectorUMRedesigned) {
sendIdleTimer = SystemClock.elapsedRealtime()
_uiState.update { it.copy(feeSelectorUM = feeSelectorUM) }
updateConfirmNotifications()
}
fun onDestinationResult(destinationUM: DestinationUM) {
_uiState.update { it.copy(destinationUM = destinationUM) }
updateConfirmNotifications()
@ -231,8 +240,12 @@ internal class NFTSendConfirmModel @Inject constructor(
),
)
if (userWallet is UserWallet.Hot) {
return // TODO [REDACTED_TASK_KEY] [Hot Wallet] Email feedback flow
}
val cardInfo =
getCardInfoUseCase(userWallet.requireColdWallet().scanResponse).getOrNull() ?: return // TODO [REDACTED_TASK_KEY]
getCardInfoUseCase(userWallet.requireColdWallet().scanResponse).getOrNull() ?: return
modelScope.launch {
sendFeedbackEmailUseCase(type = FeedbackEmailType.TransactionSendingProblem(cardInfo = cardInfo))
@ -395,13 +408,23 @@ internal class NFTSendConfirmModel @Inject constructor(
).onEach { (state, _) ->
val confirmUM = state.confirmUM
val confirmUMContent = confirmUM as? ConfirmUM.Content
val isReadyToSend = confirmUMContent != null && !confirmUM.isSending
params.callback.onResult(
state.copy(
navigationUM = NavigationUM.Content(
title = resourceReference(R.string.nft_send),
subtitle = confirmUMContent?.walletName,
backIconRes = R.drawable.ic_close_24,
subtitle = if (uiState.value.isRedesignEnabled) {
null
} else {
confirmUMContent?.walletName
},
backIconRes = if (state.isRedesignEnabled) {
when (confirmUM) {
is ConfirmUM.Success -> R.drawable.ic_close_24
else -> R.drawable.ic_back_24
}
} else {
R.drawable.ic_close_24
},
backIconClick = {
analyticsEventHandler.send(
CommonSendAnalyticEvents.CloseButtonClicked(
@ -411,25 +434,13 @@ internal class NFTSendConfirmModel @Inject constructor(
isValid = confirmUM.isPrimaryButtonEnabled,
),
)
appRouter.pop()
if (state.isRedesignEnabled) {
router.pop()
} else {
appRouter.pop()
}
},
primaryButton = NavigationButton(
textReference = when (confirmUM) {
is ConfirmUM.Success -> resourceReference(R.string.common_close)
is ConfirmUM.Content -> if (confirmUM.isSending) {
resourceReference(R.string.send_sending)
} else {
resourceReference(R.string.common_send)
}
else -> resourceReference(R.string.common_send)
},
iconRes = R.drawable.ic_tangem_24.takeIf { isReadyToSend },
isEnabled = confirmUM.isPrimaryButtonEnabled,
isHapticClick = isReadyToSend,
onClick = {
onNextClick(confirmUM)
},
),
primaryButton = primaryButtonUM(),
prevButton = null,
secondaryPairButtonsUM = (
NavigationButton(
@ -448,21 +459,40 @@ internal class NFTSendConfirmModel @Inject constructor(
}.launchIn(modelScope)
}
private fun onNextClick(confirmUM: ConfirmUM) {
when (confirmUM) {
is ConfirmUM.Success -> {
modelScope.launch {
nftSendSuccessTrigger.triggerSuccessNFTSend()
private fun primaryButtonUM(): NavigationButton {
val confirmUM = uiState.value.confirmUM
val isReadyToSend = confirmUM is ConfirmUM.Content && !confirmUM.isSending
return NavigationButton(
textReference = when (confirmUM) {
is ConfirmUM.Success -> resourceReference(R.string.common_close)
is ConfirmUM.Content -> if (confirmUM.isSending) {
resourceReference(R.string.send_sending)
} else {
resourceReference(R.string.common_send)
}
appRouter.pop()
}
is ConfirmUM.Content -> if (confirmUM.isSending) {
return
} else {
onSendClick()
}
else -> return
}
else -> resourceReference(R.string.common_send)
},
iconRes = R.drawable.ic_tangem_24,
isIconVisible = isReadyToSend,
isEnabled = confirmUM.isPrimaryButtonEnabled,
isHapticClick = isReadyToSend,
onClick = {
when (confirmUM) {
is ConfirmUM.Success -> {
modelScope.launch {
nftSendSuccessTrigger.triggerSuccessNFTSend()
}
appRouter.pop()
}
is ConfirmUM.Content -> if (confirmUM.isSending) {
return@NavigationButton
} else {
onSendClick()
}
else -> return@NavigationButton
}
},
)
}
private companion object {

View file

@ -10,10 +10,10 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.utils.parseToBigDecimal
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.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.utils.formatFooterFiatFee
import com.tangem.features.send.v2.api.utils.getTronTokenFeeSendingText
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.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

@ -1,6 +1,7 @@
package com.tangem.features.send.v2.sendnft.confirm.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
@ -9,7 +10,9 @@ import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.runtime.Composable
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
@ -20,7 +23,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.features.nft.component.NFTDetailsBlockComponent
import com.tangem.features.send.v2.common.ui.SendingText
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.common.ui.tapHelp
import com.tangem.features.send.v2.impl.R
@ -40,6 +43,7 @@ internal fun NFTSendConfirmContent(
destinationBlockComponent: DefaultSendDestinationBlockComponent,
nftDetailsBlockComponent: NFTDetailsBlockComponent,
feeBlockComponent: SendFeeBlockComponent,
feeSelectorBlockComponent: FeeSelectorBlockComponent,
notificationsComponent: DefaultSendNotificationsComponent,
notificationsUM: ImmutableList<NotificationUM>,
) {
@ -54,6 +58,7 @@ internal fun NFTSendConfirmContent(
destinationBlockComponent = destinationBlockComponent,
nftDetailsBlockComponent = nftDetailsBlockComponent,
feeBlockComponent = feeBlockComponent,
feeSelectorBlockComponent = feeSelectorBlockComponent,
)
if (confirmUM != null) {
tapHelp(isDisplay = confirmUM.showTapHelp)
@ -79,31 +84,45 @@ private fun LazyListScope.blocks(
destinationBlockComponent: DefaultSendDestinationBlockComponent,
nftDetailsBlockComponent: NFTDetailsBlockComponent,
feeBlockComponent: SendFeeBlockComponent,
feeSelectorBlockComponent: FeeSelectorBlockComponent,
) {
item(key = BLOCKS_KEY) {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
AnimatedVisibility(
visible = nftSendUM.confirmUM is ConfirmUM.Success,
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12),
) {
val wrappedConfirmUM = remember(this) { nftSendUM.confirmUM as ConfirmUM.Success }
TransactionDoneTitle(
title = resourceReference(R.string.sent_transaction_sent_title),
subtitle = resourceReference(
R.string.send_date_format,
wrappedList(
wrappedConfirmUM.transactionDate.toTimeFormat(DateTimeFormatters.dateFormatter),
wrappedConfirmUM.transactionDate.toTimeFormat(),
),
),
modifier = Modifier.padding(vertical = 12.dp),
if (nftSendUM.isRedesignEnabled) {
nftDetailsBlockComponent.Content(modifier = Modifier)
destinationBlockComponent.Content(modifier = Modifier)
feeSelectorBlockComponent.Content(
modifier = Modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.action),
)
} else {
TransactionDoneTitleAnimated(nftSendUM = nftSendUM)
destinationBlockComponent.Content(modifier = Modifier)
nftDetailsBlockComponent.Content(modifier = Modifier)
feeBlockComponent.Content(modifier = Modifier)
}
destinationBlockComponent.Content(modifier = Modifier)
nftDetailsBlockComponent.Content(modifier = Modifier)
feeBlockComponent.Content(modifier = Modifier)
}
}
}
@Composable
private fun TransactionDoneTitleAnimated(nftSendUM: NFTSendUM) {
AnimatedVisibility(
visible = nftSendUM.confirmUM is ConfirmUM.Success,
modifier = Modifier.padding(vertical = 12.dp),
) {
val wrappedConfirmUM = remember(this) { nftSendUM.confirmUM as ConfirmUM.Success }
TransactionDoneTitle(
title = resourceReference(R.string.sent_transaction_sent_title),
subtitle = resourceReference(
R.string.send_date_format,
wrappedList(
wrappedConfirmUM.transactionDate.toTimeFormat(DateTimeFormatters.dateFormatter),
wrappedConfirmUM.transactionDate.toTimeFormat(),
),
),
modifier = Modifier.padding(vertical = 12.dp),
)
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.send.v2.sendnft.confirm.model.NFTSendConfirmModel
import com.tangem.features.send.v2.sendnft.model.NFTSendModel
import com.tangem.features.send.v2.sendnft.success.model.NFTSendSuccessModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@ -23,4 +24,9 @@ internal interface NFTSendModelModule {
@IntoMap
@ClassKey(NFTSendConfirmModel::class)
fun provideNFTSendConfirmModel(model: NFTSendConfirmModel): Model
@Binds
@IntoMap
@ClassKey(NFTSendSuccessModel::class)
fun provideNFTSendSuccessModel(model: NFTSendSuccessModel): Model
}

View file

@ -20,15 +20,17 @@ import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.CreateNFTTransferTransactionUseCase
import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.send.v2.api.NFTSendComponent
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponent
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.v2.common.CommonSendRoute
@ -36,6 +38,7 @@ import com.tangem.features.send.v2.common.CommonSendRoute.*
import com.tangem.features.send.v2.common.SendConfirmAlertFactory
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.sendnft.confirm.NFTSendConfirmComponent
import com.tangem.features.send.v2.sendnft.success.NFTSendSuccessComponent
import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponent
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
@ -70,7 +73,8 @@ internal class NFTSendModel @Inject constructor(
private val getCardInfoUseCase: GetCardInfoUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val alertFactory: SendConfirmAlertFactory,
) : Model(), SendNFTComponentCallback {
private val sendFeatureToggles: SendFeatureToggles,
) : Model(), SendNFTComponentCallback, NFTSendSuccessComponent.ModelCallback {
val params: NFTSendComponent.Params = paramsContainer.require()
@ -124,7 +128,7 @@ internal class NFTSendModel @Inject constructor(
} else {
when (currentRouteFlow.value) {
is Destination -> router.push(Confirm)
Confirm -> router.push(ConfirmSuccess)
Confirm -> router.replaceAll(ConfirmSuccess)
else -> onBackClick()
}
}
@ -193,6 +197,13 @@ internal class NFTSendModel @Inject constructor(
}
}
fun showAlertError() {
alertFactory.getGenericErrorState(
onFailedTxEmailClick = ::onFailedTxEmailClick,
popBack = router::pop,
)
}
private fun onFailedTxEmailClick(errorMessage: String? = null) {
saveBlockchainErrorUseCase(
error = BlockchainErrorInfo(
@ -206,8 +217,12 @@ internal class NFTSendModel @Inject constructor(
),
)
if (userWallet is UserWallet.Hot) {
return // TODO [REDACTED_TASK_KEY] [Hot Wallet] Email feedback flow
}
val cardInfo =
getCardInfoUseCase(userWallet.requireColdWallet().scanResponse).getOrNull() ?: return // TODO [REDACTED_TASK_KEY]
getCardInfoUseCase(userWallet.requireColdWallet().scanResponse).getOrNull() ?: return
modelScope.launch {
sendFeedbackEmailUseCase(type = FeedbackEmailType.TransactionSendingProblem(cardInfo = cardInfo))
@ -245,7 +260,9 @@ internal class NFTSendModel @Inject constructor(
private fun initialState(): NFTSendUM = NFTSendUM(
destinationUM = DestinationUM.Empty(),
feeUM = FeeUM.Empty(),
feeSelectorUM = FeeSelectorUM.Loading,
confirmUM = ConfirmUM.Empty,
navigationUM = NavigationUM.Empty,
isRedesignEnabled = sendFeatureToggles.isNFTSendRedesignEnabled,
)
}

View file

@ -0,0 +1,96 @@
package com.tangem.features.send.v2.sendnft.success
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.nft.models.NFTAsset
import com.tangem.features.nft.component.NFTDetailsBlockComponent
import com.tangem.features.send.v2.api.entity.PredefinedValues
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.sendnft.success.model.NFTSendSuccessModel
import com.tangem.features.send.v2.sendnft.success.ui.NFTSendSuccessContent
import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
internal class NFTSendSuccessComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Params,
nftDetailsBlockComponentFactory: NFTDetailsBlockComponent.Factory,
sendDestinationBlockComponentFactory: SendDestinationBlockComponent.Factory,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: NFTSendSuccessModel = getOrCreateModel(params = params)
private val nftDetailsBlockComponent = nftDetailsBlockComponentFactory.create(
context = child("NFTDetailsSuccessBlock"),
params = NFTDetailsBlockComponent.Params(
userWalletId = params.userWallet.walletId,
nftAsset = params.nftAsset,
nftCollectionName = params.nftCollectionName,
isSuccessScreen = true,
title = resourceReference(R.string.nft_asset),
),
)
private val sendDestinationBlockComponent = sendDestinationBlockComponentFactory.create(
context = child("NFTDestinationSuccessBlock"),
params = DestinationBlockParams(
state = model.uiState.value.destinationUM,
analyticsCategoryName = params.analyticsCategoryName,
userWalletId = params.userWallet.walletId,
cryptoCurrency = params.cryptoCurrencyStatus.currency,
blockClickEnableFlow = MutableStateFlow(false),
predefinedValues = PredefinedValues.Empty,
),
onResult = {},
onClick = {},
)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
NFTSendSuccessContent(
nftSendUM = state,
destinationBlockComponent = sendDestinationBlockComponent,
nftDetailsBlockComponent = nftDetailsBlockComponent,
modifier = modifier,
)
}
data class Params(
val nftSendUMFlow: StateFlow<NFTSendUM>,
val analyticsCategoryName: String,
val currentRoute: Flow<CommonSendRoute>,
val cryptoCurrencyStatus: CryptoCurrencyStatus,
val userWallet: UserWallet,
val nftAsset: NFTAsset,
val nftCollectionName: String,
val txUrl: String,
val callback: ModelCallback,
)
interface ModelCallback {
fun onResult(nftSendUM: NFTSendUM)
}
@AssistedFactory
interface Factory {
fun create(appComponentContext: AppComponentContext, params: Params): NFTSendSuccessComponent
}
}

View file

@ -0,0 +1,107 @@
package com.tangem.features.send.v2.sendnft.success.model
import androidx.compose.runtime.Stable
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.navigationButtons.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
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.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.send.ui.state.SendUM
import com.tangem.features.send.v2.sendnft.success.NFTSendSuccessComponent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
@Stable
@ModelScoped
internal class NFTSendSuccessModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
private val appRouter: AppRouter,
private val urlOpener: UrlOpener,
private val shareManager: ShareManager,
) : Model() {
private val params: NFTSendSuccessComponent.Params = paramsContainer.require()
val uiState = params.nftSendUMFlow
init {
configConfirmSuccessNavigation()
}
private fun configConfirmSuccessNavigation() {
combine(
flow = uiState,
flow2 = params.currentRoute,
transform = { state, route -> state to route },
).filter { it.second is CommonSendRoute.ConfirmSuccess }.onEach { (state, _) ->
params.callback.onResult(
state.copy(
navigationUM = NavigationUM.Content(
title = stringReference(""),
subtitle = null,
backIconRes = R.drawable.ic_close_24,
backIconClick = {
analyticsEventHandler.send(
CommonSendAnalyticEvents.CloseButtonClicked(
categoryName = params.analyticsCategoryName,
source = SendScreenSource.Confirm,
isFromSummary = true,
isValid = true,
),
)
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,
),
),
),
)
}.launchIn(modelScope)
}
private fun onExploreClick() {
analyticsEventHandler.send(CommonSendAnalyticEvents.ExploreButtonClicked(params.analyticsCategoryName))
urlOpener.openUrl(params.txUrl)
}
private fun onShareClick() {
analyticsEventHandler.send(CommonSendAnalyticEvents.ShareButtonClicked(params.analyticsCategoryName))
shareManager.shareText(params.txUrl)
}
interface ModelCallback {
fun onResult(sendUM: SendUM)
}
}

View file

@ -0,0 +1,103 @@
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.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.transactions.TransactionDoneTitle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.core.ui.utils.toPx
import com.tangem.core.ui.utils.toTimeFormat
import com.tangem.features.nft.component.NFTDetailsBlockComponent
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent
import com.tangem.features.send.v2.common.ui.FeeBlock
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM
import kotlinx.coroutines.delay
@Composable
internal fun NFTSendSuccessContent(
nftSendUM: NFTSendUM,
destinationBlockComponent: SendDestinationBlockComponent,
nftDetailsBlockComponent: NFTDetailsBlockComponent,
modifier: Modifier = Modifier,
) {
var visible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
delay(ANIMATION_DELAY)
visible = true
}
val height = ANIMATION_OFFSET.toPx().toInt()
AnimatedVisibility(
visible = visible,
enter = slideInVertically(
initialOffsetY = { height },
).plus(fadeIn()),
exit = slideOutVertically().plus(fadeOut()),
label = "Animate success content",
modifier = modifier,
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(TangemTheme.colors.background.tertiary),
) {
Column(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.scrollable(
state = rememberScrollState(),
orientation = Orientation.Vertical,
),
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)
Spacer(Modifier.height(60.dp))
}
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,
),
)
}
}
}
private const val ANIMATION_DELAY = 600L
private val ANIMATION_OFFSET = (-40).dp

View file

@ -1,13 +1,16 @@
package com.tangem.features.send.v2.sendnft.ui.state
import com.tangem.common.ui.navigationButtons.NavigationUM
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.v2.common.ui.state.ConfirmUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
internal data class NFTSendUM(
val destinationUM: DestinationUM,
val feeUM: FeeUM,
val feeSelectorUM: FeeSelectorUM,
val confirmUM: ConfirmUM,
val navigationUM: NavigationUM,
val isRedesignEnabled: Boolean,
)

View file

@ -3,11 +3,11 @@ package com.tangem.features.send.v2.subcomponents.amount
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.api.entity.PredefinedValues
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponent.ModelCallback
import kotlinx.coroutines.flow.StateFlow

View file

@ -1,31 +0,0 @@
package com.tangem.features.send.v2.subcomponents.amount.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE
internal sealed class SendAmountAnalyticEvents(
category: String,
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category = category, event = event, params = params) {
/** Selected currency */
data class SelectedCurrency(
val categoryName: String,
val type: SelectedCurrencyType,
) : SendAmountAnalyticEvents(
category = categoryName,
event = "Selected Currency",
params = mapOf(TYPE to type.value),
)
/** Max amount button clicked */
data class MaxAmountButtonClicked(
val categoryName: String,
) : SendAmountAnalyticEvents(category = categoryName, event = "Max Amount Taped")
internal enum class SelectedCurrencyType(val value: String) {
Token("Token"),
AppCurrency("App Currency"),
}
}

View file

@ -2,9 +2,10 @@ package com.tangem.features.send.v2.subcomponents.amount.model
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.features.send.v2.impl.R
import javax.inject.Inject
@ModelScoped
@ -13,20 +14,19 @@ internal class SendAmountAlertFactory @Inject constructor(
) {
fun showResetSendingAlert(onConfirm: () -> Unit) {
// todo fix localization [REDACTED_TASK_KEY]
uiMessageSender.send(
DialogMessage(
title = stringReference("Confirm Convert"),
message = stringReference("Proceed with conversion? Previous data will be reset."),
title = resourceReference(R.string.send_with_swap_convert_token_alert_title),
message = resourceReference(R.string.send_with_swap_convert_token_alert_message),
firstActionBuilder = {
EventMessageAction(
title = stringReference("Confirm"),
title = resourceReference(R.string.common_confirm),
onClick = onConfirm,
)
},
secondActionBuilder = {
EventMessageAction(
title = stringReference("Not Now"),
title = resourceReference(R.string.common_not_now),
onClick = onDismissRequest,
)
},

View file

@ -22,22 +22,22 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.features.send.v2.api.entity.PredefinedValues
import com.tangem.features.send.v2.api.subcomponents.amount.analytics.CommonSendAmountAnalyticEvents
import com.tangem.features.send.v2.api.subcomponents.amount.analytics.CommonSendAmountAnalyticEvents.SelectedCurrencyType
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponentParams
import com.tangem.features.send.v2.subcomponents.amount.SendAmountReduceListener
import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateListener
import com.tangem.features.send.v2.subcomponents.amount.analytics.SendAmountAnalyticEvents
import com.tangem.features.send.v2.subcomponents.amount.analytics.SendAmountAnalyticEvents.SelectedCurrencyType
import com.tangem.features.send.v2.subcomponents.fee.SendFeeData
import com.tangem.features.send.v2.subcomponents.fee.SendFeeReloadTrigger
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -218,7 +218,7 @@ internal class SendAmountModel @Inject constructor(
),
)
analyticsEventHandler.send(
SendAmountAnalyticEvents.MaxAmountButtonClicked(categoryName = analyticsCategoryName),
CommonSendAmountAnalyticEvents.MaxAmountButtonClicked(categoryName = analyticsCategoryName),
)
}
@ -229,7 +229,7 @@ internal class SendAmountModel @Inject constructor(
override fun onAmountNext() {
(uiState.value as? AmountState.Data)?.amountTextField?.isFiatValue?.let { isFiatSelected ->
analyticsEventHandler.send(
SendAmountAnalyticEvents.SelectedCurrency(
CommonSendAmountAnalyticEvents.SelectedCurrency(
categoryName = analyticsCategoryName,
type = if (isFiatSelected) {
SelectedCurrencyType.AppCurrency

View file

@ -10,4 +10,7 @@ internal enum class EnterAddressSource {
val isPasted: Boolean
get() = this != InputField
val isAutoNext: Boolean
get() = this == RecentAddress || this == MyWallets
}

View file

@ -26,12 +26,12 @@ import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.api.entity.PredefinedValues
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams
import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams.DestinationBlockParams
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.destination.analytics.EnterAddressSource
import com.tangem.features.send.v2.subcomponents.destination.analytics.SendDestinationAnalyticEvents
@ -145,6 +145,11 @@ internal class SendDestinationModel @Inject constructor(
)
}
fun saveResult() {
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
params.callback.onDestinationResult(uiState.value)
}
private fun initSenderAddress() {
modelScope.launch {
senderAddresses.value = getNetworkAddressesUseCase.invokeSync(
@ -281,18 +286,12 @@ internal class SendDestinationModel @Inject constructor(
}
private fun autoNextFromRecipient(type: EnterAddressSource?, isValidAddress: Boolean, isValidMemo: Boolean) {
val isRecent = type == EnterAddressSource.RecentAddress
if (isRecent && isValidAddress && isValidMemo) {
if (type?.isAutoNext == true && isValidAddress && isValidMemo) {
saveResult()
(params as? SendDestinationComponentParams.DestinationParams)?.callback?.onNextClick()
}
}
private fun saveResult() {
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
params.callback.onDestinationResult(uiState.value)
}
@Suppress("LongMethod")
private fun configDestinationNavigation() {
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
@ -321,6 +320,7 @@ internal class SendDestinationModel @Inject constructor(
isValid = state.isPrimaryButtonEnabled,
),
)
saveResult()
}
params.callback.onBackClick()
},

View file

@ -22,6 +22,10 @@ internal class SendDestinationInitialStateTransformer(
Network.TransactionExtrasType.MEMO -> R.string.send_extras_hint_memo
Network.TransactionExtrasType.DESTINATION_TAG -> R.string.send_destination_tag_field
}
val placeholder = when (cryptoCurrency.network.nameResolvingType) {
Network.NameResolvingType.NONE -> resourceReference(R.string.send_enter_address_field)
Network.NameResolvingType.ENS -> resourceReference(R.string.send_enter_address_field_ens)
}
return DestinationUM.Content(
isPrimaryButtonEnabled = false,
isInitialized = isInitialized,
@ -32,7 +36,7 @@ internal class SendDestinationInitialStateTransformer(
keyboardType = KeyboardType.Text,
),
error = null,
placeholder = resourceReference(R.string.send_enter_address_field),
placeholder = placeholder,
label = resourceReference(R.string.send_recipient),
isValuePasted = false,
),

View file

@ -18,6 +18,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.components.icons.identicon.IdentIcon
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -62,7 +63,7 @@ private fun AddressBlock(address: DestinationTextFieldUM.RecipientAddress) {
Text(
text = address.label.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.secondary,
color = TangemTheme.colors.text.tertiary,
)
Row(
verticalAlignment = Alignment.CenterVertically,
@ -76,11 +77,21 @@ private fun AddressBlock(address: DestinationTextFieldUM.RecipientAddress) {
.clip(RoundedCornerShape(TangemTheme.dimens.radius18))
.background(TangemTheme.colors.background.tertiary),
)
Text(
text = address.value,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = address.value,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
val blockchainAddress = address.briefBlockchainAddress
if (!blockchainAddress.isNullOrBlank()) {
Text(
text = blockchainAddress,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
}
@ -94,7 +105,7 @@ private fun MemoBlock(memo: DestinationTextFieldUM.RecipientMemo?) {
Text(
text = memo.label.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.secondary,
color = TangemTheme.colors.text.tertiary,
)
Text(
text = memo.value,
@ -111,7 +122,7 @@ private fun AddressWithMemoBlock(
memo: DestinationTextFieldUM.RecipientMemo?,
) {
Text(
text = stringResourceSafe(R.string.send_to_address),
text = stringResourceSafe(R.string.send_recipient),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
@ -120,12 +131,21 @@ private fun AddressWithMemoBlock(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
) {
Text(
modifier = Modifier.weight(1f),
text = address.value,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = address.value,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
val blockchainAddress = address.briefBlockchainAddress
if (!blockchainAddress.isNullOrBlank()) {
Text(
text = blockchainAddress,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
IdentIcon(
address = address.value,
modifier = Modifier
@ -134,6 +154,7 @@ private fun AddressWithMemoBlock(
.background(TangemTheme.colors.background.tertiary),
)
}
if (memo != null && memo.value.isNotBlank()) {
Text(
text = stringResourceSafe(R.string.send_memo, memo.value),
@ -162,64 +183,51 @@ private fun DestinationBlockPreview(
}
private class DestinationBlockPreviewProvider : PreviewParameterProvider<DestinationUM.Content> {
val previewItem = DestinationUM.Content(
isPrimaryButtonEnabled = true,
addressTextField = DestinationTextFieldUM.RecipientAddress(
value = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
keyboardOptions = KeyboardOptions.Default,
placeholder = TextReference.Str("Enter address"),
label = resourceReference(R.string.send_recipient),
isError = false,
error = null,
isValuePasted = false,
),
memoTextField = DestinationTextFieldUM.RecipientMemo(
value = "Test memo for transaction",
keyboardOptions = KeyboardOptions.Default,
placeholder = TextReference.Str("Enter memo (optional)"),
label = TextReference.Str("Memo"),
isError = false,
error = null,
disabledText = TextReference.Str("Memo disabled"),
isEnabled = true,
isValuePasted = false,
),
recent = emptyList<DestinationRecipientListUM>().toImmutableList(),
wallets = emptyList<DestinationRecipientListUM>().toImmutableList(),
networkName = "Ethereum",
isValidating = false,
isInitialized = true,
isRedesignEnabled = false,
)
override val values: Sequence<DestinationUM.Content>
get() = sequenceOf(
DestinationUM.Content(
isPrimaryButtonEnabled = true,
addressTextField = DestinationTextFieldUM.RecipientAddress(
value = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
keyboardOptions = KeyboardOptions.Default,
placeholder = TextReference.Str("Enter address"),
label = TextReference.Str("Recipient Address"),
isError = false,
error = null,
isValuePasted = false,
previewItem,
previewItem.copy(
addressTextField = previewItem.addressTextField.copy(
value = "vitalik.eth",
blockchainAddress = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
),
memoTextField = DestinationTextFieldUM.RecipientMemo(
value = "Test memo for transaction",
keyboardOptions = KeyboardOptions.Default,
placeholder = TextReference.Str("Enter memo (optional)"),
label = TextReference.Str("Memo"),
isError = false,
error = null,
disabledText = TextReference.Str("Memo disabled"),
isEnabled = true,
isValuePasted = false,
),
recent = emptyList<DestinationRecipientListUM>().toImmutableList(),
wallets = emptyList<DestinationRecipientListUM>().toImmutableList(),
networkName = "Ethereum",
isValidating = false,
isInitialized = true,
isRedesignEnabled = false,
),
DestinationUM.Content(
isPrimaryButtonEnabled = true,
addressTextField = DestinationTextFieldUM.RecipientAddress(
value = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
keyboardOptions = KeyboardOptions.Default,
placeholder = TextReference.Str("Enter address"),
label = TextReference.Str("Recipient Address"),
isError = false,
error = null,
isValuePasted = false,
previewItem.copy(isRedesignEnabled = true),
previewItem.copy(
addressTextField = previewItem.addressTextField.copy(
value = "vitalik.eth",
blockchainAddress = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
),
memoTextField = DestinationTextFieldUM.RecipientMemo(
value = "Test memo for transaction",
keyboardOptions = KeyboardOptions.Default,
placeholder = TextReference.Str("Enter memo (optional)"),
label = TextReference.Str("Memo"),
isError = false,
error = null,
disabledText = TextReference.Str("Memo disabled"),
isEnabled = true,
isValuePasted = false,
),
recent = emptyList<DestinationRecipientListUM>().toImmutableList(),
wallets = emptyList<DestinationRecipientListUM>().toImmutableList(),
networkName = "Ethereum",
isValidating = false,
isInitialized = true,
isRedesignEnabled = true,
),
)

View file

@ -142,6 +142,7 @@ private fun LazyListScope.addressItem(
color = TangemTheme.colors.background.action,
shape = TangemTheme.shapes.roundedCornersXMedium,
),
resolvedAddress = address.blockchainAddress,
)
}
}

View file

@ -3,9 +3,9 @@ package com.tangem.features.send.v2.subcomponents.fee
import arrow.core.Either
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.features.send.v2.common.CommonSendRoute
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
import kotlinx.coroutines.flow.Flow

View file

@ -1,39 +0,0 @@
package com.tangem.features.send.v2.subcomponents.fee.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
internal sealed class SendFeeAnalyticEvents(
category: String,
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category = category, event = event, params = params) {
abstract val categoryName: String
/** Selected fee (send after next screen opened) */
data class SelectedFee(
override val categoryName: String,
val feeType: AnalyticsParam.FeeType,
) : SendFeeAnalyticEvents(
category = categoryName,
event = "Fee Selected",
params = mapOf("Fee Type" to feeType.value),
)
/** Custom fee selected */
data class CustomFeeButtonClicked(
override val categoryName: String,
) : SendFeeAnalyticEvents(
category = categoryName,
event = "Custom Fee Clicked",
)
/** Custom fee edited */
data class GasPriceInserter(
override val categoryName: String,
) : SendFeeAnalyticEvents(
category = categoryName,
event = "Gas Price Inserted",
)
}

View file

@ -3,7 +3,7 @@ package com.tangem.features.send.v2.subcomponents.fee.model
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeType
import com.tangem.utils.extensions.isZero

View file

@ -11,14 +11,13 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
import com.tangem.features.send.v2.common.analytics.CommonSendAnalyticEvents.NonceInserted
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.send.v2.api.subcomponents.feeSelector.analytics.CommonSendFeeAnalyticEvents
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.fee.SendFeeCheckReloadListener
import com.tangem.features.send.v2.subcomponents.fee.SendFeeCheckReloadTrigger
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams
import com.tangem.features.send.v2.subcomponents.fee.SendFeeReloadListener
import com.tangem.features.send.v2.subcomponents.fee.analytics.SendFeeAnalyticEvents
import com.tangem.features.send.v2.subcomponents.fee.analytics.SendFeeAnalyticEvents.GasPriceInserter
import com.tangem.features.send.v2.subcomponents.fee.model.transformers.*
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeType
@ -112,7 +111,7 @@ internal class SendFeeModel @Inject constructor(
updateFeeNotifications()
if (feeType == FeeType.Custom) {
analyticsEventHandler.send(
SendFeeAnalyticEvents.CustomFeeButtonClicked(categoryName = analyticsCategoryName),
CommonSendFeeAnalyticEvents.CustomFeeButtonClicked(categoryName = analyticsCategoryName),
)
}
}
@ -155,10 +154,12 @@ internal class SendFeeModel @Inject constructor(
val isCustomFeeEdited =
feeSelectorUM.selectedFee?.amount?.value != feeSelectorUM.fees.normal.amount.value
if (feeSelectorUM.selectedType == FeeType.Custom && isCustomFeeEdited) {
analyticsEventHandler.send(GasPriceInserter(categoryName = analyticsCategoryName))
analyticsEventHandler.send(
CommonSendFeeAnalyticEvents.GasPriceInserter(categoryName = analyticsCategoryName),
)
}
analyticsEventHandler.send(
SendFeeAnalyticEvents.SelectedFee(
CommonSendFeeAnalyticEvents.SelectedFee(
categoryName = analyticsCategoryName,
feeType = feeSelectorUM.selectedType.toAnalyticType(feeSelectorUM),
),
@ -166,7 +167,7 @@ internal class SendFeeModel @Inject constructor(
if (feeSelectorUM.nonce != null) {
analyticsEventHandler.send(
NonceInserted(
CommonSendAnalyticEvents.NonceInserted(
categoryName = analyticsCategoryName,
token = cryptoCurrencyStatus.currency.symbol,
blockchain = cryptoCurrencyStatus.currency.network.name,

View file

@ -3,9 +3,9 @@ package com.tangem.features.send.v2.subcomponents.fee.model.converters
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.v2.subcomponents.fee.model.SendFeeClickIntents
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.features.send.v2.subcomponents.fee.model.SendFeeClickIntents
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeType
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.ImmutableList

View file

@ -4,12 +4,12 @@ import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.features.send.v2.subcomponents.fee.model.SendFeeClickIntents
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.bitcoin.BitcoinCustomFeeConverter
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.kaspa.KaspaCustomFeeConverter
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
import com.tangem.utils.converter.TwoWayConverter
import kotlinx.collections.immutable.ImmutableList

View file

@ -10,7 +10,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.fee.model.checkExceedBalance

View file

@ -9,10 +9,10 @@ import com.tangem.common.ui.amountScreen.utils.getFiatReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.fee.model.checkExceedBalance
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList

View file

@ -10,7 +10,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.fee.model.checkExceedBalance

View file

@ -10,7 +10,8 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.fee.model.checkExceedBalance
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter.Companion.ETHEREUM_GAS_UNIT
@ -18,7 +19,6 @@ import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.eth
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter.Companion.GAS_DECIMALS
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter.Companion.GIGA_DECIMALS
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.setEmpty
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList

View file

@ -10,10 +10,10 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.core.ui.utils.parseToBigDecimal
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.CustomFeeConverter
import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList

View file

@ -1,12 +1,12 @@
package com.tangem.features.send.v2.subcomponents.fee.model.transformers
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.subcomponents.fee.model.SendFeeClickIntents
import com.tangem.features.send.v2.subcomponents.fee.model.converters.SendFeeCustomFieldConverter
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeType
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
import com.tangem.features.send.v2.subcomponents.fee.model.SendFeeClickIntents
import com.tangem.features.send.v2.subcomponents.fee.model.converters.SendFeeCustomFieldConverter
import com.tangem.utils.transformer.Transformer
internal class SendFeeCustomAutoFixTransformer(

View file

@ -1,11 +1,11 @@
package com.tangem.features.send.v2.subcomponents.fee.model.transformers
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.subcomponents.fee.model.SendFeeClickIntents
import com.tangem.features.send.v2.subcomponents.fee.model.converters.SendFeeCustomFieldConverter
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
import com.tangem.utils.transformer.Transformer
internal class SendFeeCustomValueChangeTransformer(

View file

@ -2,7 +2,7 @@ package com.tangem.features.send.v2.subcomponents.fee.model.transformers
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
import com.tangem.lib.crypto.BlockchainUtils.isTron

View file

@ -3,7 +3,7 @@ package com.tangem.features.send.v2.subcomponents.fee.model.transformers
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.subcomponents.fee.model.SendFeeClickIntents
import com.tangem.features.send.v2.subcomponents.fee.model.converters.FeeConverter
import com.tangem.features.send.v2.subcomponents.fee.model.converters.SendFeeCustomFieldConverter

View file

@ -1,12 +1,12 @@
package com.tangem.features.send.v2.subcomponents.fee.model.transformers
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.features.send.v2.subcomponents.fee.model.SendFeeClickIntents
import com.tangem.features.send.v2.subcomponents.fee.model.converters.FeeConverter
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeSelectorUM
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeType
import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM
import com.tangem.features.send.v2.subcomponents.fee.model.converters.FeeConverter
import com.tangem.features.send.v2.subcomponents.fee.model.SendFeeClickIntents
import com.tangem.utils.transformer.Transformer
internal class SendFeeSelectTransformer(

View file

@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
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
@ -14,6 +15,7 @@ import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.ui.amountScreen.utils.getFiatReference
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.rows.SelectorRowItem
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.EMPTY_BALANCE_SIGN
@ -62,20 +64,24 @@ internal fun FeeBlock(feeUM: FeeUM, isClickEnabled: Boolean, onClick: () -> Unit
R.string.common_fee_selector_option_market to R.drawable.ic_bird_24
}
SelectorRowItem(
titleRes = title,
title = resourceReference(title),
iconRes = icon,
preDot = stringReference(
feeAmount?.value.format {
crypto(
symbol = feeAmount?.currencySymbol.orEmpty(),
decimals = feeAmount?.decimals ?: 0,
).fee(canBeLower = feeUM.isFeeApproximate)
},
),
postDot = if (feeUM.isFeeConvertibleToFiat) {
getFiatReference(feeAmount?.value, feeUM.rate, feeUM.appCurrency)
} else {
null
preDot = remember {
stringReference(
feeAmount?.value.format {
crypto(
symbol = feeAmount?.currencySymbol.orEmpty(),
decimals = feeAmount?.decimals ?: 0,
).fee(canBeLower = feeUM.isFeeApproximate)
},
)
},
postDot = remember {
if (feeUM.isFeeConvertibleToFiat) {
getFiatReference(feeAmount?.value, feeUM.rate, feeUM.appCurrency)
} else {
null
}
},
ellipsizeOffset = feeAmount?.currencySymbol?.length,
isSelected = true,

View file

@ -16,6 +16,7 @@ import com.tangem.common.ui.amountScreen.utils.getFiatReference
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.rows.SelectorRowItem
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants.EMPTY_BALANCE_SIGN
import com.tangem.core.ui.format.bigdecimal.crypto
@ -52,7 +53,7 @@ internal fun SendSpeedSelectorItem(
.clickable { onSelect() },
) {
SelectorRowItem(
titleRes = titleRes,
title = resourceReference(titleRes),
iconRes = iconRes,
onSelect = onSelect,
modifier = modifier,

View file

@ -25,12 +25,12 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.notifications.GetTronFeeNotificationShowCountUseCase
import com.tangem.domain.notifications.IncrementNotificationsShowCountUseCase
import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase
import com.tangem.domain.tokens.GetCurrencyCheckUseCase
import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
import com.tangem.domain.utils.convertToSdkAmount

View file

@ -205,6 +205,7 @@ class SendConfirmationNotificationsTransformerTest {
isRedesignEnabled = false,
title = mockk(relaxed = true),
availableBalance = mockk(relaxed = true),
availableBalanceShort = mockk(relaxed = true),
tokenName = mockk(relaxed = true),
tokenIconState = mockk(relaxed = true),
segmentedButtonConfig = persistentListOf(),

View file

@ -204,6 +204,7 @@ class SendConfirmationNotificationsTransformerV2Test {
isRedesignEnabled = false,
title = mockk(relaxed = true),
availableBalance = mockk(relaxed = true),
availableBalanceShort = mockk(relaxed = true),
tokenName = mockk(relaxed = true),
tokenIconState = mockk(relaxed = true),
segmentedButtonConfig = persistentListOf(),

View file

@ -307,6 +307,7 @@ class TransformersComparisonTest {
isRedesignEnabled = false,
title = mockk(relaxed = true),
availableBalance = mockk(relaxed = true),
availableBalanceShort = mockk(relaxed = true),
tokenName = mockk(relaxed = true),
tokenIconState = mockk(relaxed = true),
segmentedButtonConfig = persistentListOf(),