[a11y] Add content descriptions to room list item indicators (#5236)

* [a11y] Add content descriptions to room list item indicators. These can now be read aloud as 'ongoing call', 'new messages', 'new mentions'.

* Add `contentDescription` to `UnreadIndicatorAtom` as an optional value

* Make the 'ongoing call', 'new messages', etc. indicators be read aloud before the latest event of the room summary

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Jorge Martin Espinosa 2025-08-27 17:14:59 +02:00 committed by GitHub
parent dddc557e54
commit ace078f12e
3 changed files with 20 additions and 7 deletions

View file

@ -35,6 +35,7 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.home.impl.R import io.element.android.features.home.impl.R
@ -285,9 +286,13 @@ private fun MessagePreviewAndIndicatorRow(
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
// Call and unread // Call and unread
Row( Row(
modifier = Modifier.height(16.dp), modifier = Modifier
.height(16.dp)
// Used to force this line to be read aloud earlier than the latest event when using Talkback
.zIndex(-1f),
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@ -303,8 +308,10 @@ private fun MessagePreviewAndIndicatorRow(
MentionIndicatorAtom() MentionIndicatorAtom()
} }
if (room.hasNewContent) { if (room.hasNewContent) {
val contentDescription = stringResource(CommonStrings.a11y_notifications_new_messages)
UnreadIndicatorAtom( UnreadIndicatorAtom(
color = tint color = tint,
contentDescription = contentDescription,
) )
} }
} }
@ -371,7 +378,7 @@ private fun OnGoingCallIcon(
Icon( Icon(
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
imageVector = CompoundIcons.VideoCallSolid(), imageVector = CompoundIcons.VideoCallSolid(),
contentDescription = null, contentDescription = stringResource(CommonStrings.a11y_notifications_ongoing_call),
tint = color, tint = color,
) )
} }
@ -380,7 +387,7 @@ private fun OnGoingCallIcon(
private fun NotificationOffIndicatorAtom() { private fun NotificationOffIndicatorAtom() {
Icon( Icon(
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
contentDescription = null, contentDescription = stringResource(CommonStrings.a11y_notifications_muted),
imageVector = CompoundIcons.NotificationsOffSolid(), imageVector = CompoundIcons.NotificationsOffSolid(),
tint = ElementTheme.colors.iconQuaternary, tint = ElementTheme.colors.iconQuaternary,
) )
@ -390,7 +397,7 @@ private fun NotificationOffIndicatorAtom() {
private fun MentionIndicatorAtom() { private fun MentionIndicatorAtom() {
Icon( Icon(
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
contentDescription = null, contentDescription = stringResource(CommonStrings.a11y_notifications_new_mentions),
imageVector = CompoundIcons.Mention(), imageVector = CompoundIcons.Mention(),
tint = ElementTheme.colors.unreadIndicator, tint = ElementTheme.colors.unreadIndicator,
) )

View file

@ -15,6 +15,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
@ -28,9 +30,13 @@ fun UnreadIndicatorAtom(
size: Dp = 12.dp, size: Dp = 12.dp,
color: Color = ElementTheme.colors.unreadIndicator, color: Color = ElementTheme.colors.unreadIndicator,
isVisible: Boolean = true, isVisible: Boolean = true,
contentDescription: String? = null,
) { ) {
Box( Box(
modifier = modifier modifier = modifier
.semantics {
contentDescription?.let { this.contentDescription = it }
}
.size(size) .size(size)
.clip(CircleShape) .clip(CircleShape)
.background(if (isVisible) color else Color.Transparent) .background(if (isVisible) color else Color.Transparent)

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:21c09cac237b2b4823dbb945161c6d6d574dc2c8ffb37088696d09736d8e782a oid sha256:a666f932d1a595763b3c88b31ffbe3d195ec0d986ca5f2c0173e8e25a6115317
size 124636 size 126085