misc(navigation) : use JoinedRoomLoadedFlowNode as parent of SpaceFlowNode

This commit is contained in:
ganfra 2025-10-24 12:49:05 +02:00
parent cec4e105ec
commit d87c484dd8
6 changed files with 52 additions and 36 deletions

View file

@ -62,7 +62,6 @@ import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
import io.element.android.features.securebackup.api.SecureBackupEntryPoint import io.element.android.features.securebackup.api.SecureBackupEntryPoint
import io.element.android.features.share.api.ShareEntryPoint import io.element.android.features.share.api.ShareEntryPoint
import io.element.android.features.space.api.SpaceEntryPoint
import io.element.android.features.startchat.api.StartChatEntryPoint import io.element.android.features.startchat.api.StartChatEntryPoint
import io.element.android.features.userprofile.api.UserProfileEntryPoint import io.element.android.features.userprofile.api.UserProfileEntryPoint
import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint
@ -256,7 +255,7 @@ class LoggedInFlowNode(
val serverNames: List<String> = emptyList(), val serverNames: List<String> = emptyList(),
val trigger: JoinedRoom.Trigger? = null, val trigger: JoinedRoom.Trigger? = null,
val roomDescription: RoomDescription? = null, val roomDescription: RoomDescription? = null,
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(), val initialElement: RoomNavigationTarget = RoomNavigationTarget.Root(),
val targetId: UUID = UUID.randomUUID(), val targetId: UUID = UUID.randomUUID(),
) : NavTarget ) : NavTarget
@ -358,7 +357,7 @@ class LoggedInFlowNode(
roomIdOrAlias = data.roomIdOrAlias, roomIdOrAlias = data.roomIdOrAlias,
serverNames = data.viaParameters, serverNames = data.viaParameters,
trigger = JoinedRoom.Trigger.Timeline, trigger = JoinedRoom.Trigger.Timeline,
initialElement = RoomNavigationTarget.Messages(data.eventId), initialElement = RoomNavigationTarget.Root(data.eventId),
) )
if (pushToBackstack) { if (pushToBackstack) {
backstack.push(target) backstack.push(target)
@ -377,11 +376,6 @@ class LoggedInFlowNode(
backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings)) backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings))
} }
} }
val spaceCallback = object : SpaceEntryPoint.Callback {
override fun onOpenRoom(roomId: RoomId, viaParameters: List<String>) {
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), serverNames = viaParameters))
}
}
val inputs = RoomFlowNode.Inputs( val inputs = RoomFlowNode.Inputs(
roomIdOrAlias = navTarget.roomIdOrAlias, roomIdOrAlias = navTarget.roomIdOrAlias,
roomDescription = Optional.ofNullable(navTarget.roomDescription), roomDescription = Optional.ofNullable(navTarget.roomDescription),
@ -389,7 +383,7 @@ class LoggedInFlowNode(
trigger = Optional.ofNullable(navTarget.trigger), trigger = Optional.ofNullable(navTarget.trigger),
initialElement = navTarget.initialElement initialElement = navTarget.initialElement
) )
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, joinedRoomCallback, spaceCallback)) createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, joinedRoomCallback))
} }
is NavTarget.UserProfile -> { is NavTarget.UserProfile -> {
val callback = object : UserProfileEntryPoint.Callback { val callback = object : UserProfileEntryPoint.Callback {
@ -421,7 +415,7 @@ class LoggedInFlowNode(
} }
override fun navigateTo(roomId: RoomId, eventId: EventId) { override fun navigateTo(roomId: RoomId, eventId: EventId) {
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.Messages(eventId))) backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.Root(eventId)))
} }
} }
val inputs = PreferencesEntryPoint.Params(navTarget.initialElement) val inputs = PreferencesEntryPoint.Params(navTarget.initialElement)
@ -516,9 +510,7 @@ class LoggedInFlowNode(
roomIdOrAlias = roomIdOrAlias, roomIdOrAlias = roomIdOrAlias,
serverNames = serverNames, serverNames = serverNames,
trigger = trigger, trigger = trigger,
initialElement = RoomNavigationTarget.Messages( initialElement = RoomNavigationTarget.Root(eventId = eventId)
focusedEventId = eventId
)
) )
backstack.accept(AttachRoomOperation(roomNavTarget, clearBackstack)) backstack.accept(AttachRoomOperation(roomNavTarget, clearBackstack))
} }

View file

