From 9df1b844046f7c4414e094f9b5f7a4168fe01158 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 18 Jul 2025 18:55:47 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 24 ++++++ .../com/tangem/common/routing/AppRoute.kt | 14 ++++ features/markets/impl/build.gradle.kts | 1 + .../impl/model/TokenActionsHandler.kt | 14 +++- .../send/v2/api/SendEntryPointComponent.kt | 16 ++++ features/send-v2/impl/build.gradle.kts | 2 + .../features/send/v2/di/SendFeatureModule.kt | 12 ++- .../DefaultSendEntryPointComponent.kt | 71 +++++++++++++++++ .../v2/entrypoint/di/SendEntryPointModule.kt | 20 +++++ .../entrypoint/model/SendEntryPointModel.kt | 79 +++++++++++++++++++ features/tokendetails/impl/build.gradle.kts | 1 + .../tokendetails/model/TokenDetailsModel.kt | 17 +++- features/wallet/impl/build.gradle.kts | 1 + 13 files changed, 261 insertions(+), 11 deletions(-) create mode 100644 features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendEntryPointComponent.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/di/SendEntryPointModule.kt create mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 084e92653f..5bdb2476f6 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -24,8 +24,10 @@ import com.tangem.features.onramp.component.* import com.tangem.features.pushnotifications.api.PushNotificationsComponent import com.tangem.features.send.v2.api.NFTSendComponent import com.tangem.features.send.v2.api.SendComponent +import com.tangem.features.send.v2.api.SendEntryPointComponent import com.tangem.features.staking.api.StakingComponent import com.tangem.features.swap.SwapComponent +import com.tangem.features.swap.v2.api.SendWithSwapComponent import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.wallet.WalletEntryComponent import com.tangem.features.walletconnect.components.WalletConnectEntryComponent @@ -85,6 +87,8 @@ internal class ChildFactory @Inject constructor( private val createWalletSelectionComponentFactory: CreateWalletSelectionComponent.Factory, private val createMobileWalletComponentFactory: CreateMobileWalletComponent.Factory, private val addExistingWalletComponentFactory: AddExistingWalletComponent.Factory, + private val sendWithSwapComponentFactory: SendWithSwapComponent.Factory, + private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory, private val walletConnectFeatureToggles: WalletConnectFeatureToggles, ) { @@ -442,6 +446,26 @@ internal class ChildFactory @Inject constructor( componentFactory = addExistingWalletComponentFactory, ) } + is AppRoute.SendEntryPoint -> { + createComponentChild( + context = context, + params = SendEntryPointComponent.Params( + userWalletId = route.userWalletId, + cryptoCurrency = route.currency, + ), + componentFactory = sendEntryPointComponentFactory, + ) + } + is AppRoute.SendWithSwap -> { + createComponentChild( + context = context, + params = SendWithSwapComponent.Params( + userWalletId = route.userWalletId, + currency = route.currency, + ), + componentFactory = sendWithSwapComponentFactory, + ) + } } } } \ No newline at end of file diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 9d9c95165f..089f30745e 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -288,4 +288,18 @@ sealed class AppRoute(val path: String) : Route { @Serializable object AddExistingWallet : AppRoute(path = "/add_existing_wallet") + + @Serializable + data class SendEntryPoint( + val userWalletId: UserWalletId, + val currency: CryptoCurrency, + ) : AppRoute( + path = "/send_entry_point/${userWalletId.stringValue}/${currency.id.value}?", + ) + + @Serializable + data class SendWithSwap( + val userWalletId: UserWalletId, + val currency: CryptoCurrency, + ) : AppRoute(path = "/send_with_swap/${userWalletId.stringValue}/${currency.symbol}") } \ No newline at end of file diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts index ca27225294..6029226493 100644 --- a/features/markets/impl/build.gradle.kts +++ b/features/markets/impl/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { /* Project - API */ api(projects.features.markets.api) api(projects.features.onramp.api) + api(projects.features.sendV2.api) /* Data */ implementation(projects.data.common) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt index 38e24c6890..83b4b10186 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt @@ -23,6 +23,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.markets.impl.R import com.tangem.features.markets.portfolio.impl.loader.PortfolioData import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM +import com.tangem.features.send.v2.api.SendFeatureToggles import com.tangem.utils.Provider import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -41,6 +42,7 @@ internal class TokenActionsHandler @AssistedInject constructor( private val isDemoCardUseCase: IsDemoCardUseCase, private val messageSender: UiMessageSender, private val shareManager: ShareManager, + private val sendFeatureToggles: SendFeatureToggles, ) { private val disabledActionsInDemoMode = buildSet { @@ -157,12 +159,18 @@ internal class TokenActionsHandler @AssistedInject constructor( } private fun onSendClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) { - router.push( + val route = if (sendFeatureToggles.isSendWithSwapEnabled) { + AppRoute.SendEntryPoint( + userWalletId = cryptoCurrencyData.userWallet.walletId, + currency = cryptoCurrencyData.status.currency, + ) + } else { AppRoute.Send( userWalletId = cryptoCurrencyData.userWallet.walletId, currency = cryptoCurrencyData.status.currency, - ), - ) + ) + } + router.push(route) } private fun onStakeClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) { diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendEntryPointComponent.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendEntryPointComponent.kt new file mode 100644 index 0000000000..1591ef9c5a --- /dev/null +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendEntryPointComponent.kt @@ -0,0 +1,16 @@ +package com.tangem.features.send.v2.api + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId + +interface SendEntryPointComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + val cryptoCurrency: CryptoCurrency, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/send-v2/impl/build.gradle.kts b/features/send-v2/impl/build.gradle.kts index 519db0736a..0f6bc47557 100644 --- a/features/send-v2/impl/build.gradle.kts +++ b/features/send-v2/impl/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(projects.features.sendV2.api) implementation(projects.features.txhistory.api) implementation(projects.features.nft.api) + implementation(projects.features.swapV2.api) /** Libs */ implementation(projects.libs.crypto) @@ -64,6 +65,7 @@ dependencies { implementation(projects.domain.nft.models) implementation(projects.domain.nft) implementation(projects.domain.notifications) + implementation(projects.domain.swap.models) /** Compose libraries */ diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/di/SendFeatureModule.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/di/SendFeatureModule.kt index 0cde15afdd..a4078c9cca 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/di/SendFeatureModule.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/di/SendFeatureModule.kt @@ -2,10 +2,8 @@ package com.tangem.features.send.v2.di import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.features.send.v2.DefaultSendFeatureToggles -import com.tangem.features.send.v2.api.NFTSendComponent -import com.tangem.features.send.v2.api.SendComponent -import com.tangem.features.send.v2.api.SendFeatureToggles -import com.tangem.features.send.v2.api.SendNotificationsComponent +import com.tangem.features.send.v2.api.* +import com.tangem.features.send.v2.entrypoint.DefaultSendEntryPointComponent import com.tangem.features.send.v2.send.DefaultSendComponent import com.tangem.features.send.v2.sendnft.DefaultNFTSendComponent import com.tangem.features.send.v2.subcomponents.notifications.DefaultSendNotificationsComponent @@ -43,4 +41,10 @@ internal interface SendFeatureModuleBinds { fun provideNotificationComponentFactory( impl: DefaultSendNotificationsComponent.Factory, ): SendNotificationsComponent.Factory + + @Binds + @Singleton + fun provideSendEntryPointComponentFactory( + impl: DefaultSendEntryPointComponent.Factory, + ): SendEntryPointComponent.Factory } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt new file mode 100644 index 0000000000..4d66bc37aa --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt @@ -0,0 +1,71 @@ +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.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.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.entrypoint.model.SendEntryPointModel +import com.tangem.features.swap.v2.api.SendWithSwapComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultSendEntryPointComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted private val params: SendEntryPointComponent.Params, + sendWithSwapComponentFactory: SendWithSwapComponent.Factory, + sendComponentFactory: SendComponent.Factory, +) : SendEntryPointComponent, AppComponentContext by appComponentContext { + + private val model: SendEntryPointModel = getOrCreateModel(params = params) + + private val sendWithSwapComponent = sendWithSwapComponentFactory.create( + context = child("sendEntrySendWithSwap"), + params = SendWithSwapComponent.Params( + userWalletId = params.userWalletId, + currency = params.cryptoCurrency, + callback = model, + ), + ) + + private val sendComponent = sendComponentFactory.create( + context = child("sendEntryVanillaSend"), + params = SendComponent.Params( + userWalletId = params.userWalletId, + currency = params.cryptoCurrency, + callback = model, + ), + ) + + @Composable + override fun Content(modifier: Modifier) { + val sendEntryState by model.sendEntryPointState.collectAsStateWithLifecycle() + + sendComponent.Content(modifier) + + AnimatedVisibility( + visible = sendEntryState == SendEntryPoint.SendWithSwap, + enter = fadeIn(), + exit = fadeOut(), + ) { + sendWithSwapComponent.Content(modifier) + } + } + + @AssistedFactory + interface Factory : SendEntryPointComponent.Factory { + override fun create( + context: AppComponentContext, + params: SendEntryPointComponent.Params, + ): DefaultSendEntryPointComponent + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/di/SendEntryPointModule.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/di/SendEntryPointModule.kt new file mode 100644 index 0000000000..e44f39adfd --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/di/SendEntryPointModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.send.v2.entrypoint.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.send.v2.entrypoint.model.SendEntryPointModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(ModelComponent::class) +internal interface SendEntryPointModule { + + @Binds + @IntoMap + @ClassKey(SendEntryPointModel::class) + fun provideSendEntryPointModel(model: SendEntryPointModel): Model +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt new file mode 100644 index 0000000000..9e284bbd4f --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt @@ -0,0 +1,79 @@ +package com.tangem.features.send.v2.entrypoint.model + +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.send.v2.api.SendComponent +import com.tangem.features.send.v2.api.SendEntryPointComponent +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.launch + +@ModelScoped +internal class SendEntryPointModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + val appRouter: AppRouter, + private val swapChooseTokenNetworkListener: SwapChooseTokenNetworkListener, + private val sendAmountUpdateTrigger: SendAmountUpdateTrigger, + private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger, +) : Model(), SendComponent.ModelCallback, SendWithSwapComponent.ModelCallback { + + private val params: SendEntryPointComponent.Params = paramsContainer.require() + + val sendEntryPointState: StateFlow + field = MutableStateFlow(SendEntryPoint.SendVanilla) + + private var swapChooseTokenListenerJobHolder = JobHolder() + + override fun onConvertToAnotherToken(lastAmount: String) { + appRouter.push( + AppRoute.ChooseManagedTokens( + userWalletId = params.userWalletId, + initialCurrency = params.cryptoCurrency, + source = AppRoute.ChooseManagedTokens.Source.SendViaSwap, + ), + ) + observeChooseSelectToken(lastAmount) + } + + override fun onCloseSwap(lastAmount: String) { + modelScope.launch { + if (lastAmount.isNotBlank()) { + sendAmountUpdateTrigger.triggerUpdateAmount(lastAmount) + } + triggerScreenUpdate(SendEntryPoint.SendVanilla) + } + } + + private fun observeChooseSelectToken(lastAmount: String) { + swapChooseTokenNetworkListener.swapChooseTokenNetworkResultFlow + .onEach { currency -> + if (lastAmount.isNotBlank()) { + swapAmountUpdateTrigger.triggerUpdateAmount(lastAmount) + } + triggerScreenUpdate(SendEntryPoint.SendWithSwap) + } + .launchIn(modelScope) + .saveIn(swapChooseTokenListenerJobHolder) + } + + private fun triggerScreenUpdate(entry: SendEntryPoint) { + swapChooseTokenListenerJobHolder.cancel() + sendEntryPointState.update { entry } + } +} + +enum class SendEntryPoint { + SendVanilla, + SendWithSwap, +} \ No newline at end of file diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 68e9bdc3a5..4e929dfdb3 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -101,5 +101,6 @@ dependencies { implementation(projects.features.pushNotifications.api) implementation(projects.features.swap.api) implementation(projects.features.txhistory.api) + implementation(projects.features.sendV2.api) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 55966eb359..ad68a2e248 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -80,6 +80,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBala import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express.ExpressStatusFactory +import com.tangem.features.send.v2.api.SendFeatureToggles import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.tokendetails.impl.R import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter @@ -137,6 +138,7 @@ internal class TokenDetailsModel @Inject constructor( private val router: InnerTokenDetailsRouter, private val tokenDetailsDeepLinkActionListener: TokenDetailsDeepLinkActionListener, private val analyticsExceptionHandler: AnalyticsExceptionHandler, + private val sendFeatureToggles: SendFeatureToggles, ) : Model(), TokenDetailsClickIntents { private val params = paramsContainer.require() @@ -564,10 +566,17 @@ internal class TokenDetailsModel @Inject constructor( } private fun sendCurrency() { - val route = AppRoute.Send( - currency = cryptoCurrency, - userWalletId = userWallet.walletId, - ) + val route = if (sendFeatureToggles.isSendWithSwapEnabled) { + AppRoute.SendEntryPoint( + currency = cryptoCurrency, + userWalletId = userWallet.walletId, + ) + } else { + AppRoute.Send( + currency = cryptoCurrency, + userWalletId = userWallet.walletId, + ) + } appRouter.push(route) } diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 4f26ae1d9a..c2ec06c4c3 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -110,6 +110,7 @@ dependencies { implementation(projects.features.walletSettings.api) implementation(projects.features.biometry.api) implementation(projects.features.nft.api) + implementation(projects.features.sendV2.api) /** Common modules */ implementation(projects.common)