From 38398c24b2fbfc0c9d7617b9e6064d4321977bfa Mon Sep 17 00:00:00 2001 From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com> Date: Sun, 9 Aug 2026 15:19:45 +0300 Subject: [PATCH 1/3] Show a Message not found placeholder when a replied to message cannot be loaded When the replied to event could not be fetched, the reply preview rendered the raw SDK error string in critical styling, and tapping it emitted a focus request that could never succeed, so the user got a dead end dialog. Render the localised error_message_not_found string in the neutral italic style already used for redacted and undecryptable replies, and drop the click handler for that case. The existing key is reused rather than adding a new one; a "you do not have access" wording was rejected because this state covers any failure to load the event, not only a permission problem. --- .../components/TimelineItemEventRow.kt | 8 +-- .../impl/timeline/TimelineViewTest.kt | 65 +++++++++++++++++++ .../matrix/ui/messages/reply/InReplyToView.kt | 8 +-- 3 files changed, 73 insertions(+), 8 deletions(-) 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 fed2ab6577..f4ffc9c205 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 @@ -804,12 +804,12 @@ private fun MessageEventBubbleContent( .padding(top = topPadding, start = 8.dp, end = 8.dp) .clip(shape) - val talkbackCompatModifier = if (isTalkbackActive()) { + val talkbackCompatModifier = when { // Use z-index to make the replied to text being read after the message // Usually, you'd use traversalIndex for that, but it's not working for some reason - inReplyToModifier.zIndex(1f) - } else { - inReplyToModifier.clickable(onClick = inReplyToClick) + isTalkbackActive() -> inReplyToModifier.zIndex(1f) + inReplyTo is InReplyToDetails.Error -> inReplyToModifier + else -> inReplyToModifier.clickable(onClick = inReplyToClick) } val contentHasError = currentContentValidationState.hasError() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index bcddc37a74..f733f116d6 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -13,8 +13,12 @@ package io.element.android.features.messages.impl.timeline import androidx.activity.ComponentActivity import androidx.compose.ui.test.AndroidComposeUiTest import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToIndex import androidx.compose.ui.test.v2.runAndroidComposeUiTest @@ -29,8 +33,14 @@ import io.element.android.features.messages.impl.timeline.protection.aTimelinePr import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield +import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails +import io.element.android.libraries.matrix.ui.messages.reply.aProfileDetailsReady import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams @@ -168,6 +178,61 @@ class TimelineViewTest : RobolectricTest() { eventsRecorder.assertSingle(TimelineEvent.HideShieldDialog) } + @Test + fun `clicking on a reply to a loaded event emits the expected Event`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() + setTimelineView( + state = aTimelineState( + timelineItems = persistentListOf( + aTimelineItemEvent( + content = aTimelineItemTextContent(), + inReplyTo = InReplyToDetails.Ready( + eventId = AN_EVENT_ID, + senderId = A_USER_ID, + senderProfile = aProfileDetailsReady(), + eventContent = MessageContent( + body = "Replied message", + inReplyTo = null, + isEdited = false, + threadInfo = null, + type = TextMessageType("Replied message", null), + ), + textContent = "Replied message", + ), + ), + ), + eventSink = eventsRecorder, + ), + ) + eventsRecorder.clear() + + onAllNodesWithText("Replied message").onFirst().performClick() + eventsRecorder.assertSingle(TimelineEvent.FocusOnEvent(AN_EVENT_ID)) + } + + @Test + fun `a reply to an event which could not be loaded renders a placeholder and does not emit any Event when clicked`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() + setTimelineView( + state = aTimelineState( + timelineItems = persistentListOf( + aTimelineItemEvent( + content = aTimelineItemTextContent(), + inReplyTo = InReplyToDetails.Error(eventId = AN_EVENT_ID, message = "Failed to fetch the event"), + ), + ), + eventSink = eventsRecorder, + ), + onMessageClick = {}, + ) + eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0)) + eventsRecorder.clear() + + onNodeWithText("Failed to fetch the event").assertDoesNotExist() + onNodeWithText(activity!!.getString(CommonStrings.error_message_not_found)).assertIsDisplayed().performClick() + eventsRecorder.assertEmpty() + } + @Ignore( "performScrollToIndex in compose tests no longer sets LazyListState.isScrollInProgress to true, so the LoadMore event is not emitted." + "This needs to be reworked to use a different approach to check the LoadMore event was emitted." diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt index 3072c81a15..691031fcbe 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt @@ -77,7 +77,7 @@ fun InReplyToView( else -> ReplyToLoadingContent(modifier = modifier) } is InReplyToDetails.Error -> - ReplyToErrorContent(data = inReplyTo, maxLines = maxLines, modifier = modifier) + ReplyToErrorContent(maxLines = maxLines, modifier = modifier) is InReplyToDetails.Loading -> ReplyToLoadingContent(modifier = modifier) } @@ -150,7 +150,6 @@ private fun ReplyToLoadingContent( @Composable private fun ReplyToErrorContent( - data: InReplyToDetails.Error, maxLines: Int, modifier: Modifier = Modifier, ) { @@ -161,9 +160,10 @@ private fun ReplyToErrorContent( .padding(paddings) ) { Text( - text = data.message, + text = stringResource(id = CommonStrings.error_message_not_found), style = ElementTheme.typography.fontBodyMdRegular, - color = ElementTheme.colors.textCriticalPrimary, + fontStyle = FontStyle.Italic, + color = ElementTheme.colors.textSecondary, maxLines = maxLines, overflow = TextOverflow.Ellipsis, ) From 798936111480300fa14cd42b9ed6eabf52f8a55b Mon Sep 17 00:00:00 2001 From: ElementBot Date: Sun, 9 Aug 2026 12:41:41 +0000 Subject: [PATCH 2/3] Update screenshots --- ...components_TimelineItemEventRowWithReplyOther_Day_1_en.png | 4 ++-- ...mponents_TimelineItemEventRowWithReplyOther_Night_1_en.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png index 7ddac02717..592e6327d6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4ac79f3b8fb4e231244bd96b244a4f2526d6ed28d558f9d50734d83bbb74992 -size 363909 +oid sha256:4c959c0a4f5890f5a04f911cc395816c23c5abce5c9baf1e569c302ff43206d5 +size 366838 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png index 4e17de6908..6761490630 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1e15400fa2c5ee6087316501445c551a3932b65e16de31e68acc5efbd16a559 -size 361603 +oid sha256:eb545091ad22e1c31c722d35a6492240c5a1fa66480613e37fc9c3817d8244a7 +size 364697 From aa7b9b87212a6d735d72664b1757e84ebcca5292 Mon Sep 17 00:00:00 2001 From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com> Date: Sun, 9 Aug 2026 15:47:29 +0300 Subject: [PATCH 3/3] Trigger CI after recording screenshots