Skip to content

Commit 6cd0af9

Browse files
authored
Merge pull request #5378 from element-hq/feature/fga/join_space
Feature : Join Space (WIP)
2 parents 06bd0ce + f3a9646 commit 6cd0af9

File tree

94 files changed

+848
-464
lines changed

Some content is hidden

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

94 files changed

+848
-464
lines changed

features/invite/api/src/main/kotlin/io/element/android/features/invite/api/InviteData.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
1212
import io.element.android.libraries.matrix.api.room.RoomInfo
1313
import io.element.android.libraries.matrix.api.room.isDm
1414
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
15+
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
1516
import kotlinx.parcelize.Parcelize
1617

1718
@Parcelize
@@ -36,3 +37,11 @@ fun RoomInfo.toInviteData(): InviteData {
3637
isDm = isDm,
3738
)
3839
}
40+
41+
fun SpaceRoom.toInviteData(): InviteData {
42+
return InviteData(
43+
roomId = roomId,
44+
roomName = name ?: roomId.value,
45+
isDm = false,
46+
)
47+
}

features/joinroom/impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ dependencies {
4343
testImplementation(projects.features.invite.test)
4444
testImplementation(projects.libraries.matrix.test)
4545
testImplementation(projects.libraries.preferences.test)
46+
testImplementation(projects.libraries.previewutils)
4647
}

features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt

Lines changed: 122 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import androidx.compose.runtime.setValue
2323
import dev.zacsweers.metro.Assisted
2424
import dev.zacsweers.metro.Inject
2525
import im.vector.app.features.analytics.plan.JoinedRoom
26+
import io.element.android.features.invite.api.InviteData
2627
import io.element.android.features.invite.api.SeenInvitesStore
2728
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
2829
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
@@ -42,17 +43,21 @@ import io.element.android.libraries.matrix.api.exception.ClientException
4243
import io.element.android.libraries.matrix.api.exception.ErrorKind
4344
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
4445
import io.element.android.libraries.matrix.api.room.RoomInfo
45-
import io.element.android.libraries.matrix.api.room.RoomMember
46+
import io.element.android.libraries.matrix.api.room.RoomMembershipDetails
4647
import io.element.android.libraries.matrix.api.room.RoomType
4748
import io.element.android.libraries.matrix.api.room.isDm
4849
import io.element.android.libraries.matrix.api.room.join.JoinRoom
4950
import io.element.android.libraries.matrix.api.room.join.JoinRule
5051
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
52+
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
5153
import io.element.android.libraries.matrix.ui.model.toInviteSender
5254
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
55+
import kotlinx.collections.immutable.persistentListOf
56+
import kotlinx.collections.immutable.toPersistentList
5357
import kotlinx.coroutines.CoroutineScope
5458
import kotlinx.coroutines.launch
5559
import java.util.Optional
60+
import kotlin.jvm.optionals.getOrNull
5661

5762
@Inject
5863
class JoinRoomPresenter(
@@ -80,13 +85,18 @@ class JoinRoomPresenter(
8085
): JoinRoomPresenter
8186
}
8287

