A11Y: improve accessibility on event reactions.

This commit is contained in:
Benoit Marty 2025-06-13 15:05:42 +02:00 committed by Benoit Marty
parent c598b0699e
commit 4ed12bb2d3
4 changed files with 77 additions and 4 deletions

View file

@ -28,7 +28,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -47,6 +51,7 @@ import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.ui.media.MediaRequestData
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
@Suppress("ModifierClickableOrder") // This is needed to display the right ripple shape
@ -68,6 +73,47 @@ fun MessagesReactionButton(
buttonColor
}
val a11yText = when (content) {
is MessagesReactionsButtonContent.Icon -> stringResource(id = R.string.screen_room_timeline_add_reaction)
is MessagesReactionsButtonContent.Text -> content.text
is MessagesReactionsButtonContent.Reaction -> {
val reaction = if (content.reaction.key.startsWith("mxc://")) {
stringResource(CommonStrings.common_an_image)
} else {
content.reaction.key
}
if (content.isHighlighted) {
if (content.reaction.count == 1) {
stringResource(R.string.screen_room_timeline_reaction_you_a11y, reaction)
} else {
pluralStringResource(
R.plurals.screen_room_timeline_reaction_including_you_a11y,
content.reaction.count - 1,
content.reaction.count - 1,
reaction,
)
}
} else {
pluralStringResource(
R.plurals.screen_room_timeline_reaction_a11y,
content.reaction.count,
content.reaction.count,
reaction,
)
}
}
}
val a11yClickLabel = if (content is MessagesReactionsButtonContent.Reaction) {
if (content.isHighlighted) {
stringResource(id = CommonStrings.a11y_remove_reaction_with, content.reaction.key)
} else {
stringResource(id = CommonStrings.a11y_react_with, content.reaction.key)
}
} else {
""
}
Surface(
modifier = modifier
.background(Color.Transparent)
@ -86,7 +132,18 @@ fun MessagesReactionButton(
// Inner border, to highlight when selected
.border(BorderStroke(1.dp, borderColor), RoundedCornerShape(corner = CornerSize(12.dp)))
.background(buttonColor, RoundedCornerShape(corner = CornerSize(12.dp)))
.padding(vertical = 4.dp, horizontal = 10.dp),
.padding(vertical = 4.dp, horizontal = 10.dp)
.clearAndSetSemantics {
contentDescription = a11yText
if (content is MessagesReactionsButtonContent.Reaction) {
onClick(
label = a11yClickLabel
) {
onClick()
true
}
}
},
color = buttonColor
) {
when (content) {

View file

@ -28,13 +28,22 @@
<string name="screen_room_mentions_at_room_title">"Everyone"</string>
<string name="screen_room_retry_send_menu_send_again_action">"Send again"</string>
<string name="screen_room_retry_send_menu_title">"Your message failed to send"</string>
<string name="screen_room_timeline_add_reaction">"Add emoji"</string>
<string name="screen_room_timeline_add_reaction">"Add a reaction"</string>
<string name="screen_room_timeline_beginning_of_room">"This is the beginning of %1$s."</string>
<string name="screen_room_timeline_beginning_of_room_no_name">"This is the beginning of this conversation."</string>
<string name="screen_room_timeline_legacy_call">"Unsupported call. Ask if the caller can use the new Element X app."</string>
<string name="screen_room_timeline_less_reactions">"Show less"</string>
<string name="screen_room_timeline_message_copied">"Message copied"</string>
<string name="screen_room_timeline_no_permission_to_post">"You do not have permission to post to this room"</string>
<plurals name="screen_room_timeline_reaction_a11y">
<item quantity="one">"%1$d member reacted with %2$s"</item>
<item quantity="other">"%1$d members reacted with %2$s"</item>
</plurals>
<plurals name="screen_room_timeline_reaction_including_you_a11y">
<item quantity="one">"You and %1$d member reacted with %2$s"</item>
<item quantity="other">"You and %1$d members reacted with %2$s"</item>
</plurals>
<string name="screen_room_timeline_reaction_you_a11y">"You reacted with %1$s"</string>
<string name="screen_room_timeline_reactions_show_less">"Show less"</string>
<string name="screen_room_timeline_reactions_show_more">"Show more"</string>
<string name="screen_room_timeline_read_marker_title">"New"</string>

View file

@ -391,7 +391,10 @@ class MessagesViewTest {
rule.setMessagesView(
state = state,
)
rule.onAllNodesWithText("👍️").onFirst().performClick()
rule.onAllNodesWithText(
text = "👍️",
useUnmergedTree = true,
).onFirst().performClick()
eventsRecorder.assertSingle(MessagesEvents.ToggleReaction("👍️", timelineItem.eventOrTransactionId))
}
@ -411,7 +414,10 @@ class MessagesViewTest {
rule.setMessagesView(
state = state,
)
rule.onAllNodesWithText("👍️").onFirst().performTouchInput { longClick() }
rule.onAllNodesWithText(
text = "👍️",
useUnmergedTree = true,
).onFirst().performTouchInput { longClick() }
eventsRecorder.assertSingle(ReactionSummaryEvents.ShowReactionSummary(timelineItem.eventId!!, timelineItem.reactionsState.reactions, "👍️"))
}

View file

@ -144,6 +144,7 @@
<string name="common_acceptable_use_policy">"Acceptable use policy"</string>
<string name="common_adding_caption">"Adding caption"</string>
<string name="common_advanced_settings">"Advanced settings"</string>
<string name="common_an_image">"an image"</string>
<string name="common_analytics">"Analytics"</string>
<string name="common_appearance">"Appearance"</string>
<string name="common_audio">"Audio"</string>