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 3c0ac636bb..a8a9ba7d66 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 @@ -19,6 +19,7 @@ import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onramp.component.* import com.tangem.features.pushnotifications.api.PushNotificationsComponent import com.tangem.features.send.api.SendComponent +import com.tangem.features.send.v2.api.NFTSendComponent import com.tangem.features.send.v2.api.SendFeatureToggles import com.tangem.features.staking.api.StakingComponent import com.tangem.features.swap.SwapComponent @@ -86,6 +87,7 @@ internal class ChildFactory @Inject constructor( private val nftCollectionsComponentFactory: NFTCollectionsComponent.Factory, private val nftReceiveComponentFactory: NFTReceiveComponent.Factory, private val nftDetailsComponentFactory: NFTDetailsComponent.Factory, + private val nftSendComponentFactory: NFTSendComponent.Factory, private val testerRouter: TesterRouter, private val routingFeatureToggles: RoutingFeatureToggles, private val walletConnectFeatureToggles: WalletConnectFeatureToggles, @@ -423,6 +425,17 @@ internal class ChildFactory @Inject constructor( params = NFTDetailsComponent.Params(userWalletId = route.userWalletId, nftAsset = route.nftAsset), componentFactory = nftDetailsComponentFactory, ) + is AppRoute.NFTSend -> { + createComponentChild( + context = context, + params = NFTSendComponent.Params( + userWalletId = route.userWalletId, + nftAsset = route.nftAsset, + nftCollectionName = route.nftCollectionName, + ), + componentFactory = nftSendComponentFactory, + ) + } is AppRoute.OnboardingNote, is AppRoute.SaveWallet, is AppRoute.OnboardingOther, @@ -775,6 +788,17 @@ internal class ChildFactory @Inject constructor( params = NFTDetailsComponent.Params(userWalletId = route.userWalletId, nftAsset = route.nftAsset), componentFactory = nftDetailsComponentFactory, ) + is AppRoute.NFTSend -> { + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = NFTSendComponent.Params( + userWalletId = route.userWalletId, + nftAsset = route.nftAsset, + nftCollectionName = route.nftCollectionName, + ), + componentFactory = nftSendComponentFactory, + ) + } } // endregion } 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 cc4bda0a08..8a62fb9023 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 @@ -295,4 +295,11 @@ sealed class AppRoute(val path: String) : Route { val userWalletId: UserWalletId, val nftAsset: NFTAsset, ) : AppRoute(path = "/nft_details/${userWalletId.stringValue}/${nftAsset.collectionId}/${nftAsset.id.stringValue}") + + @Serializable + data class NFTSend( + val userWalletId: UserWalletId, + val nftAsset: NFTAsset, + val nftCollectionName: String, + ) : AppRoute(path = "/send/nft/${userWalletId.stringValue}/$nftCollectionName/${nftAsset.id}") } \ No newline at end of file diff --git a/features/send-v2/api/build.gradle.kts b/features/send-v2/api/build.gradle.kts index 7bf806b77c..fce40fb61c 100644 --- a/features/send-v2/api/build.gradle.kts +++ b/features/send-v2/api/build.gradle.kts @@ -16,4 +16,5 @@ dependencies { /** Domain models */ implementation(projects.domain.wallets.models) implementation(projects.domain.tokens.models) + implementation(projects.domain.nft.models) } \ No newline at end of file diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/NFTSendComponent.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/NFTSendComponent.kt new file mode 100644 index 0000000000..2403e179e6 --- /dev/null +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/NFTSendComponent.kt @@ -0,0 +1,17 @@ +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.nft.models.NFTAsset +import com.tangem.domain.wallets.models.UserWalletId + +interface NFTSendComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + val nftAsset: NFTAsset, + val nftCollectionName: String, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file 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 e8d18db28b..3c2f9bcf9f 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,9 +2,11 @@ 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.send.DefaultSendComponent +import com.tangem.features.send.v2.sendnft.DefaultNFTSendComponent import dagger.Binds import dagger.Module import dagger.Provides @@ -29,4 +31,8 @@ internal interface SendFeatureModuleBinds { @Binds @Singleton fun provideSendComponentFactory(impl: DefaultSendComponent.Factory): SendComponent.Factory + + @Binds + @Singleton + fun provideNFTSendComponentFactory(impl: DefaultNFTSendComponent.Factory): NFTSendComponent.Factory } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/di/SendModelModule.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/di/SendModelModule.kt index 38ff838b69..3c21d1380d 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/di/SendModelModule.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/di/SendModelModule.kt @@ -2,8 +2,6 @@ package com.tangem.features.send.v2.di import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model -import com.tangem.features.send.v2.send.confirm.model.SendConfirmModel -import com.tangem.features.send.v2.send.model.SendModel import com.tangem.features.send.v2.subcomponents.amount.model.SendAmountModel import com.tangem.features.send.v2.subcomponents.destination.model.SendDestinationModel import com.tangem.features.send.v2.subcomponents.fee.model.SendFeeModel @@ -18,11 +16,6 @@ import dagger.multibindings.IntoMap @InstallIn(ModelComponent::class) internal interface SendModelModule { - @Binds - @IntoMap - @ClassKey(SendModel::class) - fun provideSendModel(model: SendModel): Model - @Binds @IntoMap @ClassKey(SendAmountModel::class) @@ -38,11 +31,6 @@ internal interface SendModelModule { @ClassKey(SendFeeModel::class) fun provideSendFeeModel(model: SendFeeModel): Model - @Binds - @IntoMap - @ClassKey(SendConfirmModel::class) - fun provideSendConfirmModel(model: SendConfirmModel): Model - @Binds @IntoMap @ClassKey(NotificationsModel::class) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/di/SendModelModule.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/di/SendModelModule.kt new file mode 100644 index 0000000000..7405859941 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/di/SendModelModule.kt @@ -0,0 +1,26 @@ +package com.tangem.features.send.v2.send.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.send.v2.send.confirm.model.SendConfirmModel +import com.tangem.features.send.v2.send.model.SendModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(ModelComponent::class) +internal interface SendModelModule { + + @Binds + @IntoMap + @ClassKey(SendModel::class) + fun provideSendModel(model: SendModel): Model + + @Binds + @IntoMap + @ClassKey(SendConfirmModel::class) + fun provideSendConfirmModel(model: SendConfirmModel): Model +} \ No newline at end of file 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 new file mode 100644 index 0000000000..8074966261 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/DefaultNFTSendComponent.kt @@ -0,0 +1,89 @@ +package com.tangem.features.send.v2.sendnft + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.features.send.v2.api.NFTSendComponent +import com.tangem.features.send.v2.sendnft.model.NFTSendModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultNFTSendComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted private val params: NFTSendComponent.Params, +) : NFTSendComponent, AppComponentContext by appComponentContext { + + // private val stackNavigation = StackNavigation() + // + // private val innerRouter = InnerRouter( + // stackNavigation = stackNavigation, + // popCallback = { onChildBack() }, + // ) + + // private val initialRoute = NFTSendRoute.Empty + // private val currentRouteFlow = MutableStateFlow(initialRoute) + + private val model: NFTSendModel = getOrCreateModel(params = params/*, router = innerRouter*/) + + // private val childStack = childStack( + // key = "NFTSendInnerStack", + // source = stackNavigation, + // serializer = null, + // initialConfiguration = initialRoute, + // handleBackButton = true, + // childFactory = { configuration, factoryContext -> + // // todo + // ComposableContentComponent { } + // }, + // ) + + init { + // childStack.subscribe( + // lifecycle = lifecycle, + // mode = ObserveLifecycleMode.CREATE_DESTROY, + // ) { stack -> + // componentScope.launch { + // when (val activeComponent = stack.active.instance) { + // is SendDestinationComponent -> { + // // analyticsEventHandler.send(SendAnalyticEvents.AddressScreenOpened) + // activeComponent.updateState(model.uiState.value.destinationUM) + // } + // is SendFeeComponent -> { + // // analyticsEventHandler.send(SendAnalyticEvents.FeeScreenOpened) + // } + // } + // currentRouteFlow.emit(stack.active.configuration) + // } + // } + } + + @Composable + override fun Content(modifier: Modifier) { + // val stackState by childStack.subscribeAsState() + // val state by model.uiState.collectAsStateWithLifecycle() + + BackHandler(onBack = ::onChildBack) + // TODO + } + + private fun onChildBack() { + // TODO + // val isEmptyRoute = childStack.value.active.configuration == NFTSendRoute.Empty + // val isEmptyStack = childStack.value.backStack.isEmpty() + // val isSuccess = model.uiState.value.confirmUM is ConfirmUM.Success + // + // if (isEmptyRoute || isEmptyStack || isSuccess) { + // router.pop() + // } else { + // stackNavigation.pop() + // } + } + + @AssistedFactory + interface Factory : NFTSendComponent.Factory { + override fun create(context: AppComponentContext, params: NFTSendComponent.Params): DefaultNFTSendComponent + } +} \ 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 new file mode 100644 index 0000000000..cf70962383 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/di/NFTSendModelModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.send.v2.sendnft.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.send.v2.sendnft.model.NFTSendModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(ModelComponent::class) +internal interface NFTSendModelModule { + + @Binds + @IntoMap + @ClassKey(NFTSendModel::class) + fun provideNFTSendModel(model: NFTSendModel): 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 new file mode 100644 index 0000000000..a87005f006 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt @@ -0,0 +1,144 @@ +package com.tangem.features.send.v2.sendnft.model + +import androidx.compose.runtime.Stable +import arrow.core.getOrElse +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.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.features.send.v2.api.NFTSendComponent +import com.tangem.features.send.v2.common.NavigationUM +import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM +import com.tangem.features.send.v2.sendnft.ui.state.NFTSendUM +import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponent +import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM +import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponent +import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import javax.inject.Inject +import kotlin.properties.Delegates + +internal interface SendNFTComponentCallback : + SendFeeComponent.ModelCallback, + SendDestinationComponent.ModelCallback + +@Suppress("LongParameterList") +@Stable +@ModelScoped +internal class NFTSendModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, + private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, +) : Model(), SendNFTComponentCallback { + + val params: NFTSendComponent.Params = paramsContainer.require() + private val userWalletId = params.userWalletId + + private val _uiState = MutableStateFlow(initialState()) + val uiState = _uiState.asStateFlow() + + private val _isBalanceHiddenFlow = MutableStateFlow(false) + val isBalanceHiddenFlow = _isBalanceHiddenFlow.asStateFlow() + + var cryptoCurrency: CryptoCurrency by Delegates.notNull() + var userWallet: UserWallet by Delegates.notNull() + var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() + var feeCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() + var appCurrency: AppCurrency = AppCurrency.Default + + init { + subscribeOnCurrencyStatusUpdates() + initAppCurrency() + } + + override fun onNavigationResult(navigationUM: NavigationUM) { + _uiState.update { it.copy(navigationUM = navigationUM) } + } + + override fun onDestinationResult(destinationUM: DestinationUM) { + _uiState.update { it.copy(destinationUM = destinationUM) } + + // todo + } + + override fun onFeeResult(feeUM: FeeUM) { + _uiState.update { it.copy(feeUM = feeUM) } + router.pop() + } + + private fun initAppCurrency() { + modelScope.launch { + appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default } + } + } + + private fun subscribeOnCurrencyStatusUpdates() { + modelScope.launch { + getUserWalletUseCase(params.userWalletId).fold( + ifRight = { wallet -> + userWallet = wallet + + // cryptoCurrency = getCryptoCurrenciesUseCase(userWalletId).getOrNull() + // ?.filterIsInstance() + // ?.firstOrNull { it.network == nftAsset.network } + // ?: return@launch + + getCurrenciesStatusUpdates( + isSingleWalletWithToken = wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), + ) + }, + ifLeft = { + // sendConfirmAlertFactory.getGenericErrorState(::onFailedTxEmailClick) + return@launch + }, + ) + } + } + + private fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean) { + getCurrencyStatusUpdatesUseCase( + userWalletId = userWalletId, + currencyId = cryptoCurrency.id, + isSingleWalletWithTokens = isSingleWalletWithToken, + ).onEach { maybeCryptoCurrency -> + maybeCryptoCurrency.fold( + ifRight = { cryptoStatus -> + cryptoCurrencyStatus = cryptoStatus + feeCryptoCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase( + userWalletId = userWalletId, + cryptoCurrencyStatus = cryptoStatus, + ).getOrNull() ?: cryptoStatus + + // router.push(NFTSendRoute.Destination(isEditMode = false)) + }, + ifLeft = { + // sendConfirmAlertFactory.getGenericErrorState { + // onFailedTxEmailClick(it.toString()) + // } + }, + ) + }.launchIn(modelScope) + } + + private fun initialState(): NFTSendUM = NFTSendUM( + destinationUM = DestinationUM.Empty(), + feeUM = FeeUM.Empty(), + confirmUM = ConfirmUM.Empty, + navigationUM = NavigationUM.Empty, + ) +} \ 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 new file mode 100644 index 0000000000..56f0d674d2 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/ui/state/NFTSendUM.kt @@ -0,0 +1,13 @@ +package com.tangem.features.send.v2.sendnft.ui.state + +import com.tangem.features.send.v2.common.NavigationUM +import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM +import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM +import com.tangem.features.send.v2.subcomponents.fee.ui.state.FeeUM + +internal data class NFTSendUM( + val destinationUM: DestinationUM, + val feeUM: FeeUM, + val confirmUM: ConfirmUM, + val navigationUM: NavigationUM, +) \ No newline at end of file