88+
private val spaceList = matrixClient.spaceService.spaceRoomList(roomId)
89+
8390
@Composable
8491
override fun present(): JoinRoomState {
8592
val coroutineScope = rememberCoroutineScope()
8693
var retryCount by remember { mutableIntStateOf(0) }
8794
val roomInfo by remember {
8895
matrixClient.getRoomInfoFlow(roomId)
8996
}.collectAsState(initial = Optional.empty())
97+
val spaceRoom by remember {
98+
spaceList.currentSpaceFlow()
99+
}.collectAsState()
90100
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
91101
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
92102
val cancelKnockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
@@ -96,55 +106,41 @@ class JoinRoomPresenter(
96106
val hideInviteAvatars by matrixClient.rememberHideInvitesAvatar()
97107
val canReportRoom by produceState(false) { value = matrixClient.canReportRoom() }
98108

99-
val contentState by produceState<ContentState>(
100-
initialValue = ContentState.Loading,
101-
key1 = roomInfo,
102-
key2 = retryCount,
103-
key3 = isDismissingContent,
104-
) {
109+
var contentState by remember {
110+
mutableStateOf<ContentState>(ContentState.Loading)
111+
}
112+
LaunchedEffect(roomInfo, retryCount, isDismissingContent, spaceRoom) {
105113
when {
106-
isDismissingContent -> value = ContentState.Dismissing
114+
isDismissingContent -> contentState = ContentState.Dismissing
107115
roomInfo.isPresent -> {
108116
val notJoinedRoom = matrixClient.getRoomPreview(roomIdOrAlias, serverNames).getOrNull()
109-
val (sender, reason) = when (roomInfo.get().currentUserMembership) {
110-
CurrentUserMembership.BANNED -> {
111-
// Workaround to get info about the sender for banned rooms
112-
// TODO re-do this once we have a better API in the SDK
113-
val membershipDetails = notJoinedRoom?.membershipDetails()?.getOrNull()
114-
membershipDetails?.senderMember to membershipDetails?.currentUserMember?.membershipChangeReason
115-
}
116-
CurrentUserMembership.INVITED -> {
117-
roomInfo.get().inviter to null
118-
}
119-
else -> null to null
120-
}
117+
val membershipDetails = notJoinedRoom?.membershipDetails()?.getOrNull()
121118
val joinedMembersCountOverride = notJoinedRoom?.previewInfo?.numberOfJoinedMembers
122-
value = roomInfo.get().toContentState(
123-
membershipSender = sender,
119+
contentState = roomInfo.get().toContentState(
124120
joinedMembersCountOverride = joinedMembersCountOverride,
125-
reason = reason,
121+
membershipDetails = membershipDetails,
122+
childrenCount = spaceRoom.getOrNull()?.childrenCount,
126123
)
127124
}
125+
spaceRoom.isPresent -> {
126+
val spaceRoom = spaceRoom.get()
127+
// Only use this state when space is not locally known
128+
contentState = if (spaceRoom.state != null) {
129+
ContentState.Loading
130+
} else {
131+
spaceRoom.toContentState()
132+
}
133+
}
128134
roomDescription.isPresent -> {
129-
value = roomDescription.get().toContentState()
135+
contentState = roomDescription.get().toContentState()
130136
}
131137
else -> {
132-
value = ContentState.Loading
138+
contentState = ContentState.Loading
133139
val result = matrixClient.getRoomPreview(roomIdOrAlias, serverNames)
134-
value = result.fold(
140+
contentState = result.fold(
135141
onSuccess = { preview ->
136-
val membershipInfo = when (preview.previewInfo.membership) {
137-
CurrentUserMembership.INVITED,
138-
CurrentUserMembership.BANNED,
139-
CurrentUserMembership.KNOCKED -> {
140-
preview.membershipDetails().getOrNull()
141-
}
142-
else -> null
143-
}
144-
preview.previewInfo.toContentState(
145-
senderMember = membershipInfo?.senderMember,
146-
reason = membershipInfo?.currentUserMember?.membershipChangeReason,
147-
)
142+
val membershipDetails = preview.membershipDetails().getOrNull()
143+
preview.previewInfo.toContentState(membershipDetails)
148144
},
149145
onFailure = { throwable ->
150146
if (throwable is ClientException.MatrixApi && (throwable.kind == ErrorKind.NotFound || throwable.kind == ErrorKind.Forbidden)) {
@@ -252,30 +248,56 @@ class JoinRoomPresenter(
252248
}
253249
}
254250

255-
private fun RoomPreviewInfo.toContentState(senderMember: RoomMember?, reason: String?): ContentState {
251+
private fun RoomPreviewInfo.toContentState(membershipDetails: RoomMembershipDetails?): ContentState {
256252
return ContentState.Loaded(
257253
roomId = roomId,
258254
name = name,
259255
topic = topic,
260256
alias = canonicalAlias,
261257
numberOfMembers = numberOfJoinedMembers,
262-
isDm = false,
263-
roomType = roomType,
264258
roomAvatarUrl = avatarUrl,
265-
joinAuthorisationStatus = when (membership) {
266-
CurrentUserMembership.INVITED -> {
267-
JoinAuthorisationStatus.IsInvited(
268-
inviteData = toInviteData(),
269-
inviteSender = senderMember?.toInviteSender()
270-
)
271-
}
272-
CurrentUserMembership.BANNED -> JoinAuthorisationStatus.IsBanned(senderMember?.toInviteSender(), reason)
273-
CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked
274-
else -> joinRule.toJoinAuthorisationStatus()
259+
joinAuthorisationStatus = computeJoinAuthorisationStatus(
260+
membership,
261+
membershipDetails,
262+
joinRule,
263+
{ toInviteData() }
264+
),
265+
joinRule = joinRule,
266+
details = when (roomType) {
267+
is RoomType.Other,
268+
RoomType.Room -> LoadedDetails.Room(
269+
isDm = false,
270+
)
271+
RoomType.Space -> LoadedDetails.Space(
272+
childrenCount = 0,
273+
heroes = persistentListOf(),
274+
)
275275
}
276276
)
277277
}
278278

279+
private fun SpaceRoom.toContentState(): ContentState {
280+
return ContentState.Loaded(
281+
roomId = roomId,
282+
name = name,
283+
topic = topic,
284+
alias = canonicalAlias,
285+
numberOfMembers = numJoinedMembers.toLong(),
286+
roomAvatarUrl = avatarUrl,
287+
joinAuthorisationStatus = computeJoinAuthorisationStatus(
288+
membership = state,
289+
membershipDetails = null,
290+
joinRule = joinRule,
291+
inviteData = { toInviteData() }
292+
),
293+
joinRule = joinRule,
294+
details = LoadedDetails.Space(
295+
childrenCount = childrenCount,
296+
heroes = heroes.toPersistentList(),
297+
)
298+
)
299+
}
300+
279301
@VisibleForTesting
280302
internal fun RoomDescription.toContentState(): ContentState {
281303
return ContentState.Loaded(
@@ -284,47 +306,79 @@ internal fun RoomDescription.toContentState(): ContentState {
284306
topic = topic,
285307
alias = alias,
286308
numberOfMembers = numberOfMembers,
287-
isDm = false,
288-
roomType = RoomType.Room,
289309
roomAvatarUrl = avatarUrl,
290310
joinAuthorisationStatus = when (joinRule) {
291311
RoomDescription.JoinRule.KNOCK -> JoinAuthorisationStatus.CanKnock
292312
RoomDescription.JoinRule.PUBLIC -> JoinAuthorisationStatus.CanJoin
293313
else -> JoinAuthorisationStatus.Unknown
294-
}
314+
},
315+
joinRule = when (joinRule) {
316+
RoomDescription.JoinRule.KNOCK -> JoinRule.Knock
317+
RoomDescription.JoinRule.PUBLIC -> JoinRule.Public
318+
RoomDescription.JoinRule.RESTRICTED -> JoinRule.Restricted(persistentListOf())
319+
RoomDescription.JoinRule.KNOCK_RESTRICTED -> JoinRule.KnockRestricted(persistentListOf())
320+
RoomDescription.JoinRule.INVITE -> JoinRule.Invite
321+
RoomDescription.JoinRule.UNKNOWN -> null
322+
},
323+
details = LoadedDetails.Room(isDm = false)
295324
)
296325
}
297326

298327
@VisibleForTesting
299328
internal fun RoomInfo.toContentState(
300-
membershipSender: RoomMember?,
301329
joinedMembersCountOverride: Long?,
302-
reason: String?,
330+
membershipDetails: RoomMembershipDetails?,
331+
childrenCount: Int?,
303332
): ContentState {
304333
return ContentState.Loaded(
305334
roomId = id,
306335
name = name,
307336
topic = topic,
308337
alias = canonicalAlias,
309338
numberOfMembers = joinedMembersCountOverride ?: joinedMembersCount,
310-
isDm = isDm,
311-
roomType = if (isSpace) RoomType.Space else RoomType.Room,
312339
roomAvatarUrl = avatarUrl,
313-
joinAuthorisationStatus = when (currentUserMembership) {
314-
CurrentUserMembership.INVITED -> JoinAuthorisationStatus.IsInvited(
315-
inviteData = toInviteData(),
316-
inviteSender = membershipSender?.toInviteSender(),
340+
joinAuthorisationStatus = computeJoinAuthorisationStatus(
341+
membership = currentUserMembership,
342+
membershipDetails = membershipDetails,
343+
joinRule = joinRule,
344+
inviteData = { toInviteData() }
345+
),
346+
joinRule = joinRule,
347+
details = if (isSpace) {
348+
LoadedDetails.Space(
349+
childrenCount = childrenCount ?: 0,
350+
heroes = heroes,
317351
)
318-
CurrentUserMembership.BANNED -> JoinAuthorisationStatus.IsBanned(
319-
banSender = membershipSender?.toInviteSender(),
320-
reason = reason,
352+
} else {
353+
LoadedDetails.Room(
354+
isDm = isDm,
321355
)
322-
CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked
323-
else -> joinRule.toJoinAuthorisationStatus()
324-
}
356+
},
325357
)
326358
}
327359

360+
private fun computeJoinAuthorisationStatus(
361+
membership: CurrentUserMembership?,
362+
membershipDetails: RoomMembershipDetails?,
363+
joinRule: JoinRule?,
364+
inviteData: () -> InviteData,
365+
): JoinAuthorisationStatus {
366+
return when (membership) {
367+
CurrentUserMembership.INVITED -> {
368+
JoinAuthorisationStatus.IsInvited(
369+
inviteData = inviteData(),
370+
inviteSender = membershipDetails?.senderMember?.toInviteSender()
371+
)
372+
}
373+
CurrentUserMembership.BANNED -> JoinAuthorisationStatus.IsBanned(
374+
membershipDetails?.senderMember?.toInviteSender(),
375+
membershipDetails?.membershipChangeReason
376+
)
377+
CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked
378+
else -> joinRule.toJoinAuthorisationStatus()
379+
}
380+
}
381+
328382
private fun JoinRule?.toJoinAuthorisationStatus(): JoinAuthorisationStatus {
329383
return when (this) {
330384
JoinRule.Knock,

features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
1616
import io.element.android.libraries.matrix.api.core.RoomAlias
1717
import io.element.android.libraries.matrix.api.core.RoomId
1818
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
19-
import io.element.android.libraries.matrix.api.room.RoomType
2019
import io.element.android.libraries.matrix.api.room.join.JoinRoom
20+
import io.element.android.libraries.matrix.api.room.join.JoinRule
21+
import io.element.android.libraries.matrix.api.user.MatrixUser
2122
import io.element.android.libraries.matrix.ui.model.InviteSender
23+
import kotlinx.collections.immutable.ImmutableList
2224

2325
internal const val MAX_KNOCK_MESSAGE_LENGTH = 500
2426

@@ -41,9 +43,6 @@ data class JoinRoomState(
4143
val joinAuthorisationStatus = when (contentState) {
4244
is ContentState.Loaded -> {
4345
when {
44-
contentState.roomType == RoomType.Space -> {
45-
JoinAuthorisationStatus.IsSpace(applicationName)
46-
}
4746
isJoinActionUnauthorized -> {
4847
JoinAuthorisationStatus.Unauthorized
4948
}
@@ -77,12 +76,13 @@ sealed interface ContentState {
7776
val topic: String?,
7877
val alias: RoomAlias?,
7978
val numberOfMembers: Long?,
80-
val isDm: Boolean,
81-
val roomType: RoomType,
8279
val roomAvatarUrl: String?,
8380
val joinAuthorisationStatus: JoinAuthorisationStatus,
81+
val joinRule: JoinRule?,
82+
val details: LoadedDetails,
8483
) : ContentState {
8584
val showMemberCount = numberOfMembers != null
85+
val isSpace = details is LoadedDetails.Space
8686

8787
fun avatarData(size: AvatarSize): AvatarData {
8888
return AvatarData(
@@ -95,9 +95,20 @@ sealed interface ContentState {
9595
}
9696
}
9797

98+
@Immutable
99+
sealed interface LoadedDetails {
100+
data class Room(
101+
val isDm: Boolean,
102+
) : LoadedDetails
103+
104+
data class Space(
105+
val childrenCount: Int,
106+
val heroes: ImmutableList<MatrixUser>,
107+
) : LoadedDetails
108+
}
109+
98110
sealed interface JoinAuthorisationStatus {
99111
data object None : JoinAuthorisationStatus
100-
data class IsSpace(val applicationName: String) : JoinAuthorisationStatus
101112
data class IsInvited(val inviteData: InviteData, val inviteSender: InviteSender?) : JoinAuthorisationStatus
102113
data class IsBanned(val banSender: InviteSender?, val reason: String?) : JoinAuthorisationStatus
103114
data object IsKnocked : JoinAuthorisationStatus

0 commit comments

Comments
 (0)