Updated on 2026-08-14
This commit is contained in:
parent
68efa8e30c
commit
46385fa3f0
10 changed files with 249 additions and 2 deletions
|
|
@ -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<ChildStack<AppRoute, RoutingComponent.Child>>,
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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<AppRoute, Child>> = 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) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<Unit, WcRoutingComponent>
|
||||
}
|
||||
|
|
@ -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<WcInnerRoute, ComposableContentComponent>> = 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<WcInnerRoute>,
|
||||
) : 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<out Route>, onComplete: (Boolean) -> Unit) {
|
||||
/** Not allowed */
|
||||
}
|
||||
}
|
||||
|
|
@ -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<WcInnerRoute>())
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue