From 6b9afd9e6f8d92af14cf6caf0b4d6c1f5b71a421 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 8 Sep 2025 21:57:25 +0200 Subject: [PATCH] feature (space) : iterate on space list (and space screen) --- .../android/appnav/room/RoomFlowNode.kt | 26 ++++++------ .../android/features/home/impl/HomeView.kt | 2 +- .../home/impl/spaces/HomeSpacesView.kt | 12 +++--- .../features/space/api/SpaceEntryPoint.kt | 7 ++++ .../android/features/space/impl/SpaceNode.kt | 3 +- .../features/space/impl/SpacePresenter.kt | 22 +++++++--- .../android/features/space/impl/SpaceView.kt | 1 - .../matrix/api/spaces/SpaceService.kt | 2 +- .../matrix/impl/spaces/RustSpaceRoomList.kt | 40 ++++++++++++------- .../matrix/impl/spaces/RustSpaceService.kt | 5 +-- .../impl/spaces/SpaceRoomListExtensions.kt | 3 ++ 11 files changed, 79 insertions(+), 44 deletions(-) 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 12d9df78cf..c6381d4c7d 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 @@ -30,7 +30,9 @@ import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode import io.element.android.appnav.room.joined.LoadingRoomNodeView import io.element.android.features.joinroom.api.JoinRoomEntryPoint import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint +import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint.Params import io.element.android.features.roomdirectory.api.RoomDescription +import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs @@ -43,6 +45,7 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomIdOrAlias +import io.element.android.libraries.matrix.api.core.SpaceId import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias @@ -72,6 +75,7 @@ class RoomFlowNode( private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint, private val syncService: SyncService, private val membershipObserver: RoomMembershipObserver, + private val spaceEntryPoint: SpaceEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Loading, @@ -106,6 +110,9 @@ class RoomFlowNode( @Parcelize data class JoinedRoom(val roomId: RoomId) : NavTarget + + @Parcelize + data class Space(val spaceId: RoomId) : NavTarget } override fun onBuilt() { @@ -146,17 +153,7 @@ class RoomFlowNode( when (membership) { CurrentUserMembership.JOINED -> { if (isSpace) { - // It should not happen, but probably due to an issue in the sliding sync, - // we can have a space here in case the space has just been joined. - // So navigate to the JoinRoom target for now, which will - // handle the space not supported screen - backstack.newRoot( - NavTarget.JoinRoom( - roomId = roomId, - serverNames = serverNames, - trigger = inputs.trigger.getOrNull() ?: JoinedRoom.Trigger.Invite, - ) - ) + backstack.newRoot(NavTarget.Space(spaceId = roomId)) } else { backstack.newRoot(NavTarget.JoinedRoom(roomId)) } @@ -194,7 +191,7 @@ class RoomFlowNode( ) } } - val params = RoomAliasResolverEntryPoint.Params(navTarget.roomAlias) + val params = Params(navTarget.roomAlias) roomAliasResolverEntryPoint.nodeBuilder(this, buildContext) .callback(callback) .params(params) @@ -218,6 +215,11 @@ class RoomFlowNode( ) createNode(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback) } + is NavTarget.Space -> { + spaceEntryPoint.nodeBuilder(this, buildContext) + .params(SpaceEntryPoint.Params.Id(navTarget.spaceId)) + .build() + } } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt index 6d531504ab..37727712fb 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt @@ -269,7 +269,7 @@ private fun HomeScaffold( .hazeSource(state = hazeState), state = state.homeSpacesState, onSpaceClick = { spaceId -> - // TODO + onRoomClick(spaceId) } ) } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt index cb93b297be..51dac2e0a3 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt @@ -52,15 +52,15 @@ fun HomeSpacesView( ) } } - state.spaceRooms.forEach { - item(it.roomId) { - val isInvitation = it.state == CurrentUserMembership.INVITED + state.spaceRooms.forEach { spaceRoom -> + item(spaceRoom.roomId) { + val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED SpaceRoomItemView( - spaceRoom = it, - showUnreadIndicator = isInvitation && it.roomId !in state.seenSpaceInvites, + spaceRoom = spaceRoom, + showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, hideAvatars = isInvitation && state.hideInvitesAvatar, onClick = { - onSpaceClick(it.roomId) + onSpaceClick(spaceRoom.roomId) }, onLongClick = { diff --git a/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt b/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt index 813450d7cb..6a7002af2f 100644 --- a/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt +++ b/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt @@ -30,6 +30,13 @@ interface SpaceEntryPoint : FeatureEntryPoint { sealed interface Params : Plugin { data class Id(val roomId: RoomId) : Params data class Full(val spaceRoom: SpaceRoom) : Params + + fun roomId(): RoomId { + return when (this) { + is Id -> roomId + is Full -> spaceRoom.roomId + } + } } interface Callback : Plugin { diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceNode.kt index 950cfe17d7..870b9b139c 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceNode.kt @@ -22,10 +22,11 @@ import io.element.android.libraries.di.SessionScope class SpaceNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val presenter: SpacePresenter, + private val presenterFactory: SpacePresenter.Factory, ) : Node(buildContext, plugins = plugins) { val params = plugins.filterIsInstance().single() + private val presenter = presenterFactory.create(params) @Composable override fun View(modifier: Modifier) { diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpacePresenter.kt index b4a764b409..7e1c12ca87 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpacePresenter.kt @@ -11,22 +11,33 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.Inject import io.element.android.features.invite.api.SeenInvitesStore +import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.matrix.api.MatrixClient -import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.flow.map -import javax.inject.Inject -class SpacePresenter @Inject constructor( +@Inject +class SpacePresenter( + @Assisted private val params: SpaceEntryPoint.Params, private val client: MatrixClient, private val seenInvitesStore: SeenInvitesStore, ) : Presenter { + @AssistedFactory + interface Factory { + fun create(params: SpaceEntryPoint.Params): SpacePresenter + } + + private val spaceRoomList = client.spaceService.spaceRoomList(params.roomId()) + @Composable override fun present(): SpaceState { val hideInvitesAvatar by remember { @@ -35,18 +46,19 @@ class SpacePresenter @Inject constructor( .mediaPreviewConfigFlow .mapState { config -> config.hideInviteAvatar } }.collectAsState() - val spaceRooms by client.spaceService.spaceRoomsFlow.collectAsState(emptyList()) val seenSpaceInvites by remember { seenInvitesStore.seenRoomIds().map { it.toPersistentSet() } }.collectAsState(persistentSetOf()) + val children by spaceRoomList.spaceRoomsFlow.collectAsState(emptyList()) + fun handleEvents(event: SpaceEvents) { //when (event) { } } return SpaceState( parentSpace = null, - children = spaceRooms.toPersistentList(), + children = children.toPersistentList(), seenSpaceInvites = seenSpaceInvites, hideInvitesAvatar = hideInvitesAvatar, eventSink = ::handleEvents, diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceView.kt index d1774d98bf..d7d03974da 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceView.kt @@ -132,7 +132,6 @@ private fun SpaceViewTopBar( }, actions = { }, - windowInsets = WindowInsets(0.dp) ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt index 22f78bf780..b4572ad0bb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt @@ -14,5 +14,5 @@ interface SpaceService { val spaceRoomsFlow: SharedFlow> suspend fun joinedSpaces(): Result> - suspend fun spaceRoomList(id: RoomId): SpaceRoomList + fun spaceRoomList(id: RoomId): SpaceRoomList } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomList.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomList.kt index efe405714e..9691f63c12 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomList.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomList.kt @@ -10,40 +10,52 @@ package io.element.android.libraries.matrix.impl.spaces import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoomList +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import uniffi.matrix_sdk_ui.SpaceRoomListPaginationState import org.matrix.rustcomponents.sdk.SpaceRoomList as InnerSpaceRoomList class RustSpaceRoomList( - private val inner: InnerSpaceRoomList, + private val innerProvider: suspend () -> InnerSpaceRoomList, sessionCoroutineScope: CoroutineScope, spaceRoomMapper: SpaceRoomMapper, ) : SpaceRoomList { + + private val inner = CompletableDeferred() override val spaceRoomsFlow = MutableSharedFlow>(replay = 1, extraBufferCapacity = Int.MAX_VALUE) - override val paginationStatusFlow = MutableStateFlow(inner.paginationState().into()) + override val paginationStatusFlow: MutableStateFlow = + MutableStateFlow(SpaceRoomList.PaginationStatus.Idle(hasMoreToLoad = false)) private val spaceListUpdateProcessor = SpaceListUpdateProcessor(spaceRoomsFlow, spaceRoomMapper) init { - inner.paginationStateFlow() - .onEach { paginationStatus -> - paginationStatusFlow.emit(paginationStatus.into()) - } - .launchIn(sessionCoroutineScope) + sessionCoroutineScope.launch { + inner.complete(innerProvider()) + } + sessionCoroutineScope.launch { + inner.await().paginationStateFlow() + .onEach { paginationStatus -> + paginationStatusFlow.emit(paginationStatus.into()) + } + .collect() + } - inner.spaceListUpdateFlow() - .onEach { updates -> - spaceListUpdateProcessor.postUpdates(updates) - } - .launchIn(sessionCoroutineScope) + sessionCoroutineScope.launch { + inner.await().spaceListUpdateFlow() + .onEach { updates -> + spaceListUpdateProcessor.postUpdates(updates) + } + .collect() + } } override suspend fun paginate(): Result { return runCatchingExceptions { - inner.paginate() + inner.await().paginate() } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt index da4f62ad0c..3e8fd77843 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt @@ -51,10 +51,9 @@ class RustSpaceService( } } - override suspend fun spaceRoomList(id: RoomId): SpaceRoomList { - val innerSpaceRoomList = innerSpaceService.spaceRoomList(id.value) + override fun spaceRoomList(id: RoomId): SpaceRoomList { return RustSpaceRoomList( - inner = innerSpaceRoomList, + innerProvider = { innerSpaceService.spaceRoomList(id.value) }, sessionCoroutineScope = sessionCoroutineScope, spaceRoomMapper = spaceRoomMapper ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomListExtensions.kt index 4976f37117..3d1a002e4d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomListExtensions.kt @@ -28,6 +28,9 @@ internal fun SpaceRoomListInterface.paginationStateFlow(): Flow