Skip to content

Commit 5dfcd23

Browse files
committed
Create common SelectedItem composable.
1 parent 3c68dd8 commit 5dfcd23

File tree

3 files changed

+174
-234
lines changed

3 files changed

+174
-234
lines changed
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+
}

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

Lines changed: 16 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,17 @@
77

88
package io.element.android.libraries.matrix.ui.components
99

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
2010
import androidx.compose.runtime.Composable
2111
import androidx.compose.runtime.CompositionLocalProvider
22-
import androidx.compose.runtime.remember
23-
import androidx.compose.ui.Alignment
2412
import androidx.compose.ui.Modifier
25-
import androidx.compose.ui.draw.clip
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
3213
import androidx.compose.ui.platform.LocalLayoutDirection
3314
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.TextOverflow
3815
import androidx.compose.ui.tooling.preview.PreviewParameter
3916
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
4417
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
4518
import io.element.android.libraries.designsystem.components.avatar.AvatarType
4619
import io.element.android.libraries.designsystem.preview.ElementPreview
4720
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
48-
import io.element.android.libraries.designsystem.text.toPx
49-
import io.element.android.libraries.designsystem.theme.components.Icon
50-
import io.element.android.libraries.designsystem.theme.components.Surface
51-
import io.element.android.libraries.designsystem.theme.components.Text
5221
import io.element.android.libraries.matrix.ui.model.SelectRoomInfo
5322
import io.element.android.libraries.matrix.ui.model.getAvatarData
5423
import io.element.android.libraries.ui.strings.CommonStrings
@@ -60,89 +29,22 @@ fun SelectedRoom(
6029
onRemoveRoom: (SelectRoomInfo) -> Unit,
6130
modifier: Modifier = Modifier,
6231
) {
63-
val actionRemove = stringResource(id = CommonStrings.action_remove)
64-
val a11yRoomName = stringResource(id = CommonStrings.common_room_name)
65-
Box(
66-
modifier = modifier
67-
.width(AvatarSize.SelectedRoom.dp)
68-
.clearAndSetSemantics {
69-
contentDescription = roomInfo.name
70-
?: roomInfo.canonicalAlias?.value
71-
?: a11yRoomName
72-
// Note: this does not set the click effect to the whole Box
73-
// when talkback is not enabled
74-
onClick(
75-
label = actionRemove,
76-
action = {
77-
onRemoveRoom(roomInfo)
78-
true
79-
}
80-
)
81-
}
82-
) {
83-
Column(
84-
horizontalAlignment = Alignment.CenterHorizontally,
85-
) {
86-
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
87-
val closeIconRadius = 12.dp.toPx()
88-
val closeIconOffset = 10.dp.toPx()
89-
Avatar(
90-
modifier = Modifier
91-
.graphicsLayer {
92-
compositingStrategy = CompositingStrategy.Offscreen
93-
}
94-
.drawWithContent {
95-
drawContent()
96-
val xOffset = if (isRtl) {
97-
closeIconOffset
98-
} else {
99-
size.width - closeIconOffset
100-
}
101-
drawCircle(
102-
color = Color.Black,
103-
center = Offset(
104-
x = xOffset,
105-
y = closeIconOffset,
106-
),
107-
radius = closeIconRadius,
108-
blendMode = BlendMode.Clear,
109-
)
110-
},
111-
avatarData = roomInfo.getAvatarData(AvatarSize.SelectedRoom),
112-
avatarType = AvatarType.Room(
113-
heroes = roomInfo.heroes.map { it.getAvatarData(AvatarSize.SelectedRoom) }.toImmutableList(),
114-
isTombstoned = roomInfo.isTombstoned,
115-
),
116-
)
117-
Text(
118-
// If name is null, we do not have space to render "No room name", so just use `#` here.
119-
text = roomInfo.name ?: "#",
120-
overflow = TextOverflow.Ellipsis,
121-
maxLines = 1,
122-
style = MaterialTheme.typography.bodyMedium,
123-
color = ElementTheme.colors.textSecondary,
124-
)
125-
}
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 = { onRemoveRoom(roomInfo) }
136-
),
137-
) {
138-
Icon(
139-
imageVector = CompoundIcons.Close(),
140-
contentDescription = stringResource(id = CommonStrings.action_remove),
141-
tint = ElementTheme.colors.iconOnSolidPrimary,
142-
modifier = Modifier.padding(2.dp)
143-
)
144-
}
145-
}
32+
SelectedItem(
33+
avatarData = roomInfo.getAvatarData(AvatarSize.SelectedRoom),
34+
avatarType = AvatarType.Room(
35+
heroes = roomInfo.heroes.map { it.getAvatarData(AvatarSize.SelectedRoom) }.toImmutableList(),
36+
isTombstoned = roomInfo.isTombstoned,
37+
),
38+
// If name is null, we do not have space to render "No room name", so just use `#` here.
39+
text = roomInfo.name ?: "#",
40+
maxLines = 1,
41+
a11yContentDescription = roomInfo.name
42+
?: roomInfo.canonicalAlias?.value
43+
?: stringResource(id = CommonStrings.common_room_name),
44+
canRemove = true,
45+
onRemoveClick = { onRemoveRoom(roomInfo) },
46+
modifier = modifier,
47+
)
14648
}
14749

14850
@PreviewsDayNight

0 commit comments

Comments
 (0)