Skip to content

Commit a625c10

Browse files
authored
Merge pull request #5207 from element-hq/feature/bma/spaceInfoUi
Add UI components for spaces.
2 parents 7a5a197 + 7b1a4bb commit a625c10

File tree

18 files changed

+288
-26
lines changed

18 files changed

+288
-26
lines changed

features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import io.element.android.tests.testutils.lambda.assert
5858
import io.element.android.tests.testutils.lambda.lambdaRecorder
5959
import io.element.android.tests.testutils.lambda.value
6060
import io.element.android.tests.testutils.test
61+
import kotlinx.collections.immutable.persistentListOf
6162
import kotlinx.coroutines.flow.first
6263
import kotlinx.coroutines.flow.flowOf
6364
import kotlinx.coroutines.test.runTest
@@ -913,7 +914,7 @@ class JoinRoomPresenterTest {
913914
val client = FakeMatrixClient(
914915
getNotJoinedRoomResult = { _, _ ->
915916
Result.success(
916-
aRoomPreview(info = aRoomPreviewInfo(joinRule = JoinRule.KnockRestricted(emptyList())))
917+
aRoomPreview(info = aRoomPreviewInfo(joinRule = JoinRule.KnockRestricted(persistentListOf())))
917918
)
918919
}
919920
)
@@ -933,7 +934,7 @@ class JoinRoomPresenterTest {
933934
val client = FakeMatrixClient(
934935
getNotJoinedRoomResult = { _, _ ->
935936
Result.success(
936-
aRoomPreview(info = aRoomPreviewInfo(joinRule = JoinRule.Restricted(emptyList())))
937+
aRoomPreview(info = aRoomPreviewInfo(joinRule = JoinRule.Restricted(persistentListOf())))
937938
)
938939
}
939940
)

libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,6 @@ enum class AvatarSize(val dp: Dp) {
6565
UserVerification(52.dp),
6666

6767
OrganizationHeader(64.dp),
68+
SpaceHeader(64.dp),
6869
SpaceMember(24.dp),
6970
}

libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/AllowRule.kt

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

88
package io.element.android.libraries.matrix.api.room.join
99

10+
import androidx.compose.runtime.Immutable
1011
import io.element.android.libraries.matrix.api.core.RoomId
1112

13+
@Immutable
1214
sealed interface AllowRule {
1315
data class RoomMembership(val roomId: RoomId) : AllowRule
1416
data class Custom(val json: String) : AllowRule

libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt

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

88
package io.element.android.libraries.matrix.api.room.join
99

10+
import androidx.compose.runtime.Immutable
11+
import kotlinx.collections.immutable.ImmutableList
12+
13+
@Immutable
1014
sealed interface JoinRule {
1115
data object Public : JoinRule
1216
data object Private : JoinRule
1317
data object Knock : JoinRule
1418
data object Invite : JoinRule
15-
data class Restricted(val rules: List<AllowRule>) : JoinRule
16-
data class KnockRestricted(val rules: List<AllowRule>) : JoinRule
19+
data class Restricted(val rules: ImmutableList<AllowRule>) : JoinRule
20+
data class KnockRestricted(val rules: ImmutableList<AllowRule>) : JoinRule
1721
data class Custom(val value: String) : JoinRule
1822
}

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/JoinRule.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package io.element.android.libraries.matrix.impl.room.join
99

1010
import io.element.android.libraries.matrix.api.room.join.JoinRule
11+
import kotlinx.collections.immutable.toPersistentList
1112
import org.matrix.rustcomponents.sdk.JoinRule as RustJoinRule
1213

1314
fun RustJoinRule.map(): JoinRule {
@@ -16,9 +17,9 @@ fun RustJoinRule.map(): JoinRule {
1617
RustJoinRule.Private -> JoinRule.Private
1718
RustJoinRule.Knock -> JoinRule.Knock
1819
RustJoinRule.Invite -> JoinRule.Invite
19-
is RustJoinRule.Restricted -> JoinRule.Restricted(rules.map { it.map() })
20+
is RustJoinRule.Restricted -> JoinRule.Restricted(rules.map { it.map() }.toPersistentList())
2021
is RustJoinRule.Custom -> JoinRule.Custom(repr)
21-
is RustJoinRule.KnockRestricted -> JoinRule.KnockRestricted(rules.map { it.map() })
22+
is RustJoinRule.KnockRestricted -> JoinRule.KnockRestricted(rules.map { it.map() }.toPersistentList())
2223
}
2324
}
2425

libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/OrganizationHeader.kt

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import androidx.compose.material3.Text
1616
import androidx.compose.runtime.Composable
1717
import androidx.compose.ui.Alignment
1818
import androidx.compose.ui.Modifier
19-
import androidx.compose.ui.res.pluralStringResource
20-
import androidx.compose.ui.res.stringResource
2119
import androidx.compose.ui.text.style.TextAlign
2220
import androidx.compose.ui.unit.dp
2321
import io.element.android.compound.theme.ElementTheme
@@ -28,8 +26,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarType
2826
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
2927
import io.element.android.libraries.designsystem.preview.ElementPreview
3028
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
31-
import io.element.android.libraries.ui.strings.CommonPlurals
32-
import io.element.android.libraries.ui.strings.CommonStrings
3329

3430
/**
3531
* Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=3643-2048&m=dev
@@ -60,16 +56,9 @@ fun OrganizationHeader(
6056
textAlign = TextAlign.Center,
6157
)
6258
Spacer(modifier = Modifier.height(12.dp))
63-
val subtitle = stringResource(
64-
id = CommonStrings.screen_space_list_details,
65-
pluralStringResource(CommonPlurals.common_spaces, numberOfSpaces, numberOfSpaces),
66-
pluralStringResource(CommonPlurals.common_rooms, numberOfRooms, numberOfRooms),
67-
)
68-
Text(
69-
text = subtitle,
70-
style = ElementTheme.typography.fontBodyLgRegular,
71-
color = ElementTheme.colors.textSecondary,
72-
textAlign = TextAlign.Center,
59+
SpaceInfoRow(
60+
leftText = numberOfSpaces(numberOfSpaces),
61+
rightText = numberOfRooms(numberOfRooms),
7362
)
7463
}
7564
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.matrix.ui.components
9+
10+
import androidx.compose.foundation.layout.Arrangement
11+
import androidx.compose.foundation.layout.Column
12+
import androidx.compose.foundation.layout.fillMaxWidth
13+
import androidx.compose.foundation.layout.padding
14+
import androidx.compose.material3.Text
15+
import androidx.compose.runtime.Composable
16+
import androidx.compose.ui.Alignment
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.text.style.TextAlign
19+
import androidx.compose.ui.text.style.TextOverflow
20+
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
21+
import androidx.compose.ui.unit.dp
22+
import io.element.android.compound.theme.ElementTheme
23+
import io.element.android.libraries.designsystem.components.avatar.Avatar
24+
import io.element.android.libraries.designsystem.components.avatar.AvatarData
25+
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
26+
import io.element.android.libraries.designsystem.components.avatar.AvatarType
27+
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
28+
import io.element.android.libraries.designsystem.preview.ElementPreview
29+
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
30+
import io.element.android.libraries.matrix.api.room.join.JoinRule
31+
import io.element.android.libraries.matrix.api.user.MatrixUser
32+
import kotlinx.collections.immutable.ImmutableList
33+
import kotlinx.collections.immutable.persistentListOf
34+
35+
/**
36+
* Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=3643-2429&m=dev
37+
*/
38+
@Composable
39+
fun SpaceHeaderView(
40+
avatarData: AvatarData,
41+
name: String,
42+
topic: String,
43+
joinRule: JoinRule,
44+
heroes: ImmutableList<MatrixUser>,
45+
numberOfMembers: Long,
46+
numberOfRooms: Int,
47+
modifier: Modifier = Modifier,
48+
topicMaxLines: Int = Int.MAX_VALUE,
49+
) {
50+
Column(
51+
modifier = modifier
52+
.fillMaxWidth()
53+
.padding(top = 32.dp, bottom = 24.dp, start = 16.dp, end = 16.dp),
54+
horizontalAlignment = Alignment.CenterHorizontally,
55+
verticalArrangement = Arrangement.spacedBy(16.dp)
56+
) {
57+
Avatar(
58+
avatarData = avatarData,
59+
avatarType = AvatarType.Space(false),
60+
)
61+
Text(
62+
text = name,
63+
style = ElementTheme.typography.fontHeadingLgBold,
64+
color = ElementTheme.colors.textPrimary,
65+
textAlign = TextAlign.Center,
66+
)
67+
SpaceInfoRow(
68+
joinRule = joinRule,
69+
numberOfRooms = numberOfRooms,
70+
)
71+
SpaceMembersView(
72+
heroes = heroes,
73+
numberOfMembers = numberOfMembers,
74+
modifier = Modifier.padding(horizontal = 32.dp),
75+
)
76+
Text(
77+
text = topic,
78+
style = ElementTheme.typography.fontBodyMdRegular,
79+
color = ElementTheme.colors.textPrimary,
80+
textAlign = TextAlign.Center,
81+
maxLines = topicMaxLines,
82+
overflow = TextOverflow.Ellipsis,
83+
)
84+
}
85+
}
86+
87+
@PreviewsDayNight
88+
@Composable
89+
internal fun SpaceHeaderViewPreview() = ElementPreview {
90+
SpaceHeaderView(
91+
avatarData = anAvatarData(
92+
url = "anUrl",
93+
size = AvatarSize.SpaceHeader,
94+
),
95+
name = "Space name",
96+
topic = "Space topic: " + LoremIpsum(40).values.first(),
97+
topicMaxLines = 2,
98+
joinRule = JoinRule.Public,
99+
heroes = persistentListOf(
100+
aMatrixUser(id = "@1:d", displayName = "Alice", avatarUrl = "aUrl"),
101+
aMatrixUser(id = "@2:d", displayName = "Bob"),
102+
aMatrixUser(id = "@3:d", displayName = "Charlie", avatarUrl = "aUrl"),
103+
aMatrixUser(id = "@4:d", displayName = "Dave"),
104+
),
105+
numberOfMembers = 999,
106+
numberOfRooms = 10,
107+
)
108+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.matrix.ui.components
9+
10+
import androidx.compose.foundation.layout.Arrangement.spacedBy
11+
import androidx.compose.foundation.layout.Column
12+
import androidx.compose.foundation.layout.Row
13+
import androidx.compose.foundation.layout.fillMaxWidth
14+
import androidx.compose.foundation.layout.size
15+
import androidx.compose.material3.Text
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.runtime.ReadOnlyComposable
18+
import androidx.compose.ui.Alignment
19+
import androidx.compose.ui.Modifier
20+
import androidx.compose.ui.graphics.vector.ImageVector
21+
import androidx.compose.ui.res.pluralStringResource
22+
import androidx.compose.ui.res.stringResource
23+
import androidx.compose.ui.text.style.TextAlign
24+
import androidx.compose.ui.unit.dp
25+
import io.element.android.compound.theme.ElementTheme
26+
import io.element.android.compound.tokens.generated.CompoundIcons
27+
import io.element.android.libraries.designsystem.preview.ElementPreview
28+
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
29+
import io.element.android.libraries.designsystem.theme.components.Icon
30+
import io.element.android.libraries.matrix.api.room.join.JoinRule
31+
import io.element.android.libraries.ui.strings.CommonPlurals
32+
import io.element.android.libraries.ui.strings.CommonStrings
33+
34+
@Composable
35+
fun SpaceInfoRow(
36+
leftText: String,
37+
rightText: String,
38+
modifier: Modifier = Modifier,
39+
iconVector: ImageVector? = null,
40+
) {
41+
Row(
42+
modifier = modifier,
43+
horizontalArrangement = spacedBy(4.dp),
44+
verticalAlignment = Alignment.CenterVertically,
45+
) {
46+
if (iconVector != null) {
47+
Icon(
48+
modifier = Modifier.size(20.dp),
49+
imageVector = iconVector,
50+
contentDescription = null,
51+
tint = ElementTheme.colors.iconTertiary,
52+
)
53+
}
54+
val text = stringResource(id = CommonStrings.screen_space_list_details, leftText, rightText)
55+
Text(
56+
text = text,
57+
style = ElementTheme.typography.fontBodyLgRegular,
58+
color = ElementTheme.colors.textSecondary,
59+
textAlign = TextAlign.Center,
60+
)
61+
}
62+
}
63+
64+
@Composable
65+
fun SpaceInfoRow(
66+
joinRule: JoinRule,
67+
numberOfRooms: Int,
68+
modifier: Modifier = Modifier,
69+
) {
70+
val (leftText, rightText, icon) = when (joinRule) {
71+
JoinRule.Public -> Triple(
72+
stringResource(id = CommonStrings.common_public_space),
73+
numberOfRooms(numberOfRooms),
74+
CompoundIcons.Public(),
75+
)
76+
// TODO External space
77+
// JoinRule.Private -> Triple(
78+
// stringResource(id = CommonStrings.common_external_space),
79+
// numberOfRooms(numberOfRooms),
80+
// CompoundIcons.Guest(),
81+
// )
82+
// JoinRule.Private,
83+
else -> Triple(
84+
stringResource(id = CommonStrings.common_private_space),
85+
numberOfRooms(numberOfRooms),
86+
CompoundIcons.Lock(),
87+
)
88+
}
89+
SpaceInfoRow(
90+
leftText = leftText,
91+
rightText = rightText,
92+
modifier = modifier,
93+
iconVector = icon,
94+
)
95+
}
96+
97+
@Composable
98+
@ReadOnlyComposable
99+
fun numberOfRooms(numberOfRooms: Int): String {
100+
return pluralStringResource(CommonPlurals.common_rooms, numberOfRooms, numberOfRooms)
101+
}
102+
103+
@Composable
104+
@ReadOnlyComposable
105+
fun numberOfSpaces(numberOfSpaces: Int): String {
106+
return pluralStringResource(CommonPlurals.common_spaces, numberOfSpaces, numberOfSpaces)
107+
}
108+
109+
@PreviewsDayNight
110+
@Composable
111+
internal fun SpaceInfoRowPreview() = ElementPreview {
112+
Column(
113+
modifier = Modifier.fillMaxWidth(),
114+
verticalArrangement = spacedBy(4.dp),
115+
horizontalAlignment = Alignment.CenterHorizontally,
116+
) {
117+
SpaceInfoRow(
118+
leftText = numberOfSpaces(5),
119+
rightText = numberOfRooms(10),
120+
)
121+
SpaceInfoRow(
122+
leftText = "Element space",
123+
rightText = numberOfRooms(16),
124+
iconVector = CompoundIcons.Workspace(),
125+
)
126+
SpaceInfoRow(
127+
joinRule = JoinRule.Private,
128+
numberOfRooms = 4,
129+
)
130+
SpaceInfoRow(
131+
joinRule = JoinRule.Public,
132+
numberOfRooms = 10,
133+
)
134+
}
135+
}
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)