@ -9,7 +9,6 @@ package io.element.android.appnav.room
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
@ -37,7 +36,6 @@ import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.withPreviousValue import io.element.android.libraries.core.coroutine.withPreviousValue
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
@ -49,11 +47,11 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.ui.room.LoadingRoomState import io.element.android.libraries.matrix.ui.room.LoadingRoomState
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -130,7 +128,6 @@ class RoomFlowNode(
private fun subscribeToRoomInfoFlow(roomId: RoomId, serverNames: List<String>) { private fun subscribeToRoomInfoFlow(roomId: RoomId, serverNames: List<String>) {
val roomInfoFlow = client.getRoomInfoFlow(roomId) val roomInfoFlow = client.getRoomInfoFlow(roomId)
val isSpaceFlow = roomInfoFlow.map { it.getOrNull()?.isSpace.orFalse() }.distinctUntilChanged()
// This observes the local membership changes for the room // This observes the local membership changes for the room
val membershipUpdateFlow = membershipObserver.updates val membershipUpdateFlow = membershipObserver.updates
@ -143,14 +140,10 @@ class RoomFlowNode(
.map { it.getOrNull()?.currentUserMembership } .map { it.getOrNull()?.currentUserMembership }
.distinctUntilChanged() .distinctUntilChanged()
.withPreviousValue() .withPreviousValue()
combine(currentMembershipFlow, isSpaceFlow) { (previousMembership, membership), isSpace -> currentMembershipFlow.onEach { (previousMembership, membership) ->
Timber.d("Room membership: $membership") Timber.d("Room membership: $membership")
if (membership == CurrentUserMembership.JOINED) { if (membership == CurrentUserMembership.JOINED) {
if (isSpace) { backstack.newRoot(NavTarget.JoinedRoom(roomId))
backstack.newRoot(NavTarget.JoinedSpace(spaceId = roomId))
} else {
backstack.newRoot(NavTarget.JoinedRoom(roomId))
}
} else { } else {
val leavingFromCurrentDevice = val leavingFromCurrentDevice =
membership == CurrentUserMembership.LEFT && membership == CurrentUserMembership.LEFT &&

View file

@ -13,7 +13,7 @@ import kotlinx.parcelize.Parcelize
sealed interface RoomNavigationTarget : Parcelable { sealed interface RoomNavigationTarget : Parcelable {
@Parcelize @Parcelize
data class Messages(val focusedEventId: EventId? = null) : RoomNavigationTarget data class Root(val eventId: EventId? = null) : RoomNavigationTarget
@Parcelize @Parcelize
data object Details : RoomNavigationTarget data object Details : RoomNavigationTarget

View file

@ -22,8 +22,11 @@ import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode import io.element.android.annotations.ContributesNode
import io.element.android.appnav.di.RoomGraphFactory import io.element.android.appnav.di.RoomGraphFactory
import io.element.android.appnav.room.RoomNavigationTarget import io.element.android.appnav.room.RoomNavigationTarget
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode.Inputs
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode.NavTarget
import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
import io.element.android.features.space.api.SpaceEntryPoint
import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.NodeInputs
@ -51,6 +54,7 @@ class JoinedRoomLoadedFlowNode(
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
private val messagesEntryPoint: MessagesEntryPoint, private val messagesEntryPoint: MessagesEntryPoint,
private val roomDetailsEntryPoint: RoomDetailsEntryPoint, private val roomDetailsEntryPoint: RoomDetailsEntryPoint,
private val spaceEntryPoint: SpaceEntryPoint,
private val appNavigationStateService: AppNavigationStateService, private val appNavigationStateService: AppNavigationStateService,
@SessionCoroutineScope @SessionCoroutineScope
private val sessionCoroutineScope: CoroutineScope, private val sessionCoroutineScope: CoroutineScope,
@ -59,11 +63,7 @@ class JoinedRoomLoadedFlowNode(
roomGraphFactory: RoomGraphFactory, roomGraphFactory: RoomGraphFactory,
) : BaseFlowNode<JoinedRoomLoadedFlowNode.NavTarget>( ) : BaseFlowNode<JoinedRoomLoadedFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = when (val input = plugins.filterIsInstance<Inputs>().first().initialElement) { initialElement = initialElement(plugins),
is RoomNavigationTarget.Messages -> NavTarget.Messages(input.focusedEventId)
RoomNavigationTarget.Details -> NavTarget.RoomDetails
RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings
},
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
), ),
buildContext = buildContext, buildContext = buildContext,
@ -154,9 +154,25 @@ class JoinedRoomLoadedFlowNode(
NavTarget.RoomNotificationSettings -> { NavTarget.RoomNotificationSettings -> {
createRoomDetailsNode(buildContext, RoomDetailsEntryPoint.InitialTarget.RoomNotificationSettings) createRoomDetailsNode(buildContext, RoomDetailsEntryPoint.InitialTarget.RoomNotificationSettings)
} }
NavTarget.Space -> {
createSpaceNode(buildContext)
}
} }
} }
private fun createSpaceNode(buildContext: BuildContext): Node {
val callback = object : SpaceEntryPoint.Callback {
override fun onOpenRoom(roomId: RoomId, viaParameters: List<String>) {
callbacks.forEach { it.onOpenRoom(roomId, viaParameters) }
}
}
return spaceEntryPoint.nodeBuilder(this, buildContext)
.inputs(SpaceEntryPoint.Inputs(roomId = inputs.room.roomId))
.callback(callback)
.build()
}
private fun createMessagesNode( private fun createMessagesNode(
buildContext: BuildContext, buildContext: BuildContext,
navTarget: NavTarget.Messages, navTarget: NavTarget.Messages,
@ -188,6 +204,9 @@ class JoinedRoomLoadedFlowNode(
} }
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize
data object Space : NavTarget
@Parcelize @Parcelize
data class Messages(val focusedEventId: EventId? = null) : NavTarget data class Messages(val focusedEventId: EventId? = null) : NavTarget
@ -206,3 +225,18 @@ class JoinedRoomLoadedFlowNode(
BackstackView() BackstackView()
} }
} }
private fun initialElement(plugins: List<Plugin>): NavTarget {
val input = plugins.filterIsInstance<Inputs>().single()
return when (input.initialElement) {
is RoomNavigationTarget.Root -> {
if (input.room.roomInfoFlow.value.isSpace) {
NavTarget.Space
} else {
NavTarget.Messages(input.initialElement.eventId)
}
}
RoomNavigationTarget.Details -> NavTarget.RoomDetails
RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings
}
}

View file

@ -19,8 +19,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@ -76,7 +74,6 @@ private fun LoadingRoomTopBar(
BackButton(onClick = onBackClick) BackButton(onClick = onBackClick)
}, },
title = { title = {
IconTitlePlaceholdersRowMolecule(iconSize = AvatarSize.TimelineRoom.dp)
}, },
) )
} }

View file

@ -120,7 +120,7 @@ class JoinedRoomLoadedFlowNodeTest {
// GIVEN // GIVEN
val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {})) val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {}))
val fakeMessagesEntryPoint = FakeMessagesEntryPoint() val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages()) val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root())
val roomFlowNode = createJoinedRoomLoadedFlowNode( val roomFlowNode = createJoinedRoomLoadedFlowNode(
plugins = listOf(inputs), plugins = listOf(inputs),
messagesEntryPoint = fakeMessagesEntryPoint, messagesEntryPoint = fakeMessagesEntryPoint,
@ -141,7 +141,7 @@ class JoinedRoomLoadedFlowNodeTest {
val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {})) val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {}))
val fakeMessagesEntryPoint = FakeMessagesEntryPoint() val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint() val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages()) val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root())
val roomFlowNode = createJoinedRoomLoadedFlowNode( val roomFlowNode = createJoinedRoomLoadedFlowNode(
plugins = listOf(inputs), plugins = listOf(inputs),
messagesEntryPoint = fakeMessagesEntryPoint, messagesEntryPoint = fakeMessagesEntryPoint,
@ -162,7 +162,7 @@ class JoinedRoomLoadedFlowNodeTest {
val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {})) val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {}))
val fakeMessagesEntryPoint = FakeMessagesEntryPoint() val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint() val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages()) val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root())
val activeRoomsHolder = ActiveRoomsHolder() val activeRoomsHolder = ActiveRoomsHolder()
val roomFlowNode = createJoinedRoomLoadedFlowNode( val roomFlowNode = createJoinedRoomLoadedFlowNode(
plugins = listOf(inputs), plugins = listOf(inputs),
@ -185,7 +185,7 @@ class JoinedRoomLoadedFlowNodeTest {
val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {})) val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {}))
val fakeMessagesEntryPoint = FakeMessagesEntryPoint() val fakeMessagesEntryPoint = FakeMessagesEntryPoint()
val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint() val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint()
val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages()) val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root())
val activeRoomsHolder = ActiveRoomsHolder().apply { val activeRoomsHolder = ActiveRoomsHolder().apply {
addRoom(room) addRoom(room)
} }