diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index b0be2dde88..99e72f4973 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -49,7 +49,9 @@ import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.ViewConfiguration import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.CustomAccessibilityAction import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.semantics.isTraversalGroup import androidx.compose.ui.semantics.semantics @@ -217,13 +219,23 @@ fun TimelineItemEventRow( inReplyToClick(inReplyToEventId) } - Column(modifier = modifier.fillMaxWidth()) { + val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.canBeRepliedTo + val accessibilityActions = rememberTimelineItemAccessibilityActions( + canReply = canReply, + onLongClick = onLongClick, + onSwipeToReply = onSwipeToReply, + ) + + Column( + modifier = modifier + .fillMaxWidth() + .semantics { customActions = accessibilityActions } + ) { if (event.groupPosition.isNew()) { Spacer(modifier = Modifier.height(16.dp)) } else { Spacer(modifier = Modifier.height(2.dp)) } - val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.canBeRepliedTo if (canReply) { val state: SwipeableActionsState = rememberSwipeableActionsState() val offset = state.offset.floatValue @@ -319,6 +331,40 @@ fun TimelineItemEventRow( } } +/** + * Exposes the gestures of a timeline item — long press for the action list, swipe for a reply — as TalkBack actions, since neither gesture is reachable + * with a screen reader enabled. + */ +@Composable +private fun rememberTimelineItemAccessibilityActions( + canReply: Boolean, + onLongClick: () -> Unit, + onSwipeToReply: () -> Unit, +): List { + val messageActionsLabel = stringResource(CommonStrings.common_message_actions) + val replyLabel = stringResource(CommonStrings.action_reply) + val latestOnLongClick by rememberUpdatedState(onLongClick) + val latestOnSwipeToReply by rememberUpdatedState(onSwipeToReply) + return remember(canReply, messageActionsLabel, replyLabel) { + buildList { + add( + CustomAccessibilityAction(messageActionsLabel) { + latestOnLongClick() + true + } + ) + if (canReply) { + add( + CustomAccessibilityAction(replyLabel) { + latestOnSwipeToReply() + true + } + ) + } + } + } +} + @Composable private fun ThreadSummaryView( threadSummary: ThreadSummary, diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesViewA11y_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesViewA11y_en.png index 3c357233b9..4bd7d43a53 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesViewA11y_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesViewA11y_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95c5746de02c584536b1178614d15460bbdda633ddf44485261007618bd99f50 -size 132268 +oid sha256:b494272032ad9af988e41736a0a0f6aa101bdd9c185bd685085ccefd0a6427a6 +size 138254