diff --git a/app/src/main/java/com/tangem/tap/routing/RootContent.kt b/app/src/main/java/com/tangem/tap/routing/RootContent.kt index d0c69a7abe..c4d8c96d97 100644 --- a/app/src/main/java/com/tangem/tap/routing/RootContent.kt +++ b/app/src/main/java/com/tangem/tap/routing/RootContent.kt @@ -17,7 +17,8 @@ import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity import com.arkivanov.decompose.ExperimentalDecomposeApi 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.stackAnimation import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.value.Value import com.tangem.common.routing.AppRoute @@ -33,6 +34,7 @@ import com.tangem.tap.routing.component.RoutingComponent @Composable internal fun RootContent( stack: Value>, + wcContent: @Composable (modifier: Modifier) -> Unit, uiDependencies: UiDependencies, modifier: Modifier = Modifier, ) { @@ -69,6 +71,8 @@ internal fun RootContent( } } + wcContent(Modifier.fillMaxSize()) + TangemSnackbarHost( modifier = Modifier .align(Alignment.BottomCenter) diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index fe42e73ef0..b6cdb0f0be 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -15,6 +15,7 @@ import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.features.walletconnect.components.WcRoutingComponent import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.routing.RootContent import com.tangem.tap.routing.component.RoutingComponent @@ -32,10 +33,16 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private val childFactory: ChildFactory, private val appRouterConfig: AppRouterConfig, private val uiDependencies: UiDependencies, + private val wcRoutingComponentFactory: WcRoutingComponent.Factory, ) : RoutingComponent, AppComponentContext by context, SnackbarHandler { + private val wcRoutingComponent: WcRoutingComponent by lazy { + wcRoutingComponentFactory + .create(childByContext(componentContext = this), params = Unit) + } + private val stack: Value> = childStack( source = navigationProvider.getOrCreateTyped(), initialStack = { getInitialStackOrInit() }, @@ -53,7 +60,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( stack.subscribe(lifecycle) { stack -> val stackItems = stack.items.map { it.configuration } - + wcRoutingComponent.onAppRouteChange(stack.active.configuration) if (appRouterConfig.stack != stackItems) { appRouterConfig.stack = stackItems } @@ -66,6 +73,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor( modifier = modifier, stack = stack, uiDependencies = uiDependencies, + wcContent = { wcRoutingComponent.Content(it) }, ) } diff --git a/features/walletconnect/api/build.gradle.kts b/features/walletconnect/api/build.gradle.kts index 9eea21d4c0..c1db369e4a 100644 --- a/features/walletconnect/api/build.gradle.kts +++ b/features/walletconnect/api/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { /** Core */ implementation(projects.core.decompose) implementation(projects.core.ui) + implementation(projects.common.routing) /** Domain models */ implementation(projects.domain.wallets.models) diff --git a/features/walletconnect/api/src/main/kotlin/com/tangem/features/walletconnect/components/WcRoutingComponent.kt b/features/walletconnect/api/src/main/kotlin/com/tangem/features/walletconnect/components/WcRoutingComponent.kt new file mode 100644 index 0000000000..2f18f247e4 --- /dev/null +++ b/features/walletconnect/api/src/main/kotlin/com/tangem/features/walletconnect/components/WcRoutingComponent.kt @@ -0,0 +1,12 @@ +package com.tangem.features.walletconnect.components + +import com.tangem.common.routing.AppRoute +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface WcRoutingComponent : ComposableContentComponent { + + fun onAppRouteChange(appRoute: AppRoute) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt new file mode 100644 index 0000000000..9593b8d1fa --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt @@ -0,0 +1,65 @@ +package com.tangem.features.walletconnect.connections.routing + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.slot.ChildSlot +import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.value.Value +import com.arkivanov.decompose.value.subscribe +import com.tangem.common.routing.AppRoute +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.walletconnect.components.WcRoutingComponent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +@Suppress("UnusedPrivateMember") +internal class DefaultWcRoutingComponent @AssistedInject constructor( + @Assisted private val appComponentContext: AppComponentContext, + @Assisted params: Unit, +) : AppComponentContext by appComponentContext, WcRoutingComponent { + + private val model: WcRoutingModel = getOrCreateModel() + + private val slot: Value> = childSlot( + source = model.innerRouter.slotNavigation, + serializer = WcInnerRoute.serializer(), + childFactory = ::childFactory, + ) + + init { + slot.subscribe(lifecycle) { slot -> if (slot.child == null) model.onSlotEmpty() } + } + + @Composable + override fun Content(modifier: Modifier) { + val slot by slot.subscribeAsState() + slot.child?.instance?.Content(modifier) + } + + override fun onAppRouteChange(appRoute: AppRoute) { + model.onAppRouteChange(appRoute) + } + + private fun childFactory(config: WcInnerRoute, componentContext: ComponentContext): ComposableContentComponent { + val childContext = childByContext( + componentContext = componentContext, + router = model.innerRouter, + ) + return when (config) { + is WcInnerRoute.SignMessage -> TODO() + is WcInnerRoute.Pair -> TODO() + } + } + + @AssistedFactory + interface Factory : WcRoutingComponent.Factory { + override fun create(context: AppComponentContext, params: Unit): DefaultWcRoutingComponent + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt new file mode 100644 index 0000000000..eae62e34c4 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt @@ -0,0 +1,21 @@ +package com.tangem.features.walletconnect.connections.routing + +import com.tangem.core.decompose.navigation.Route +import com.tangem.domain.walletconnect.model.WcPairRequest +import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest +import kotlinx.serialization.Serializable + +@Serializable +internal sealed interface WcInnerRoute : Route { + + @Serializable + sealed interface Method : WcInnerRoute { + val rawRequest: WcSdkSessionRequest + } + + @Serializable + data class SignMessage(override val rawRequest: WcSdkSessionRequest) : Method + + @Serializable + data class Pair(val request: WcPairRequest) : WcInnerRoute +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRouter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRouter.kt new file mode 100644 index 0000000000..cf545cf82d --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRouter.kt @@ -0,0 +1,35 @@ +package com.tangem.features.walletconnect.connections.routing + +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.arkivanov.decompose.router.slot.navigate +import com.tangem.core.decompose.navigation.Route +import com.tangem.core.decompose.navigation.Router +import kotlin.reflect.KClass + +internal class WcRouter( + val slotNavigation: SlotNavigation, +) : Router { + + override fun push(route: Route, onComplete: (Boolean) -> Unit) { + val isComplete = route is WcInnerRoute + if (isComplete) slotNavigation.activate(route) + onComplete(isComplete) + } + + override fun replaceAll(vararg routes: Route, onComplete: (Boolean) -> Unit) { + /** Not allowed */ + } + + override fun pop(onComplete: (Boolean) -> Unit) { + slotNavigation.navigate { null } + } + + override fun popTo(route: Route, onComplete: (Boolean) -> Unit) { + /** Not allowed */ + } + + override fun popTo(routeClass: KClass, onComplete: (Boolean) -> Unit) { + /** Not allowed */ + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt new file mode 100644 index 0000000000..114e0b1e39 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt @@ -0,0 +1,89 @@ +package com.tangem.features.walletconnect.connections.routing + +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.tangem.common.routing.AppRoute +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.domain.walletconnect.WcPairService +import com.tangem.domain.walletconnect.WcRequestService +import com.tangem.domain.walletconnect.model.WcEthMethodName +import com.tangem.domain.walletconnect.model.WcMethodName +import com.tangem.domain.walletconnect.model.WcSolanaMethodName +import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* +import javax.inject.Inject + +@ModelScoped +internal class WcRoutingModel @Inject constructor( + private val requestService: WcRequestService, + private val pairService: WcPairService, + override val dispatchers: CoroutineDispatcherProvider, + private val featureToggles: WalletConnectFeatureToggles, +) : Model() { + + val innerRouter = WcRouter(SlotNavigation()) + + private val isSlotEmpty = MutableStateFlow(true) + private val permittedAppRoute = MutableStateFlow(false) + + init { + if (featureToggles.isRedesignedWalletConnectEnabled) { + setupQueue() + } + } + + fun onSlotEmpty() { + isSlotEmpty.update { true } + } + + private fun setupQueue() { + val requestFlow = requestService.wcRequest + .map { (methodName, rawRequest) -> + when (methodName) { + WcEthMethodName.EthSign, + WcEthMethodName.PersonalSign, + -> WcInnerRoute.SignMessage(rawRequest) + WcEthMethodName.SignTypeData -> TODO() + WcEthMethodName.SignTypeDataV4 -> TODO() + WcEthMethodName.SignTransaction -> TODO() + WcEthMethodName.SendTransaction -> TODO() + WcSolanaMethodName.SignMessage -> TODO() + WcSolanaMethodName.SignTransaction -> TODO() + WcSolanaMethodName.SendAllTransaction -> TODO() + is WcMethodName.Unsupported -> TODO() + } + } + + val pairFlow = pairService.pairFlow + .map { request -> WcInnerRoute.Pair(request) } + + merge(requestFlow, pairFlow) + .onEach { configuration -> + awaitQueueReady() + isSlotEmpty.update { false } + innerRouter.push(configuration) + } + .launchIn(modelScope) + } + + private suspend fun awaitQueueReady() = combine( + isSlotEmpty, + permittedAppRoute, + ) { isSlotEmpty, permittedAppRoute -> isSlotEmpty && permittedAppRoute } + .first { it } + + fun onAppRouteChange(appRoute: AppRoute) { + permittedAppRoute.value = when (appRoute) { + AppRoute.Initial, + is AppRoute.Welcome, + is AppRoute.Disclaimer, + is AppRoute.Stories, + -> { + innerRouter.pop() + false + } + else -> true + } + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectFeatureModule.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectFeatureModule.kt index e8a98073d4..3b9df5bd90 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectFeatureModule.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectFeatureModule.kt @@ -1,7 +1,9 @@ package com.tangem.features.walletconnect.di import com.tangem.features.walletconnect.components.WalletConnectEntryComponent +import com.tangem.features.walletconnect.components.WcRoutingComponent import com.tangem.features.walletconnect.connections.components.DefaultWalletConnectEntryComponent +import com.tangem.features.walletconnect.connections.routing.DefaultWcRoutingComponent import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -17,4 +19,8 @@ internal interface WalletConnectFeatureModule { fun bindWalletConnectEntryComponentFactory( factory: DefaultWalletConnectEntryComponent.Factory, ): WalletConnectEntryComponent.Factory + + @Binds + @Singleton + fun bindWcRoutingComponentFactory(factory: DefaultWcRoutingComponent.Factory): WcRoutingComponent.Factory } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt index a9f3afb61d..cc9d17fe49 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/di/WalletConnectModelModule.kt @@ -5,6 +5,7 @@ import com.tangem.core.decompose.model.Model import com.tangem.features.walletconnect.connections.model.WcAppInfoModel import com.tangem.features.walletconnect.connections.model.WcConnectedAppInfoModel import com.tangem.features.walletconnect.connections.model.WcConnectionsModel +import com.tangem.features.walletconnect.connections.routing.WcRoutingModel import com.tangem.features.walletconnect.transaction.model.WcSignTransactionModel import dagger.Binds import dagger.Module @@ -26,6 +27,11 @@ internal interface WalletConnectModelModule { @ClassKey(WcAppInfoModel::class) fun bindWcAppInfoModel(model: WcAppInfoModel): Model + @Binds + @IntoMap + @ClassKey(WcRoutingModel::class) + fun bindWcRoutingModel(model: WcRoutingModel): Model + @Binds @IntoMap @ClassKey(WcConnectedAppInfoModel::class)