diff --git a/app/src/main/kotlin/io/element/android/x/MainActivity.kt b/app/src/main/kotlin/io/element/android/x/MainActivity.kt index cf37b22159..2ebfd2ad12 100644 --- a/app/src/main/kotlin/io/element/android/x/MainActivity.kt +++ b/app/src/main/kotlin/io/element/android/x/MainActivity.kt @@ -34,8 +34,8 @@ import com.bumble.appyx.core.integrationpoint.NodeComponentActivity import com.bumble.appyx.core.plugin.NodeReadyObserver import io.element.android.libraries.architecture.bindings import io.element.android.libraries.core.log.logger.LoggerTag -import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.designsystem.utils.LocalSnackbarDispatcher +import io.element.android.libraries.theme.ElementTheme import io.element.android.x.di.AppBindings import io.element.android.x.intent.SafeUriHandler import timber.log.Timber @@ -74,7 +74,6 @@ class MainActivity : NodeComponentActivity() { NodeHost(integrationPoint = appyxIntegrationPoint) { MainNode( it, - appBindings.mainDaggerComponentOwner(), plugins = listOf( object : NodeReadyObserver { override fun init(node: MainNode) { @@ -83,7 +82,8 @@ class MainActivity : NodeComponentActivity() { mainNode.handleIntent(intent) } } - ) + ), + context = applicationContext ) } } 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 ce894ec2d6..d412519d35 100644 --- a/app/src/main/kotlin/io/element/android/x/MainNode.kt +++ b/app/src/main/kotlin/io/element/android/x/MainNode.kt @@ -16,6 +16,7 @@ package io.element.android.x +import android.content.Context import android.content.Intent import android.os.Parcelable import androidx.compose.runtime.Composable @@ -27,24 +28,17 @@ 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.LoggedInAppScopeFlowNode import io.element.android.appnav.RootFlowNode -import io.element.android.appnav.room.RoomLoadedFlowNode -import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.DaggerComponentOwner -import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.x.di.MainDaggerComponentsOwner -import io.element.android.x.di.RoomComponent -import io.element.android.x.di.SessionComponent import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize class MainNode( buildContext: BuildContext, - private val mainDaggerComponentOwner: MainDaggerComponentsOwner, plugins: List, + @ApplicationContext context: Context, ) : ParentNode( navModel = PermanentNavModel( navTargets = setOf(RootNavTarget), @@ -53,38 +47,12 @@ class MainNode( buildContext = buildContext, plugins = plugins, ), - DaggerComponentOwner by mainDaggerComponentOwner { + DaggerComponentOwner { - private val loggedInFlowNodeCallback = object : LoggedInAppScopeFlowNode.LifecycleCallback { - override fun onFlowCreated(identifier: String, client: MatrixClient) { - val component = bindings().sessionComponentBuilder().client(client).build() - mainDaggerComponentOwner.addComponent(identifier, component) - } - - override fun onFlowReleased(identifier: String, client: MatrixClient) { - mainDaggerComponentOwner.removeComponent(identifier) - } - } - - private val roomFlowNodeCallback = object : RoomLoadedFlowNode.LifecycleCallback { - override fun onFlowCreated(identifier: String, room: MatrixRoom) { - val component = bindings().roomComponentBuilder().room(room).build() - mainDaggerComponentOwner.addComponent(identifier, component) - } - - override fun onFlowReleased(identifier: String, room: MatrixRoom) { - mainDaggerComponentOwner.removeComponent(identifier) - } - } + override val daggerComponent = (context as DaggerComponentOwner).daggerComponent override fun resolve(navTarget: RootNavTarget, buildContext: BuildContext): Node { - return createNode( - context = buildContext, - plugins = listOf( - loggedInFlowNodeCallback, - roomFlowNodeCallback, - ) - ) + return createNode(context = buildContext) } @Composable @@ -100,4 +68,5 @@ class MainNode( @Parcelize object RootNavTarget : Parcelable + } diff --git a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt index 5fb3523d6e..914be65946 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt @@ -24,7 +24,6 @@ import io.element.android.libraries.matrix.api.tracing.TracingService @ContributesTo(AppScope::class) interface AppBindings { - fun mainDaggerComponentOwner(): MainDaggerComponentsOwner fun snackbarDispatcher(): SnackbarDispatcher fun tracingService(): TracingService fun bugReporter(): BugReporter diff --git a/app/src/main/kotlin/io/element/android/x/di/DefaultRoomComponentFactory.kt b/app/src/main/kotlin/io/element/android/x/di/DefaultRoomComponentFactory.kt new file mode 100644 index 0000000000..44fc54570d --- /dev/null +++ b/app/src/main/kotlin/io/element/android/x/di/DefaultRoomComponentFactory.kt @@ -0,0 +1,33 @@ +/* + * 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.x.di + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.appnav.di.RoomComponentFactory +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.room.MatrixRoom +import javax.inject.Inject + +@ContributesBinding(SessionScope::class) +class DefaultRoomComponentFactory @Inject constructor( + private val roomComponentBuilder: RoomComponent.Builder +) : RoomComponentFactory { + + override fun create(room: MatrixRoom): Any { + return roomComponentBuilder.room(room).build() + } +} diff --git a/app/src/main/kotlin/io/element/android/x/di/DefaultSessionComponentFactory.kt b/app/src/main/kotlin/io/element/android/x/di/DefaultSessionComponentFactory.kt new file mode 100644 index 0000000000..922d25f6af --- /dev/null +++ b/app/src/main/kotlin/io/element/android/x/di/DefaultSessionComponentFactory.kt @@ -0,0 +1,33 @@ +/* + * 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.x.di + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.appnav.di.SessionComponentFactory +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.MatrixClient +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultSessionComponentFactory @Inject constructor( + private val sessionComponentBuilder: SessionComponent.Builder +) : SessionComponentFactory { + + override fun create(client: MatrixClient): Any { + return sessionComponentBuilder.client(client).build() + } +} diff --git a/app/src/main/kotlin/io/element/android/x/di/MainDaggerComponentsOwner.kt b/app/src/main/kotlin/io/element/android/x/di/MainDaggerComponentsOwner.kt deleted file mode 100644 index de800bb587..0000000000 --- a/app/src/main/kotlin/io/element/android/x/di/MainDaggerComponentsOwner.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.x.di - -import android.content.Context -import io.element.android.libraries.di.AppScope -import io.element.android.libraries.di.ApplicationContext -import io.element.android.libraries.di.DaggerComponentOwner -import io.element.android.libraries.di.SingleIn -import javax.inject.Inject - -@SingleIn(AppScope::class) -class MainDaggerComponentsOwner @Inject constructor(@ApplicationContext context: Context) : DaggerComponentOwner { - - private val daggerComponents = LinkedHashMap().apply { - put("app", (context as DaggerComponentOwner).daggerComponent) - } - - fun addComponent(identifier: String, component: Any) { - daggerComponents[identifier] = component - } - - fun removeComponent(identifier: String) { - daggerComponents.remove(identifier) - } - - /** - * We expose the dagger components in the opposite order they arrived. - * So we pick the most recent component when searching with the [io.element.android.libraries.architecture.bindings] methods. - */ - override val daggerComponent: Any - get() = daggerComponents.values.reversed() -} diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt index 6a0e60aaaf..7f36a7a51a 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt @@ -31,12 +31,13 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.appnav.di.SessionComponentFactory import io.element.android.libraries.architecture.NodeInputs 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.di.DaggerComponentOwner import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.ui.di.MatrixUIBindings import kotlinx.parcelize.Parcelize @@ -50,6 +51,7 @@ import kotlinx.parcelize.Parcelize class LoggedInAppScopeFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, + sessionComponentFactory: SessionComponentFactory, ) : ParentNode( navModel = PermanentNavModel( navTargets = setOf(NavTarget), @@ -57,7 +59,7 @@ class LoggedInAppScopeFlowNode @AssistedInject constructor( ), buildContext = buildContext, plugins = plugins -) { +), DaggerComponentOwner { interface Callback : Plugin { fun onOpenBugReport() } @@ -65,29 +67,20 @@ class LoggedInAppScopeFlowNode @AssistedInject constructor( @Parcelize object NavTarget : Parcelable - 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 val daggerComponent = sessionComponentFactory.create(inputs.matrixClient) 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) } - } ) } @@ -97,13 +90,10 @@ class LoggedInAppScopeFlowNode @AssistedInject constructor( plugins().forEach { it.onOpenBugReport() } } } - val nodeLifecycleCallbacks = plugins() - return createNode(buildContext, nodeLifecycleCallbacks + callback) + return createNode(buildContext, listOf(callback)) } - suspend fun attachSession(): LoggedInFlowNode { - return waitForChildAttached { _ -> true } - } + suspend fun attachSession(): LoggedInFlowNode = waitForChildAttached() @Composable override fun View(modifier: Modifier) { 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 5006218bda..164c2ae2e4 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -227,14 +227,13 @@ class LoggedInFlowNode @AssistedInject constructor( .build() } is NavTarget.Room -> { - val nodeLifecycleCallbacks = plugins() val callback = object : RoomLoadedFlowNode.Callback { override fun onForwardedToSingleRoom(roomId: RoomId) { coroutineScope.launch { attachRoom(roomId) } } } val inputs = RoomFlowNode.Inputs(roomId = navTarget.roomId, initialElement = navTarget.initialElement) - createNode(buildContext, plugins = listOf(inputs, callback) + nodeLifecycleCallbacks) + createNode(buildContext, plugins = listOf(inputs, callback)) } NavTarget.Settings -> { val callback = object : PreferencesEntryPoint.Callback { 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 85032e751f..60095c17d0 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -29,7 +29,6 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node 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.core.state.MutableSavedStateMap import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.pop @@ -196,8 +195,7 @@ class RootFlowNode @AssistedInject constructor( backstack.push(NavTarget.BugReport) } } - val nodeLifecycleCallbacks = plugins() - createNode(buildContext, plugins = listOf(inputs, callback) + nodeLifecycleCallbacks) + createNode(buildContext, plugins = listOf(inputs, callback)) } NavTarget.NotLoggedInFlow -> createNode(buildContext) NavTarget.SplashScreen -> splashNode(buildContext) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/NodeLifecycleCallback.kt b/appnav/src/main/kotlin/io/element/android/appnav/di/RoomComponentFactory.kt similarity index 77% rename from appnav/src/main/kotlin/io/element/android/appnav/NodeLifecycleCallback.kt rename to appnav/src/main/kotlin/io/element/android/appnav/di/RoomComponentFactory.kt index a06564c3aa..d22153beff 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/NodeLifecycleCallback.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/di/RoomComponentFactory.kt @@ -14,8 +14,10 @@ * limitations under the License. */ -package io.element.android.appnav +package io.element.android.appnav.di -import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.matrix.api.room.MatrixRoom -interface NodeLifecycleCallback : Plugin +interface RoomComponentFactory { + fun create(room: MatrixRoom): Any +} diff --git a/appnav/src/main/kotlin/io/element/android/appnav/di/SessionComponentFactory.kt b/appnav/src/main/kotlin/io/element/android/appnav/di/SessionComponentFactory.kt new file mode 100644 index 0000000000..f357662a90 --- /dev/null +++ b/appnav/src/main/kotlin/io/element/android/appnav/di/SessionComponentFactory.kt @@ -0,0 +1,23 @@ +/* + * 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.di + +import io.element.android.libraries.matrix.api.MatrixClient + +interface SessionComponentFactory { + fun create(client: MatrixClient): Any +} diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index f8fa7e629f..c3df0f0ee2 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -36,7 +36,6 @@ import com.bumble.appyx.navmodel.backstack.operation.newRoot import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.appnav.NodeLifecycleCallback import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.libraries.architecture.BackstackNode @@ -103,12 +102,11 @@ class RoomFlowNode @AssistedInject constructor( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Loaded -> { - val nodeLifecycleCallbacks = plugins() val roomFlowNodeCallback = plugins() val awaitRoomState = loadingRoomStateStateFlow.value if (awaitRoomState is LoadingRoomState.Loaded) { val inputs = RoomLoadedFlowNode.Inputs(awaitRoomState.room, initialElement = inputs.initialElement) - createNode(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback + nodeLifecycleCallbacks) + createNode(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback) } else { loadingNode(buildContext, this::navigateUp) } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt index 8230e62119..34c459f979 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt @@ -27,19 +27,19 @@ 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 com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.appnav.NodeLifecycleCallback +import io.element.android.appnav.di.RoomComponentFactory import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint 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.inputs +import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId @@ -60,6 +60,7 @@ class RoomLoadedFlowNode @AssistedInject constructor( private val messagesEntryPoint: MessagesEntryPoint, private val roomDetailsEntryPoint: RoomDetailsEntryPoint, private val appNavigationStateService: AppNavigationStateService, + roomComponentFactory: RoomComponentFactory, roomMembershipObserver: RoomMembershipObserver, ) : BackstackNode( backstack = BackStack( @@ -68,17 +69,12 @@ class RoomLoadedFlowNode @AssistedInject constructor( ), buildContext = buildContext, plugins = plugins, -) { +), DaggerComponentOwner { interface Callback : Plugin { fun onForwardedToSingleRoom(roomId: RoomId) } - interface LifecycleCallback : NodeLifecycleCallback { - fun onFlowCreated(identifier: String, room: MatrixRoom) - fun onFlowReleased(identifier: String, room: MatrixRoom) - } - data class Inputs( val room: MatrixRoom, val initialElement: NavTarget = NavTarget.Messages, @@ -86,18 +82,17 @@ class RoomLoadedFlowNode @AssistedInject constructor( private val inputs: Inputs = inputs() private val callbacks = plugins.filterIsInstance() + override val daggerComponent = roomComponentFactory.create(inputs.room) init { lifecycle.subscribe( onCreate = { - Timber.v("OnCreate") - plugins().forEach { it.onFlowCreated(id, inputs.room) } + Timber.v("OnCreate => ${inputs.room.roomId}") appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId) fetchRoomMembers() }, onDestroy = { Timber.v("OnDestroy") - plugins().forEach { it.onFlowReleased(id, inputs.room) } appNavigationStateService.onLeavingRoom(id) } )