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 7f56ef4d63..fc4e30e749 100644 --- a/app/src/main/kotlin/io/element/android/x/MainNode.kt +++ b/app/src/main/kotlin/io/element/android/x/MainNode.kt @@ -28,7 +28,7 @@ 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.RoomFlowNode +import io.element.android.appnav.room.RoomFlowNode import io.element.android.appnav.RootFlowNode import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.createNode 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 fb9c5162df..369b3054fb 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -42,6 +42,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.appnav.loggedin.LoggedInNode +import io.element.android.appnav.room.AwaitRoomNode +import io.element.android.appnav.room.RoomFlowNode import io.element.android.features.analytics.api.AnalyticsEntryPoint import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.features.invitelist.api.InviteListEntryPoint diff --git a/appnav/src/main/kotlin/io/element/android/appnav/AwaitRoomNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/AwaitRoomNode.kt similarity index 87% rename from appnav/src/main/kotlin/io/element/android/appnav/AwaitRoomNode.kt rename to appnav/src/main/kotlin/io/element/android/appnav/room/AwaitRoomNode.kt index 0da87e046a..bd894a4aeb 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/AwaitRoomNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/AwaitRoomNode.kt @@ -16,7 +16,7 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package io.element.android.appnav +package io.element.android.appnav.room import android.os.Parcelable import androidx.compose.foundation.background @@ -43,10 +43,11 @@ 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.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.appnav.safeRoot import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.createNode @@ -59,21 +60,17 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.placeholderBackground import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.theme.ElementTheme -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.stateIn import kotlinx.parcelize.Parcelize @ContributesNode(SessionScope::class) class AwaitRoomNode @AssistedInject constructor( @Assisted val buildContext: BuildContext, @Assisted plugins: List, - private val matrixClient: MatrixClient, + awaitRoomStateFlowFactory: AwaitRoomStateFlowFactory, ) : BackstackNode( backstack = BackStack( @@ -90,11 +87,7 @@ class AwaitRoomNode @AssistedInject constructor( ) : NodeInputs private val inputs: Inputs = inputs() - private val roomStateFlow = suspend { - matrixClient.getRoom(roomId = inputs.roomId) - } - .asFlow() - .stateIn(lifecycleScope, SharingStarted.Eagerly, null) + private val awaitRoomStateFlow = awaitRoomStateFlowFactory.create(lifecycleScope, inputs.roomId) sealed interface NavTarget : Parcelable { @Parcelize @@ -105,11 +98,10 @@ class AwaitRoomNode @AssistedInject constructor( } init { - roomStateFlow.onEach { room -> - if (room == null) { - backstack.newRoot(NavTarget.Loading) - } else { - backstack.newRoot(NavTarget.Loaded) + awaitRoomStateFlow.onEach { awaitRoomState -> + when (awaitRoomState) { + is AwaitRoomState.Loaded -> backstack.safeRoot(NavTarget.Loaded) + else -> backstack.safeRoot(NavTarget.Loading) } }.launchIn(lifecycleScope) } @@ -119,12 +111,12 @@ class AwaitRoomNode @AssistedInject constructor( NavTarget.Loaded -> { val nodeLifecycleCallbacks = plugins() val roomFlowNodeCallback = plugins() - val room = roomStateFlow.value - if (room == null) { - loadingNode(buildContext, this::navigateUp) - } else { - val inputs = RoomFlowNode.Inputs(room, initialElement = inputs.initialElement) + val awaitRoomState = awaitRoomStateFlow.value + if (awaitRoomState is AwaitRoomState.Loaded) { + val inputs = RoomFlowNode.Inputs(awaitRoomState.room, initialElement = inputs.initialElement) createNode(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback + nodeLifecycleCallbacks) + } else { + loadingNode(buildContext, this::navigateUp) } } NavTarget.Loading -> { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/AwaitRoomState.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/AwaitRoomState.kt new file mode 100644 index 0000000000..a0f0e1cd33 --- /dev/null +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/AwaitRoomState.kt @@ -0,0 +1,53 @@ +/* + * 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.room + +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.MatrixRoom +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +sealed interface AwaitRoomState { + object Loading : AwaitRoomState + object Error : AwaitRoomState + data class Loaded(val room: MatrixRoom) : AwaitRoomState +} + +@SingleIn(SessionScope::class) +class AwaitRoomStateFlowFactory @Inject constructor(private val matrixClient: MatrixClient) { + + fun create(lifecycleScope: CoroutineScope, roomId: RoomId): StateFlow = suspend { + matrixClient.getRoom(roomId = roomId) + } + .asFlow() + .map { room -> + if (room != null) { + AwaitRoomState.Loaded(room) + } else { + AwaitRoomState.Error + } + } + .stateIn(lifecycleScope, SharingStarted.Eagerly, AwaitRoomState.Loading) +} diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt similarity index 98% rename from appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt rename to appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index a0867facf2..fd4b4e5174 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.appnav +package io.element.android.appnav.room import android.os.Parcelable import androidx.compose.runtime.Composable @@ -32,6 +32,7 @@ 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.features.messages.api.MessagesEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.libraries.architecture.BackstackNode diff --git a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt index daff06af16..6f816f9de9 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt @@ -26,6 +26,7 @@ import com.bumble.appyx.navmodel.backstack.activeElement import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.bumble.appyx.testing.unit.common.helper.parentNodeTestHelper import com.google.common.truth.Truth +import io.element.android.appnav.room.RoomFlowNode import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.libraries.architecture.childNode