diff --git a/app/src/main/kotlin/io/element/android/x/MainNode.kt b/app/src/main/kotlin/io/element/android/x/MainNode.kt index 8e7d0f194d..44006a6da5 100644 --- a/app/src/main/kotlin/io/element/android/x/MainNode.kt +++ b/app/src/main/kotlin/io/element/android/x/MainNode.kt @@ -27,7 +27,7 @@ import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.plugin.Plugin -import io.element.android.appnav.LoggedInFlowNode +import io.element.android.appnav.LoggedInAppScopeFlowNode import io.element.android.appnav.room.RoomLoadedFlowNode import io.element.android.appnav.RootFlowNode import io.element.android.libraries.architecture.bindings @@ -56,7 +56,7 @@ class MainNode( ), DaggerComponentOwner by mainDaggerComponentOwner { - private val loggedInFlowNodeCallback = object : LoggedInFlowNode.LifecycleCallback { + private val loggedInFlowNodeCallback = object : LoggedInAppScopeFlowNode.LifecycleCallback { override fun onFlowCreated(identifier: String, client: MatrixClient) { val component = bindings().sessionComponentBuilder().client(client).build() mainDaggerComponentOwner.addComponent(identifier, component) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt new file mode 100644 index 0000000000..501f1dbc3f --- /dev/null +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.appnav + +import android.os.Parcelable +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import coil.Coil +import com.bumble.appyx.core.composable.Children +import com.bumble.appyx.core.lifecycle.subscribe +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins +import com.bumble.appyx.navmodel.backstack.BackStack +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.architecture.BackstackNode +import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler +import io.element.android.libraries.architecture.bindings +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.architecture.waitForChildAttached +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.ui.di.MatrixUIBindings +import kotlinx.parcelize.Parcelize + +@ContributesNode(AppScope::class) +class LoggedInAppScopeFlowNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : BackstackNode( + backstack = BackStack( + initialElement = NavTarget.Root, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins +) { + interface Callback : Plugin { + fun onOpenBugReport() + } + + sealed interface NavTarget : Parcelable { + @Parcelize + data object Root : NavTarget + } + + interface LifecycleCallback : NodeLifecycleCallback { + fun onFlowCreated(identifier: String, client: MatrixClient) + + fun onFlowReleased(identifier: String, client: MatrixClient) + } + + data class Inputs( + val matrixClient: MatrixClient + ) : NodeInputs + + private val inputs: Inputs = inputs() + + override fun onBuilt() { + super.onBuilt() + lifecycle.subscribe( + onCreate = { + plugins().forEach { it.onFlowCreated(id, inputs.matrixClient) } + val imageLoaderFactory = bindings().loggedInImageLoaderFactory() + Coil.setImageLoader(imageLoaderFactory) + }, + onDestroy = { + plugins().forEach { it.onFlowReleased(id, inputs.matrixClient) } + } + ) + } + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + NavTarget.Root -> { + val callback = object : LoggedInFlowNode.Callback { + override fun onOpenBugReport() { + plugins().forEach { it.onOpenBugReport() } + } + } + createNode(buildContext, listOf(callback)) + } + } + } + + suspend fun attachSession(): LoggedInFlowNode { + return waitForChildAttached { navTarget -> + navTarget is NavTarget.Root + } + } + + @Composable + override fun View(modifier: Modifier) { + Children( + navModel = backstack, + modifier = modifier, + transitionHandler = rememberDefaultTransitionHandler(), + ) + } +} diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 7943151a5e..e2da3afa3d 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import coil.Coil import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext @@ -53,19 +52,15 @@ import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.features.roomlist.api.RoomListEntryPoint import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.libraries.architecture.BackstackNode -import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler -import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.createNode -import io.element.android.libraries.architecture.inputs import io.element.android.libraries.deeplink.DeeplinkData import io.element.android.libraries.designsystem.utils.SnackbarDispatcher -import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.MAIN_SPACE import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.sync.SyncState -import io.element.android.libraries.matrix.ui.di.MatrixUIBindings import io.element.android.libraries.push.api.notifications.NotificationDrawerManager import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope @@ -76,7 +71,7 @@ import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import timber.log.Timber -@ContributesNode(AppScope::class) +@ContributesNode(SessionScope::class) class LoggedInFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, @@ -91,6 +86,7 @@ class LoggedInFlowNode @AssistedInject constructor( private val networkMonitor: NetworkMonitor, private val notificationDrawerManager: NotificationDrawerManager, private val ftueState: FtueState, + private val matrixClient: MatrixClient, snackbarDispatcher: SnackbarDispatcher, ) : BackstackNode( backstack = BackStack( @@ -105,32 +101,18 @@ class LoggedInFlowNode @AssistedInject constructor( fun onOpenBugReport() } - interface LifecycleCallback : NodeLifecycleCallback { - fun onFlowCreated(identifier: String, client: MatrixClient) - - fun onFlowReleased(identifier: String, client: MatrixClient) - } - - data class Inputs( - val matrixClient: MatrixClient - ) : NodeInputs - - private val inputs: Inputs = inputs() - private val syncService = inputs.matrixClient.syncService() + private val syncService = matrixClient.syncService() private val loggedInFlowProcessor = LoggedInEventProcessor( snackbarDispatcher, - inputs.matrixClient.roomMembershipObserver(), - inputs.matrixClient.sessionVerificationService(), + matrixClient.roomMembershipObserver(), + matrixClient.sessionVerificationService(), ) override fun onBuilt() { super.onBuilt() lifecycle.subscribe( onCreate = { - plugins().forEach { it.onFlowCreated(id, inputs.matrixClient) } - val imageLoaderFactory = bindings().loggedInImageLoaderFactory() - Coil.setImageLoader(imageLoaderFactory) - appNavigationStateService.onNavigateToSession(id, inputs.matrixClient.sessionId) + appNavigationStateService.onNavigateToSession(id, matrixClient.sessionId) // TODO We do not support Space yet, so directly navigate to main space appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE) loggedInFlowProcessor.observeEvents(coroutineScope) @@ -146,7 +128,6 @@ class LoggedInFlowNode @AssistedInject constructor( } }, onDestroy = { - plugins().forEach { it.onFlowReleased(id, inputs.matrixClient) } appNavigationStateService.onLeavingSpace(id) appNavigationStateService.onLeavingSession(id) loggedInFlowProcessor.stopObserving() @@ -351,4 +332,3 @@ class LoggedInFlowNode @AssistedInject constructor( backstack.push(NavTarget.InviteList) } } - diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 089e956c61..0841137f90 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -72,15 +72,14 @@ class RootFlowNode @AssistedInject constructor( private val bugReportEntryPoint: BugReportEntryPoint, private val intentResolver: IntentResolver, private val oidcActionFlow: OidcActionFlow, -) : - BackstackNode( - backstack = BackStack( - initialElement = NavTarget.SplashScreen, - savedStateMap = buildContext.savedStateMap, - ), - buildContext = buildContext, - plugins = plugins - ) { +) : BackstackNode( + backstack = BackStack( + initialElement = NavTarget.SplashScreen, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins +) { override fun onBuilt() { matrixClientsHolder.restoreWithSavedState(buildContext.savedStateMap) @@ -191,14 +190,14 @@ class RootFlowNode @AssistedInject constructor( val matrixClient = matrixClientsHolder.getOrNull(navTarget.sessionId) ?: return splashNode(buildContext).also { Timber.w("Couldn't find any session, go through SplashScreen") } - val inputs = LoggedInFlowNode.Inputs(matrixClient) - val callback = object : LoggedInFlowNode.Callback { + val inputs = LoggedInAppScopeFlowNode.Inputs(matrixClient) + val callback = object : LoggedInAppScopeFlowNode.Callback { override fun onOpenBugReport() { backstack.push(NavTarget.BugReport) } } val nodeLifecycleCallbacks = plugins() - createNode(buildContext, plugins = listOf(inputs, callback) + nodeLifecycleCallbacks) + createNode(buildContext, plugins = listOf(inputs, callback) + nodeLifecycleCallbacks) } NavTarget.NotLoggedInFlow -> createNode(buildContext) NavTarget.SplashScreen -> splashNode(buildContext) @@ -233,6 +232,7 @@ class RootFlowNode @AssistedInject constructor( private suspend fun navigateTo(deeplinkData: DeeplinkData) { Timber.d("Navigating to $deeplinkData") attachSession(deeplinkData.sessionId) + .attachSession() .apply { when (deeplinkData) { is DeeplinkData.Root -> attachRoot() @@ -246,7 +246,7 @@ class RootFlowNode @AssistedInject constructor( oidcActionFlow.post(oidcAction) } - private suspend fun attachSession(sessionId: SessionId): LoggedInFlowNode { + private suspend fun attachSession(sessionId: SessionId): LoggedInAppScopeFlowNode { //TODO handle multi-session return waitForChildAttached { navTarget -> navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId