Skip to content

Commit cc10ba4

Browse files
authored
Merge pull request #5185 from element-hq/feature/bma/invitePoepleUi
Iterate on invite people UI
2 parents 2b3a579 + a39dafe commit cc10ba4

File tree

84 files changed

+494
-302
lines changed

Some content is hidden

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

84 files changed

+494
-302
lines changed

features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import io.element.android.libraries.designsystem.components.async.AsyncLoading
3030
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
3131
import io.element.android.libraries.designsystem.preview.ElementPreview
3232
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
33-
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
3433
import io.element.android.libraries.designsystem.theme.components.SearchBar
3534
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
3635
import io.element.android.libraries.designsystem.theme.components.Text
@@ -192,10 +191,6 @@ private fun InvitePeopleSearchBar(
192191
onCheckedChange = { onToggleUser(invitableUser.matrixUser) },
193192
modifier = Modifier.fillMaxWidth()
194193
)
195-
196-
if (index < results.lastIndex) {
197-
HorizontalDivider()
198-
}
199194
}
200195
}
201196
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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.designsystem.atomic.atoms
9+
10+
import androidx.compose.foundation.layout.Arrangement
11+
import androidx.compose.foundation.layout.Box
12+
import androidx.compose.foundation.layout.Column
13+
import androidx.compose.foundation.layout.padding
14+
import androidx.compose.foundation.layout.size
15+
import androidx.compose.foundation.selection.toggleable
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.semantics.Role
19+
import androidx.compose.ui.unit.dp
20+
import io.element.android.compound.theme.ElementTheme
21+
import io.element.android.compound.tokens.generated.CompoundIcons
22+
import io.element.android.libraries.designsystem.preview.ElementPreview
23+
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
24+
import io.element.android.libraries.designsystem.theme.components.Icon
25+
26+
@Composable
27+
fun SelectedIndicatorAtom(
28+
checked: Boolean,
29+
enabled: Boolean,
30+
modifier: Modifier = Modifier,
31+
) {
32+
if (checked) {
33+
Icon(
34+
modifier = modifier.toggleable(
35+
value = true,
36+
role = Role.Companion.Checkbox,
37+
enabled = enabled,
38+
onValueChange = {},
39+
),
40+
imageVector = CompoundIcons.CheckCircleSolid(),
41+
contentDescription = null,
42+
tint = if (enabled) {
43+
ElementTheme.colors.iconAccentPrimary
44+
} else {
45+
ElementTheme.colors.iconDisabled
46+
},
47+
)
48+
} else {
49+
Box(modifier)
50+
}
51+
}
52+
53+
@Composable
54+
@PreviewsDayNight
55+
internal fun SelectedIndicatorAtomPreview() = ElementPreview {
56+
Column(
57+
modifier = Modifier.padding(8.dp),
58+
verticalArrangement = Arrangement.spacedBy(8.dp),
59+
) {
60+
SelectedIndicatorAtom(
61+
modifier = Modifier.size(24.dp),
62+
checked = false,
63+
enabled = false,
64+
)
65+
SelectedIndicatorAtom(
66+
modifier = Modifier.size(24.dp),
67+
checked = true,
68+
enabled = false,
69+
)
70+
SelectedIndicatorAtom(
71+
modifier = Modifier.size(24.dp),
72+
checked = false,
73+
enabled = true,
74+
)
75+
SelectedIndicatorAtom(
76+
modifier = Modifier.size(24.dp),
77+
checked = true,
78+
enabled = true,
79+
)
80+
}
81+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ enum class AvatarSize(val dp: Dp) {
2424
UserHeader(96.dp),
2525
UserListItem(36.dp),
2626

27-
SelectedUser(56.dp),
27+
SelectedUser(52.dp),
2828
SelectedRoom(56.dp),
2929

3030
DmCluster(75.dp),

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ import androidx.compose.ui.Modifier
1919
import androidx.compose.ui.semantics.Role
2020
import androidx.compose.ui.tooling.preview.Preview
2121
import androidx.compose.ui.unit.dp
22+
import io.element.android.libraries.designsystem.atomic.atoms.SelectedIndicatorAtom
2223
import io.element.android.libraries.designsystem.components.avatar.AvatarData
2324
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
2425
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
25-
import io.element.android.libraries.designsystem.theme.components.Checkbox
2626
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
2727
import io.element.android.libraries.matrix.ui.model.getAvatarData
2828

@@ -50,21 +50,21 @@ fun CheckableUserRow(
5050
avatarData = data.avatarData,
5151
name = data.name,
5252
subtext = data.subtext,
53+
enabled = enabled,
5354
)
5455
}
5556
is CheckableUserRowData.Unresolved -> {
5657
UnresolvedUserRow(
5758
modifier = rowModifier,
5859
avatarData = data.avatarData,
5960
id = data.id,
61+
enabled = enabled,
6062
)
6163
}
6264
}
63-
64-
Checkbox(
65-
modifier = Modifier.padding(end = 4.dp),
65+
SelectedIndicatorAtom(
66+
modifier = Modifier.padding(end = 24.dp),
6667
checked = checked,
67-
onCheckedChange = null,
6868
enabled = enabled,
6969
)
7070
}

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

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,24 @@ open class MatrixUserProvider : PreviewParameterProvider<MatrixUser> {
1515
override val values: Sequence<MatrixUser>
1616
get() = sequenceOf(
1717
aMatrixUser(),
18-
aMatrixUser().copy(displayName = null),
18+
aMatrixUser(displayName = null),
19+
)
20+
}
21+
22+
open class MatrixUserWithNullProvider : PreviewParameterProvider<MatrixUser?> {
23+
override val values: Sequence<MatrixUser?>
24+
get() = sequenceOf(
25+
aMatrixUser(),
26+
aMatrixUser(displayName = null),
27+
null,
28+
)
29+
}
30+
31+
open class MatrixUserWithAvatarProvider : PreviewParameterProvider<MatrixUser?> {
32+
override val values: Sequence<MatrixUser?>
33+
get() = sequenceOf(
34+
aMatrixUser(displayName = "John Doe"),
35+
aMatrixUser(displayName = "John Doe", avatarUrl = "anUrl"),
1936
)
2037
}
2138

@@ -41,12 +58,3 @@ fun aMatrixUserList() = listOf(
4158
aMatrixUser("@victor:server.org", "Victor"),
4259
aMatrixUser("@walter:server.org", "Walter"),
4360
)
44-
45-
open class MatrixUserWithNullProvider : PreviewParameterProvider<MatrixUser?> {
46-
override val values: Sequence<MatrixUser?>
47-
get() = sequenceOf(
48-
aMatrixUser(),
49-
aMatrixUser().copy(displayName = null),
50-
null,
51-
)
52-
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ fun MatrixUserRow(
2828
name = matrixUser.getBestName(),
2929
subtext = if (matrixUser.displayName.isNullOrEmpty()) null else matrixUser.userId.value,
3030
modifier = modifier,
31-
trailingContent,
31+
trailingContent = trailingContent,
3232
)
3333

3434
@PreviewsDayNight

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class SelectRoomInfoProvider : PreviewParameterProvider<SelectRoomInfo> {
2020
get() = sequenceOf(
2121
aSelectRoomInfo(roomId = RoomId("!room1:domain")),
2222
aSelectRoomInfo(roomId = RoomId("!room2:domain"), name = "Room with a name"),
23-
aSelectRoomInfo(roomId = RoomId("!room3:domain"), name = "Room with a name and alias", canonicalAlias = RoomAlias("#alias:domain")),
23+
aSelectRoomInfo(roomId = RoomId("!room3:domain"), name = "Room with a name and avatar", avatarUrl = "anUrl"),
2424
)
2525
}
2626

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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.clickable
11+
import androidx.compose.foundation.interaction.MutableInteractionSource
12+
import androidx.compose.foundation.layout.Box
13+
import androidx.compose.foundation.layout.Column
14+
import androidx.compose.foundation.layout.padding
15+
import androidx.compose.foundation.layout.size
16+
import androidx.compose.foundation.layout.width
17+
import androidx.compose.foundation.shape.CircleShape
18+
import androidx.compose.material3.MaterialTheme
19+
import androidx.compose.material3.ripple
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.remember
22+
import androidx.compose.ui.Alignment
23+
import androidx.compose.ui.Modifier
24+
import androidx.compose.ui.draw.clip
25+
import androidx.compose.ui.draw.clipToBounds
26+
import androidx.compose.ui.draw.drawWithContent
27+
import androidx.compose.ui.geometry.Offset
28+
import androidx.compose.ui.graphics.BlendMode
29+
import androidx.compose.ui.graphics.Color
30+
import androidx.compose.ui.graphics.CompositingStrategy
31+
import androidx.compose.ui.graphics.graphicsLayer
32+
import androidx.compose.ui.platform.LocalLayoutDirection
33+
import androidx.compose.ui.res.stringResource
34+
import androidx.compose.ui.semantics.clearAndSetSemantics
35+
import androidx.compose.ui.semantics.contentDescription
36+
import androidx.compose.ui.semantics.onClick
37+
import androidx.compose.ui.text.style.TextAlign
38+
import androidx.compose.ui.text.style.TextOverflow
39+
import androidx.compose.ui.unit.LayoutDirection
40+
import androidx.compose.ui.unit.dp
41+
import io.element.android.compound.theme.ElementTheme
42+
import io.element.android.compound.tokens.generated.CompoundIcons
43+
import io.element.android.libraries.designsystem.components.avatar.Avatar
44+
import io.element.android.libraries.designsystem.components.avatar.AvatarData
45+
import io.element.android.libraries.designsystem.components.avatar.AvatarType
46+
import io.element.android.libraries.designsystem.text.toPx
47+
import io.element.android.libraries.designsystem.theme.components.Icon
48+
import io.element.android.libraries.designsystem.theme.components.Surface
49+
import io.element.android.libraries.designsystem.theme.components.Text
50+
import io.element.android.libraries.ui.strings.CommonStrings
51+
52+
@Composable
53+
fun SelectedItem(
54+
avatarData: AvatarData,
55+
avatarType: AvatarType,
56+
text: String,
57+
maxLines: Int,
58+
a11yContentDescription: String,
59+
canRemove: Boolean,
60+
onRemoveClick: () -> Unit,
61+
modifier: Modifier = Modifier,
62+
) {
63+
val actionRemove = stringResource(id = CommonStrings.action_remove)
64+
Box(
65+
modifier = modifier
66+
.width(avatarData.size.dp)
67+
.clearAndSetSemantics {
68+
contentDescription = a11yContentDescription
69+
if (canRemove) {
70+
// Note: this does not set the click effect to the whole Box
71+
// when talkback is not enabled
72+
onClick(
73+
label = actionRemove,
74+
action = {
75+
onRemoveClick()
76+
true
77+
}
78+
)
79+
}
80+
}
81+
) {
82+
Column(
83+
horizontalAlignment = Alignment.CenterHorizontally,
84+
) {
85+
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
86+
val closeIconRadius = 12.dp.toPx()
87+
val closeIconOffset = 10.dp.toPx()
88+
Avatar(
89+
avatarData = avatarData,
90+
avatarType = avatarType,
91+
modifier = Modifier
92+
.graphicsLayer {
93+
compositingStrategy = CompositingStrategy.Offscreen
94+
}
95+
.drawWithContent {
96+
drawContent()
97+
if (canRemove) {
98+
val xOffset = if (isRtl) {
99+
closeIconOffset
100+
} else {
101+
size.width - closeIconOffset
102+
}
103+
drawCircle(
104+
color = Color.Black,
105+
center = Offset(
106+
x = xOffset,
107+
y = closeIconOffset,
108+
),
109+
radius = closeIconRadius,
110+
blendMode = BlendMode.Clear,
111+
)
112+
}
113+
},
114+
)
115+
Text(
116+
modifier = Modifier.clipToBounds(),
117+
text = text,
118+
overflow = TextOverflow.Ellipsis,
119+
maxLines = maxLines,
120+
style = MaterialTheme.typography.bodyMedium,
121+
color = ElementTheme.colors.textSecondary,
122+
textAlign = TextAlign.Center,
123+
)
124+
}
125+
if (canRemove) {
126+
Surface(
127+
color = ElementTheme.colors.bgActionPrimaryRest,
128+
modifier = Modifier
129+
.clip(CircleShape)
130+
.size(20.dp)
131+
.align(Alignment.TopEnd)
132+
.clickable(
133+
indication = ripple(),
134+
interactionSource = remember { MutableInteractionSource() },
135+
onClick = onRemoveClick,
136+
),
137+
) {
138+
Icon(
139+
imageVector = CompoundIcons.Close(),
140+
// Note: keep the context description for the test
141+
contentDescription = stringResource(id = CommonStrings.action_remove),
142+
tint = ElementTheme.colors.iconOnSolidPrimary,
143+
modifier = Modifier.padding(2.dp)
144+
)
145+
}
146+
}
147+
}
148+
}

0 commit comments

Comments
 (0)