Skip to content

Commit 3af4405

Browse files
bmartyganfraElementBot
authored
Space list (#5320)
* feature(spaces) : introduce SpaceRoomList matrix api * feature (space) : extract SpaceRoomItemView * feature(spaces) : start introducing SpaceScreen * feature (space) : iterate on space list (and space screen) * feature (space) : add space cache and navigation to sub space/room * feature (space) : display top bar title * Code cleanup, remove dead code and fix compilation issue * More compilation fixes. * Update screenshots * Fix test compilation issues. * Introduce MatrixClient.rememberHideInvitesAvatar() extension to reduce code duplication. * Add test on SpacePresenter * Add test on SpaceRoomCache and fix implementation * Iterate on SpaceRoomCache thanks to SpaceRoomCacheTest * Add UT on SpaceListUpdateProcessor * Fix quality issue. * Add tests on RustSpaceRoomList --------- Co-authored-by: ganfra <[email protected]> Co-authored-by: ElementBot <[email protected]>
2 parents 0ebabe3 + 3b41d4a commit 3af4405

File tree

65 files changed

+1840
-286
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1840
-286
lines changed

appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import io.element.android.features.roomdirectory.api.RoomDescription
6060
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
6161
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
6262
import io.element.android.features.share.api.ShareEntryPoint
63+
import io.element.android.features.space.api.SpaceEntryPoint
6364
import io.element.android.features.startchat.api.StartChatEntryPoint
6465
import io.element.android.features.userprofile.api.UserProfileEntryPoint
6566
import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint
@@ -334,7 +335,7 @@ class LoggedInFlowNode(
334335
.build()
335336
}
336337
is NavTarget.Room -> {
337-
val callback = object : JoinedRoomLoadedFlowNode.Callback {
338+
val joinedRoomCallback = object : JoinedRoomLoadedFlowNode.Callback {
338339
override fun onOpenRoom(roomId: RoomId, serverNames: List<String>) {
339340
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), serverNames))
340341
}
@@ -373,14 +374,19 @@ class LoggedInFlowNode(
373374
backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings))
374375
}
375376
}
377+
val spaceCallback = object : SpaceEntryPoint.Callback {
378+
override fun onOpenRoom(roomId: RoomId) {
379+
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
380+
}
381+
}
376382
val inputs = RoomFlowNode.Inputs(
377383
roomIdOrAlias = navTarget.roomIdOrAlias,
378384
roomDescription = Optional.ofNullable(navTarget.roomDescription),
379385
serverNames = navTarget.serverNames,
380386
trigger = Optional.ofNullable(navTarget.trigger),
381387
initialElement = navTarget.initialElement
382388
)
383-
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, callback))
389+
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, joinedRoomCallback, spaceCallback))
384390
}
385391
is NavTarget.UserProfile -> {
386392
val callback = object : UserProfileEntryPoint.Callback {

appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
3030
import io.element.android.appnav.room.joined.LoadingRoomNodeView
3131
import io.element.android.features.joinroom.api.JoinRoomEntryPoint
3232
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint
33+
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint.Params
3334
import io.element.android.features.roomdirectory.api.RoomDescription
35+
import io.element.android.features.space.api.SpaceEntryPoint
3436
import io.element.android.libraries.architecture.BackstackView
3537
import io.element.android.libraries.architecture.BaseFlowNode
3638
import io.element.android.libraries.architecture.NodeInputs
@@ -72,6 +74,7 @@ class RoomFlowNode(
7274
private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint,
7375
private val syncService: SyncService,
7476
private val membershipObserver: RoomMembershipObserver,
77+
private val spaceEntryPoint: SpaceEntryPoint,
7578
) : BaseFlowNode<RoomFlowNode.NavTarget>(
7679
backstack = BackStack(
7780
initialElement = NavTarget.Loading,
@@ -106,6 +109,9 @@ class RoomFlowNode(
106109

107110
@Parcelize
108111
data class JoinedRoom(val roomId: RoomId) : NavTarget
112+
113+
@Parcelize
114+
data class Space(val spaceId: RoomId) : NavTarget
109115
}
110116

111117
override fun onBuilt() {
@@ -146,17 +152,7 @@ class RoomFlowNode(
146152
when (membership) {
147153
CurrentUserMembership.JOINED -> {
148154
if (isSpace) {
149-
// It should not happen, but probably due to an issue in the sliding sync,
150-
// we can have a space here in case the space has just been joined.
151-
// So navigate to the JoinRoom target for now, which will
152-
// handle the space not supported screen
153-
backstack.newRoot(
154-
NavTarget.JoinRoom(
155-
roomId = roomId,
156-
serverNames = serverNames,
157-
trigger = inputs.trigger.getOrNull() ?: JoinedRoom.Trigger.Invite,
158-
)
159-
)
155+
backstack.newRoot(NavTarget.Space(spaceId = roomId))
160156
} else {
161157
backstack.newRoot(NavTarget.JoinedRoom(roomId))
162158
}
@@ -194,7 +190,7 @@ class RoomFlowNode(
194190
)
195191
}
196192
}
197-
val params = RoomAliasResolverEntryPoint.Params(navTarget.roomAlias)
193+
val params = Params(navTarget.roomAlias)
198194
roomAliasResolverEntryPoint.nodeBuilder(this, buildContext)
199195
.callback(callback)
200196
.params(params)
@@ -218,6 +214,13 @@ class RoomFlowNode(
218214
)
219215
createNode<JoinedRoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
220216
}
217+
is NavTarget.Space -> {
218+
val spaceCallback = plugins<SpaceEntryPoint.Callback>().single()
219+
spaceEntryPoint.nodeBuilder(this, buildContext)
220+
.inputs(SpaceEntryPoint.Inputs(roomId = navTarget.spaceId))
221+
.callback(spaceCallback)
222+
.build()
223+
}
221224
}
222225
}
223226

features/home/impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ dependencies {
5555
implementation(libs.haze.materials)
5656
implementation(projects.features.reportroom.api)
5757
implementation(projects.features.changeroommemberroles.api)
58+
implementation(projects.libraries.previewutils)
5859
api(projects.features.home.api)
5960

6061
testImplementation(libs.androidx.compose.ui.test.junit)

features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ private fun HomeScaffold(
269269
.hazeSource(state = hazeState),
270270
state = state.homeSpacesState,
271271
onSpaceClick = { spaceId ->
272-
// TODO
272+
onRoomClick(spaceId)
273273
}
274274
)
275275
}

features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenter.kt

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ import io.element.android.features.leaveroom.api.LeaveRoomEvent
3636
import io.element.android.features.leaveroom.api.LeaveRoomState
3737
import io.element.android.libraries.architecture.AsyncData
3838
import io.element.android.libraries.architecture.Presenter
39-
import io.element.android.libraries.core.coroutine.mapState
4039
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
4140
import io.element.android.libraries.matrix.api.MatrixClient
4241
import io.element.android.libraries.matrix.api.core.RoomId
4342
import io.element.android.libraries.matrix.api.encryption.EncryptionService
4443
import io.element.android.libraries.matrix.api.encryption.RecoveryState
4544
import io.element.android.libraries.matrix.api.roomlist.RoomList
4645
import io.element.android.libraries.matrix.api.timeline.ReceiptType
46+
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
4747
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
4848
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
4949
import io.element.android.libraries.push.api.battery.BatteryOptimizationState
@@ -101,12 +101,7 @@ class RoomListPresenter(
101101
var securityBannerDismissed by rememberSaveable { mutableStateOf(false) }
102102

103103
// Avatar indicator
104-
val hideInvitesAvatar by remember {
105-
client
106-
.mediaPreviewService()
107-
.mediaPreviewConfigFlow
108-
.mapState { config -> config.hideInviteAvatar }
109-
}.collectAsState()
104+
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
110105

111106
val contextMenu = remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) }
112107
val declineInviteMenu = remember { mutableStateOf<RoomListState.DeclineInviteMenu>(RoomListState.DeclineInviteMenu.Hidden) }

features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ import androidx.compose.runtime.getValue
1313
import androidx.compose.runtime.remember
1414
import dev.zacsweers.metro.Inject
1515
import io.element.android.features.invite.api.SeenInvitesStore
16-
import io.element.android.features.invite.api.seenSpaceIds
1716
import io.element.android.libraries.architecture.Presenter
18-
import io.element.android.libraries.core.coroutine.mapState
1917
import io.element.android.libraries.matrix.api.MatrixClient
18+
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
2019
import kotlinx.collections.immutable.persistentSetOf
2120
import kotlinx.collections.immutable.toPersistentSet
2221
import kotlinx.coroutines.flow.map
@@ -28,15 +27,10 @@ class HomeSpacesPresenter(
2827
) : Presenter<HomeSpacesState> {
2928
@Composable
3029
override fun present(): HomeSpacesState {
31-
val hideInvitesAvatar by remember {
32-
client
33-
.mediaPreviewService()
34-
.mediaPreviewConfigFlow
35-
.mapState { config -> config.hideInviteAvatar }
36-
}.collectAsState()
37-
val spaceRooms by client.spaceService.spaceRooms.collectAsState(emptyList())
30+
val hideInvitesAvatar by client.rememberHideInvitesAvatar()
31+
val spaceRooms by client.spaceService.spaceRoomsFlow.collectAsState(emptyList())
3832
val seenSpaceInvites by remember {
39-
seenInvitesStore.seenSpaceIds().map { it.toPersistentSet() }
33+
seenInvitesStore.seenRoomIds().map { it.toPersistentSet() }
4034
}.collectAsState(persistentSetOf())
4135

4236
fun handleEvents(event: HomeSpacesEvents) {

features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77

88
package io.element.android.features.home.impl.spaces
99

10-
import io.element.android.libraries.matrix.api.core.SpaceId
10+
import io.element.android.libraries.matrix.api.core.RoomId
1111
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
1212
import kotlinx.collections.immutable.ImmutableSet
1313

1414
data class HomeSpacesState(
1515
val space: CurrentSpace,
1616
val spaceRooms: List<SpaceRoom>,
17-
val seenSpaceInvites: ImmutableSet<SpaceId>,
17+
val seenSpaceInvites: ImmutableSet<RoomId>,
1818
val hideInvitesAvatar: Boolean,
1919
val eventSink: (HomeSpacesEvents) -> Unit,
2020
)

features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStateProvider.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
package io.element.android.features.home.impl.spaces
99

1010
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
11-
import io.element.android.libraries.matrix.api.core.SpaceId
11+
import io.element.android.libraries.matrix.api.core.RoomId
1212
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
13+
import io.element.android.libraries.previewutils.room.aSpaceRoom
1314
import kotlinx.collections.immutable.toImmutableSet
1415

1516
open class HomeSpacesStateProvider : PreviewParameterProvider<HomeSpacesState> {
@@ -18,12 +19,12 @@ open class HomeSpacesStateProvider : PreviewParameterProvider<HomeSpacesState> {
1819
aHomeSpacesState(
1920
spaceRooms = SpaceRoomProvider().values.toList(),
2021
seenSpaceInvites = setOf(
21-
SpaceId("!spaceId3:example.com"),
22+
RoomId("!spaceId3:example.com"),
2223
),
2324
),
2425
aHomeSpacesState(
2526
space = CurrentSpace.Space(
26-
spaceRoom = aSpaceRooms(spaceId = SpaceId("!mySpace:example.com"))
27+
spaceRoom = aSpaceRoom(roomId = RoomId("!mySpace:example.com"))
2728
),
2829
spaceRooms = aListOfSpaceRooms(),
2930
),
@@ -33,7 +34,7 @@ open class HomeSpacesStateProvider : PreviewParameterProvider<HomeSpacesState> {
3334
internal fun aHomeSpacesState(
3435
space: CurrentSpace = CurrentSpace.Root,
3536
spaceRooms: List<SpaceRoom> = aListOfSpaceRooms(),
36-
seenSpaceInvites: Set<SpaceId> = emptySet(),
37+
seenSpaceInvites: Set<RoomId> = emptySet(),
3738
hideInvitesAvatar: Boolean = false,
3839
eventSink: (HomeSpacesEvents) -> Unit = {},
3940
) = HomeSpacesState(
@@ -46,8 +47,8 @@ internal fun aHomeSpacesState(
4647

4748
fun aListOfSpaceRooms(): List<SpaceRoom> {
4849
return listOf(
49-
aSpaceRooms(spaceId = SpaceId("!spaceId0:example.com")),
50-
aSpaceRooms(spaceId = SpaceId("!spaceId1:example.com")),
51-
aSpaceRooms(spaceId = SpaceId("!spaceId2:example.com")),
50+
aSpaceRoom(roomId = RoomId("!spaceId0:example.com")),
51+
aSpaceRoom(roomId = RoomId("!spaceId1:example.com")),
52+
aSpaceRoom(roomId = RoomId("!spaceId2:example.com")),
5253
)
5354
}

features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,18 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
1414
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
1515
import io.element.android.libraries.designsystem.preview.ElementPreview
1616
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
17-
import io.element.android.libraries.matrix.api.core.SpaceId
17+
import io.element.android.libraries.matrix.api.core.RoomId
1818
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
1919
import io.element.android.libraries.matrix.ui.components.SpaceHeaderRootView
2020
import io.element.android.libraries.matrix.ui.components.SpaceHeaderView
21+
import io.element.android.libraries.matrix.ui.components.SpaceRoomItemView
2122
import io.element.android.libraries.matrix.ui.model.getAvatarData
2223
import kotlinx.collections.immutable.toImmutableList
2324

2425
@Composable
2526
fun HomeSpacesView(
2627
state: HomeSpacesState,
27-
onSpaceClick: (SpaceId) -> Unit,
28+
onSpaceClick: (RoomId) -> Unit,
2829
modifier: Modifier = Modifier,
2930
) {
3031
LazyColumn(modifier) {
@@ -51,15 +52,18 @@ fun HomeSpacesView(
5152
)
5253
}
5354
}
54-
state.spaceRooms.forEach {
55-
item(it.spaceId) {
56-
val isInvitation = it.state == CurrentUserMembership.INVITED
57-
HomeSpaceItemView(
58-
spaceRoom = it,
59-
showUnreadIndicator = isInvitation && it.spaceId !in state.seenSpaceInvites,
55+
state.spaceRooms.forEach { spaceRoom ->
56+
item(spaceRoom.roomId) {
57+
val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED
58+
SpaceRoomItemView(
59+
spaceRoom = spaceRoom,
60+
showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites,
6061
hideAvatars = isInvitation && state.hideInvitesAvatar,
6162
onClick = {
62-
onSpaceClick(it.spaceId)
63+
onSpaceClick(spaceRoom.roomId)
64+
},
65+
onLongClick = {
66+
// TODO
6367
}
6468
)
6569
}

features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/SpaceRoomProvider.kt

Lines changed: 11 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,77 +8,44 @@
88
package io.element.android.features.home.impl.spaces
99

1010
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
11-
import io.element.android.libraries.matrix.api.core.RoomAlias
12-
import io.element.android.libraries.matrix.api.core.SpaceId
11+
import io.element.android.libraries.matrix.api.core.RoomId
1312
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
14-
import io.element.android.libraries.matrix.api.room.RoomType
15-
import io.element.android.libraries.matrix.api.room.join.JoinRule
1613
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
17-
import io.element.android.libraries.matrix.api.user.MatrixUser
14+
import io.element.android.libraries.previewutils.room.aSpaceRoom
1815

1916
class SpaceRoomProvider : PreviewParameterProvider<SpaceRoom> {
2017
override val values: Sequence<SpaceRoom> = sequenceOf(
21-
aSpaceRooms(),
22-
aSpaceRooms(
18+
aSpaceRoom(),
19+
aSpaceRoom(
2320
numJoinedMembers = 5,
2421
childrenCount = 10,
2522
worldReadable = true,
26-
spaceId = SpaceId("!spaceId0:example.com"),
23+
roomId = RoomId("!spaceId0:example.com"),
2724
),
28-
aSpaceRooms(
25+
aSpaceRoom(
2926
numJoinedMembers = 5,
3027
childrenCount = 10,
3128
worldReadable = true,
3229
avatarUrl = "anUrl",
33-
spaceId = SpaceId("!spaceId1:example.com"),
30+
roomId = RoomId("!spaceId1:example.com"),
3431
),
35-
aSpaceRooms(
32+
aSpaceRoom(
3633
name = null,
3734
numJoinedMembers = 5,
3835
childrenCount = 10,
3936
worldReadable = true,
4037
avatarUrl = "anUrl",
41-
spaceId = SpaceId("!spaceId2:example.com"),
38+
roomId = RoomId("!spaceId2:example.com"),
4239
state = CurrentUserMembership.INVITED,
4340
),
44-
aSpaceRooms(
41+
aSpaceRoom(
4542
name = null,
4643
numJoinedMembers = 5,
4744
childrenCount = 10,
4845
worldReadable = true,
4946
avatarUrl = "anUrl",
50-
spaceId = SpaceId("!spaceId3:example.com"),
47+
roomId = RoomId("!spaceId3:example.com"),
5148
state = CurrentUserMembership.INVITED,
5249
),
5350
)
5451
}
55-
56-
fun aSpaceRooms(
57-
name: String? = "Space name",
58-
avatarUrl: String? = null,
59-
canonicalAlias: RoomAlias? = null,
60-
childrenCount: Int = 0,
61-
guestCanJoin: Boolean = false,
62-
heroes: List<MatrixUser> = emptyList(),
63-
joinRule: JoinRule? = null,
64-
numJoinedMembers: Int = 0,
65-
spaceId: SpaceId = SpaceId("!spaceId:example.com"),
66-
roomType: RoomType = RoomType.Space,
67-
state: CurrentUserMembership? = null,
68-
topic: String? = null,
69-
worldReadable: Boolean = false,
70-
) = SpaceRoom(
71-
name = name,
72-
avatarUrl = avatarUrl,
73-
canonicalAlias = canonicalAlias,
74-
childrenCount = childrenCount,
75-
guestCanJoin = guestCanJoin,
76-
heroes = heroes,
77-
joinRule = joinRule,
78-
numJoinedMembers = numJoinedMembers,
79-
spaceId = spaceId,
80-
roomType = roomType,
81-
state = state,
82-
topic = topic,
83-
worldReadable = worldReadable
84-
)

0 commit comments

Comments
 (0)