From 54d452c364f16f51223259d4dda0340102a60802 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 1 Jul 2026 17:55:36 +0200 Subject: [PATCH 1/4] Fix accessibility: remove TalkBack reverseLayout workaround (#6392) The LazyColumn was using reverseLayout=false when TalkBack was active to work around a Compose accessibility traversal bug. This placed new messages at the top of the screen, distorting the reading order for TalkBack users. With Compose BOM 2026.06.00, reverseLayout=true now works correctly with TalkBack traversal, so remove both workarounds: - Always use reverseLayout=true in TimelineView - Remove the .reversed() for expanded groups in TimelineItemGroupedEventsRow Note: this change should be manually tested with TalkBack to verify correct traversal order (oldest messages first, newest last). Co-Authored-By: Claude Sonnet 4.6 --- .../features/messages/impl/timeline/TimelineView.kt | 6 +----- .../timeline/components/TimelineItemGroupedEventsRow.kt | 9 +-------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 0a306aea1c..3b870ef839 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -77,7 +77,6 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag import io.element.android.libraries.ui.strings.CommonStrings -import io.element.android.libraries.ui.utils.a11y.isTalkbackActive import io.element.android.wysiwyg.link.Link import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest @@ -128,9 +127,6 @@ fun TimelineView( val context = LocalContext.current val toastMessage = stringResource(CommonStrings.common_copied_to_clipboard) val view = LocalView.current - // Disable reverse layout when TalkBack is enabled to avoid incorrect ordering issues seen in the current Compose UI version - val useReverseLayout = !isTalkbackActive() - fun inReplyToClick(eventId: EventId) { state.eventSink(TimelineEvent.FocusOnEvent(eventId)) } @@ -158,7 +154,7 @@ fun TimelineView( .nestedScroll(nestedScrollConnection) .testTag(TestTags.timeline), state = lazyListState, - reverseLayout = useReverseLayout, + reverseLayout = true, contentPadding = PaddingValues(top = 64.dp, bottom = 8.dp), ) { items( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index b41e66ae82..4a200f01f7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -34,7 +34,6 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.ui.utils.a11y.isTalkbackActive import io.element.android.wysiwyg.link.Link @Composable @@ -158,13 +157,7 @@ private fun TimelineItemGroupedEventsRowContent( ) if (isExpanded) { Column { - timelineItem.events.let { - if (isTalkbackActive()) { - it.reversed() - } else { - it - } - }.forEach { subGroupEvent -> + timelineItem.events.forEach { subGroupEvent -> TimelineItemRow( timelineMode = timelineMode, timelineItem = subGroupEvent, From 6181f60b5e70471ee8ac02624ce51b950e27dde1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 2 Jul 2026 16:12:54 +0200 Subject: [PATCH 2/4] Fix compilation issue --- .../messages/impl/timeline/TimelineView.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 3b870ef839..ea3dd27a3a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -208,14 +208,12 @@ fun TimelineView( onFocusEventRender = ::onFocusEventRender, ) - if (useReverseLayout) { - FloatingDateBadgeOverlay( - lazyListState = lazyListState, - timelineItems = state.timelineItems, - isLive = state.isLive, - topOffset = floatingDateTopOffset, - ) - } + FloatingDateBadgeOverlay( + lazyListState = lazyListState, + timelineItems = state.timelineItems, + isLive = state.isLive, + topOffset = floatingDateTopOffset, + ) } } From 60837c0d7d6c258ac87cd8f548f53f0f1e07abca Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 3 Jul 2026 09:24:42 +0200 Subject: [PATCH 3/4] Hide floating badge date from accessibility --- .../messages/impl/timeline/components/FloatingDateBadge.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/FloatingDateBadge.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/FloatingDateBadge.kt index 996bb07b81..5946798a76 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/FloatingDateBadge.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/FloatingDateBadge.kt @@ -27,6 +27,8 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme @@ -102,7 +104,10 @@ internal fun BoxScope.FloatingDateBadgeOverlay( visible = showBadge, modifier = Modifier .align(Alignment.TopCenter) - .padding(top = 8.dp + topOffset), + .padding(top = 8.dp + topOffset) + .clearAndSetSemantics { + hideFromAccessibility() + }, enter = fadeIn(animationSpec = tween(150)), exit = fadeOut(animationSpec = tween(300)), ) { From b6f79a883e74c94150c928014f9477ef41a2ace3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 3 Jul 2026 12:21:24 +0200 Subject: [PATCH 4/4] Improve accessibility text on RR --- .../receipt/TimelineItemReadReceiptView.kt | 14 +++++++++----- .../ui-strings/src/main/res/values/localazy.xml | 9 +++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt index 3ccf32d422..c9b712eba7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt @@ -61,10 +61,7 @@ fun TimelineItemReadReceiptView( ReadReceiptsRow(modifier = modifier) { ReadReceiptsAvatars( receipts = state.receipts, - modifier = Modifier - .clip(RoundedCornerShape(4.dp)) - .clickable(onClick = onReadReceiptsClick) - .padding(2.dp) + onClick = onReadReceiptsClick, ) } } else { @@ -124,7 +121,8 @@ private fun ReadReceiptsRow( @Composable private fun ReadReceiptsAvatars( receipts: ImmutableList, - modifier: Modifier = Modifier + onClick: () -> Unit, + modifier: Modifier = Modifier, ) { val avatarSize = AvatarSize.TimelineReadReceipt.dp val avatarStrokeSize = 1.dp @@ -132,6 +130,12 @@ private fun ReadReceiptsAvatars( val receiptDescription = computeReceiptDescription(receipts) Row( modifier = modifier + .clip(RoundedCornerShape(4.dp)) + .clickable( + onClick = onClick, + onClickLabel = stringResource(id = CommonStrings.a11y_read_receipts_tap_to_show_all_android), + ) + .padding(2.dp) .clearAndSetSemantics { testTag = TestTags.messageReadReceipts.value contentDescription = receiptDescription diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 4885c38ef3..82acfd14b3 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -44,13 +44,14 @@ "QR Code" "React with %1$s" "React with other emojis" - "Read by %1$s and %2$s" + "Previous message read by %1$s and %2$s" - "Read by %1$s and %2$d other" - "Read by %1$s and %2$d others" + "Previous message read by %1$s and %2$d other" + "Previous message read by %1$s and %2$d others" - "Read by %1$s" + "Previous message read by %1$s" "Tap to show all" + "show all read receipts" "Remove reaction: %1$s" "Remove reaction with %1$s" "Room avatar"