From f7bd99f737b690971c81ddadc4739bedff692d6c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 18 Aug 2025 19:52:11 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../nft/component/NFTDetailsBlockComponent.kt | 5 +- .../block/DefaultNFTDetailsBlockComponent.kt | 2 + .../nft/details/block/ui/NFTDetailsBlock.kt | 31 +++-- .../success/ui/SendConfirmSuccessContent.kt | 2 +- .../v2/sendnft/DefaultNFTSendComponent.kt | 34 +++++- .../confirm/NFTSendConfirmComponent.kt | 39 ++++++- .../confirm/model/NFTSendConfirmModel.kt | 98 ++++++++++------ .../confirm/ui/NFTSendConfirmContent.kt | 59 ++++++---- .../send/v2/sendnft/di/NFTSendModelModule.kt | 6 + .../send/v2/sendnft/model/NFTSendModel.kt | 17 ++- .../success/NFTSendSuccessComponent.kt | 96 ++++++++++++++++ .../success/model/NFTSendSuccessModel.kt | 107 ++++++++++++++++++ .../success/ui/NFTSendSuccessContent.kt | 103 +++++++++++++++++ .../send/v2/sendnft/ui/state/NFTSendUM.kt | 5 +- 14 files changed, 527 insertions(+), 77 deletions(-) create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/NFTSendSuccessComponent.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/model/NFTSendSuccessModel.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/ui/NFTSendSuccessContent.kt diff --git a/features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTDetailsBlockComponent.kt b/features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTDetailsBlockComponent.kt index ed1df0a1f2..801c8f6420 100644 --- a/features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTDetailsBlockComponent.kt +++ b/features/nft/api/src/main/kotlin/com/tangem/features/nft/component/NFTDetailsBlockComponent.kt @@ -2,8 +2,9 @@ package com.tangem.features.nft.component import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.nft.models.NFTAsset +import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.nft.models.NFTAsset interface NFTDetailsBlockComponent : ComposableContentComponent { @@ -11,6 +12,8 @@ interface NFTDetailsBlockComponent : ComposableContentComponent { val userWalletId: UserWalletId, val nftAsset: NFTAsset, val nftCollectionName: String, + val title: TextReference, + val isSuccessScreen: Boolean, ) interface Factory : ComponentFactory diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/DefaultNFTDetailsBlockComponent.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/DefaultNFTDetailsBlockComponent.kt index 234cb0adfb..4856ca5634 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/DefaultNFTDetailsBlockComponent.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/DefaultNFTDetailsBlockComponent.kt @@ -22,6 +22,8 @@ class DefaultNFTDetailsBlockComponent @AssistedInject constructor( assetName = stringReference(params.nftAsset.name.orEmpty()), collectionName = stringReference(params.nftCollectionName), assetImage = params.nftAsset.media?.imageUrl, + title = params.title, + isSuccessScreen = params.isSuccessScreen, networkIconRes = getActiveIconRes(params.nftAsset.network.rawId), ) } diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/ui/NFTDetailsBlock.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/ui/NFTDetailsBlock.kt index 6336d2dbb2..8d7d1148c9 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/ui/NFTDetailsBlock.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/ui/NFTDetailsBlock.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference @@ -19,12 +20,15 @@ import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.nft.common.ui.NFTLogo import com.tangem.features.nft.impl.R +@Suppress("LongParameterList") @Composable internal fun NFTDetailsBlock( + title: TextReference, assetName: TextReference, collectionName: TextReference, assetImage: String?, networkIconRes: Int, + isSuccessScreen: Boolean, ) { Column( modifier = Modifier @@ -35,20 +39,21 @@ internal fun NFTDetailsBlock( verticalArrangement = Arrangement.spacedBy(6.dp), ) { Text( - text = "NFT Asset", + text = title.resolveReference(), style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.secondary, + color = TangemTheme.colors.text.tertiary, ) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp), ) { - NFTLogo( - assetImage, - networkIconRes, - background = TangemTheme.colors.background.action, - ) - + if (isSuccessScreen) { + NFTLogo( + assetImage, + networkIconRes, + background = TangemTheme.colors.background.action, + ) + } Column( verticalArrangement = Arrangement.spacedBy(2.dp), ) { @@ -63,6 +68,14 @@ internal fun NFTDetailsBlock( color = TangemTheme.colors.text.tertiary, ) } + if (!isSuccessScreen) { + SpacerWMax() + NFTLogo( + assetImage, + networkIconRes, + background = TangemTheme.colors.background.action, + ) + } } } } @@ -78,6 +91,8 @@ private fun NFTDetailsBlock_Preview() { collectionName = stringReference("NFT Collection"), assetImage = null, networkIconRes = R.drawable.img_polygon_22, + title = stringReference("From My Wallet"), + isSuccessScreen = false, ) } } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/ui/SendConfirmSuccessContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/ui/SendConfirmSuccessContent.kt index 636828e73f..1d679a3e59 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/ui/SendConfirmSuccessContent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/ui/SendConfirmSuccessContent.kt @@ -56,7 +56,7 @@ internal fun SendConfirmSuccessContent(sendUM: SendUM, destinationBlockComponent .padding(horizontal = TangemTheme.dimens.spacing16) .scrollable( state = rememberScrollState(), - orientation = Orientation.Horizontal, + orientation = Orientation.Vertical, ), verticalArrangement = Arrangement.spacedBy(12.dp), ) { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt index c4ecdee6e2..b23440cfeb 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt @@ -18,7 +18,6 @@ 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 @@ -29,6 +28,7 @@ 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(), 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(), + txUrl = txUrl, + ), + ) + } + private fun getStubComponent() = ComposableContentComponent { } private fun onChildBack() { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/NFTSendConfirmComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/NFTSendConfirmComponent.kt index cc0fe9a936..d2fd5670b1 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/NFTSendConfirmComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/NFTSendConfirmComponent.kt @@ -10,17 +10,23 @@ 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.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.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 @@ -28,13 +34,17 @@ import com.tangem.features.send.v2.subcomponents.destination.DefaultSendDestinat 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, val isBalanceHidingFlow: StateFlow, val onLoadFee: suspend () -> Either, + val onSendTransaction: () -> Unit, ) interface ModelCallback { fun onResult(nftSendUM: NFTSendUM) } + + @AssistedFactory + interface Factory { + fun create(appComponentContext: AppComponentContext, params: Params): NFTSendConfirmComponent + } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt index 522470a90e..d834d13885 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt @@ -33,6 +33,7 @@ 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 @@ -61,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 @@ -89,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() @@ -141,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() @@ -400,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( @@ -416,26 +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, - isIconVisible = isReadyToSend, - isEnabled = confirmUM.isPrimaryButtonEnabled, - isHapticClick = isReadyToSend, - onClick = { - onNextClick(confirmUM) - }, - ), + primaryButton = primaryButtonUM(), prevButton = null, secondaryPairButtonsUM = ( NavigationButton( @@ -454,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 { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/ui/NFTSendConfirmContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/ui/NFTSendConfirmContent.kt index c2a2de5774..9ff84edfb4 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/ui/NFTSendConfirmContent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/ui/NFTSendConfirmContent.kt @@ -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,6 +10,7 @@ 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 @@ -21,6 +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.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, ) { @@ -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), + ) + } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/di/NFTSendModelModule.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/di/NFTSendModelModule.kt index 3252c7bc71..5980fa7d22 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/di/NFTSendModelModule.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/di/NFTSendModelModule.kt @@ -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 } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt index 0bebb6f107..90a8753b4a 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt @@ -29,6 +29,8 @@ 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( @@ -249,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, ) } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/NFTSendSuccessComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/NFTSendSuccessComponent.kt new file mode 100644 index 0000000000..76f7329658 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/NFTSendSuccessComponent.kt @@ -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, + val analyticsCategoryName: String, + val currentRoute: Flow, + 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 + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/model/NFTSendSuccessModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/model/NFTSendSuccessModel.kt new file mode 100644 index 0000000000..9352ec6898 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/model/NFTSendSuccessModel.kt @@ -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) + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/ui/NFTSendSuccessContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/ui/NFTSendSuccessContent.kt new file mode 100644 index 0000000000..10fa100bc9 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/success/ui/NFTSendSuccessContent.kt @@ -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 \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/ui/state/NFTSendUM.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/ui/state/NFTSendUM.kt index fb48eed197..45287e3c6e 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/ui/state/NFTSendUM.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/ui/state/NFTSendUM.kt @@ -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, ) \ No newline at end of file