From f9112cc583f56c85ece8b5c07cf07144783d452b Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Mon, 13 Jul 2026 15:57:55 +0200 Subject: [PATCH] Collapse runs of deleted messages in the timeline (#7195) * Collapse runs of deleted messages in the timeline (#7111) * Collapse runs of deleted messages in the timeline Collapse a run of three or more consecutive deleted messages into a single expandable group, reusing the existing state-change grouping (the same way element-web does it) instead of showing one "Message removed" placeholder per deletion. The header shows the count only ("N deleted messages"); the Rust SDK does not expose who performed the redaction, so we don't attribute it. On by default. Runs shorter than three are left as individual placeholders, and day dividers and read receipts are preserved (a run is broken by any non-redacted item, so a day is never emptied). Run the collapse as the final step of TimelineItemGrouper.group() instead of a separate pass in the presenter, as suggested in review. Kept as its own finalization step rather than via canBeGrouped() so the group stays all redacted and the count-only header is preserved. The group id is resolved through the grouper's existing id registry, like the other groups, so it stays stable however the run grows and the user's expand/collapse state is kept across timeline updates. * Replace temporary strings with final ones * Update screenshots --------- Co-authored-by: Hi Dude! Co-authored-by: manfrommedan Co-authored-by: ElementBot --- .../impl/timeline/TimelineStateProvider.kt | 18 ++ .../TimelineItemGroupedEventsRow.kt | 46 ++++- .../impl/timeline/groups/Groupability.kt | 8 + .../timeline/groups/TimelineItemGrouper.kt | 57 +++++- .../impl/src/main/res/values/localazy.xml | 4 + .../impl/timeline/CollapseRedactedRunsTest.kt | 183 ++++++++++++++++++ .../impl/timeline/TimelinePresenterTest.kt | 23 +++ .../impl/timeline/groups/GroupabilityTest.kt | 60 ++++++ .../groups/TimelineItemGrouperTest.kt | 28 +++ .../tests/konsist/KonsistPreviewTest.kt | 1 + ...omreaction_EmojiItemWithPopup_Day_0_en.png | 4 +- ...reaction_EmojiItemWithPopup_Night_0_en.png | 4 +- ...s.event_ATimelineItemEventRow_Day_0_en.png | 4 +- ...event_ATimelineItemEventRow_Night_0_en.png | 4 +- ...t_TimelineImageWithCaptionRow_Day_0_en.png | 4 +- ...TimelineImageWithCaptionRow_Night_0_en.png | 4 +- ...t_TimelineVideoWithCaptionRow_Day_0_en.png | 4 +- ...TimelineVideoWithCaptionRow_Night_0_en.png | 4 +- ...lineItemRedactedMessagesGroup_Day_0_en.png | 3 + ...neItemRedactedMessagesGroup_Night_0_en.png | 3 + ...blockedusers_BlockedUsersView_Day_4_en.png | 4 +- ...blockedusers_BlockedUsersView_Day_5_en.png | 4 +- ...ockedusers_BlockedUsersView_Night_4_en.png | 4 +- ...ockedusers_BlockedUsersView_Night_5_en.png | 4 +- ...mpl.userstatus_UserStatusView_Day_5_en.png | 4 +- ...mpl.userstatus_UserStatusView_Day_6_en.png | 4 +- ...l.userstatus_UserStatusView_Night_5_en.png | 4 +- ...l.userstatus_UserStatusView_Night_6_en.png | 4 +- ...impl_RoomMemberModerationView_Day_5_en.png | 4 +- ...impl_RoomMemberModerationView_Day_7_en.png | 4 +- ...pl_RoomMemberModerationView_Night_5_en.png | 4 +- ...pl_RoomMemberModerationView_Night_7_en.png | 4 +- ...orizontalFloatingToolbarNoFab_Day_0_en.png | 4 +- ...izontalFloatingToolbarNoFab_Night_0_en.png | 4 +- ...nts_HorizontalFloatingToolbar_Day_0_en.png | 4 +- ...s_HorizontalFloatingToolbar_Night_0_en.png | 4 +- tools/localazy/config.json | 1 + 37 files changed, 477 insertions(+), 54 deletions(-) create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/CollapseRedactedRunsTest.kt create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/GroupabilityTest.kt create mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemRedactedMessagesGroup_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemRedactedMessagesGroup_Night_0_en.png diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index fc7cd844fa..61b5c690a4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -21,6 +21,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemRead import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo import io.element.android.features.messages.impl.timeline.model.anAggregatedReaction import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel @@ -255,6 +256,23 @@ internal fun aGroupedEvents( ) } +internal fun aRedactedMessagesGroupedEvents( + id: UniqueId = UniqueId("redacted_group"), + count: Int = 4, +): TimelineItem.GroupedEvents { + val events = (0 until count).map { index -> + aTimelineItemEvent( + eventId = EventId("\$redacted_$index"), + content = TimelineItemRedactedContent, + ) + } + return TimelineItem.GroupedEvents( + id = id, + events = events.toImmutableList(), + aggregatedReadReceipts = persistentListOf(), + ) +} + internal fun aTimelineRoomInfo( name: String = ROOM_NAME, isDm: Boolean = false, 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 ed927956df..a49952b272 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 @@ -19,12 +19,14 @@ import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.aGroupedEvents +import io.element.android.features.messages.impl.timeline.aRedactedMessagesGroupedEvents import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView +import io.element.android.features.messages.impl.timeline.groups.isRedactedMessagesGroup import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState @@ -147,12 +149,18 @@ private fun TimelineItemGroupedEventsRowContent( }, ) { Column(modifier = modifier.animateContentSize()) { + val count = timelineItem.events.size + // A group made entirely of redacted events is a collapsed run of deleted messages + // (element-web style); anything else is the regular run of room state changes. For the + // redacted case we show only the count: the SDK does not expose who performed the redaction, + // and showing the original authors would be misleading. + val headerText = if (timelineItem.isRedactedMessagesGroup()) { + pluralStringResource(R.plurals.screen_room_timeline_redacted_messages, count, count) + } else { + pluralStringResource(R.plurals.screen_room_timeline_state_changes, count, count) + } GroupHeaderView( - text = pluralStringResource( - id = R.plurals.screen_room_timeline_state_changes, - count = timelineItem.events.size, - timelineItem.events.size - ), + text = headerText, isExpanded = isExpanded, isHighlighted = !isExpanded && timelineItem.events.any { it.isEvent(focusedEventId) }, onClick = onExpandGroupClick, @@ -252,3 +260,31 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi eventSink = {}, ) } + +@PreviewsDayNight +@Composable +internal fun TimelineItemRedactedMessagesGroupPreview() = ElementPreview { + // A collapsed run of deleted messages, shown as a single "N removed messages" header. + TimelineItemGroupedEventsRowContent( + isExpanded = false, + onExpandGroupClick = {}, + timelineItem = aRedactedMessagesGroupedEvents(count = 11), + timelineMode = Timeline.Mode.Live, + timelineRoomInfo = aTimelineRoomInfo(), + timelineProtectionState = aTimelineProtectionState(), + focusedEventId = null, + isLastOutgoingMessage = false, + displayThreadSummaries = false, + onClick = {}, + onLongClick = {}, + onLinkLongClick = {}, + inReplyToClick = {}, + onUserDataClick = {}, + onLinkClick = {}, + onReactionClick = { _, _ -> }, + onReactionLongClick = { _, _ -> }, + onMoreReactionsClick = {}, + onReadReceiptClick = {}, + eventSink = {}, + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt index 8ebfcfc9d2..431942378a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt @@ -44,6 +44,14 @@ import io.element.android.libraries.matrix.api.timeline.item.event.StickerConten import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent +/** + * Return true when every event in the group is a redacted (deleted) message, i.e. the group is a + * collapsed run of deleted messages rather than the usual run of room state changes. Used to pick + * the group header label. An empty group is not considered a redacted group. + */ +internal fun TimelineItem.GroupedEvents.isRedactedMessagesGroup(): Boolean = + events.isNotEmpty() && events.all { it.content is TimelineItemRedactedContent } + /** * Return true if the Event can be grouped in a collapse/expand block * When [canBeGrouped] returns a value, [canBeDisplayedInBubbleBlock] MUST return the opposite value. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt index 6af4f8be6a..2e01e53828 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt @@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.UniqueId import kotlinx.collections.immutable.toImmutableList @@ -47,7 +48,8 @@ class TimelineItemGrouper { if (currentGroup.isNotEmpty()) { result.addGroup(groupIds, currentGroup) } - return result + // Finally, fold runs of consecutive deleted messages into a single group (element-web style). + return result.collapseRedactedRuns(groupIds) } } @@ -89,3 +91,56 @@ private fun MutableMap.getOrPutGroupId(timelineItems: List.collapseRedactedRuns(groupIds: MutableMap): List { + val result = mutableListOf() + val run = mutableListOf() + + fun flushRun() { + when { + run.isEmpty() -> Unit + run.size < MIN_REDACTED_RUN_SIZE -> result.addAll(run) + else -> { + val events = run.reversed() + result.add( + TimelineItem.GroupedEvents( + id = UniqueId(groupIds.getOrPutGroupId(events)), + events = events.toImmutableList(), + aggregatedReadReceipts = events.flatMap { it.readReceiptState.receipts }.toImmutableList(), + ) + ) + } + } + run.clear() + } + + for (item in this) { + if (item is TimelineItem.Event && item.content is TimelineItemRedactedContent) { + run.add(item) + } else { + flushRun() + result.add(item) + } + } + flushRun() + return result +} diff --git a/features/messages/impl/src/main/res/values/localazy.xml b/features/messages/impl/src/main/res/values/localazy.xml index 173a4e78ad..e4790feba0 100644 --- a/features/messages/impl/src/main/res/values/localazy.xml +++ b/features/messages/impl/src/main/res/values/localazy.xml @@ -79,6 +79,10 @@ "Show more" "Show reactions summary" "New" + + "%1$d removed message" + "%1$d removed messages" + "%1$d room change" "%1$d room changes" diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/CollapseRedactedRunsTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/CollapseRedactedRunsTest.kt new file mode 100644 index 0000000000..8ccce4c995 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/CollapseRedactedRunsTest.kt @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.timeline + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData +import io.element.android.features.messages.impl.timeline.groups.collapseRedactedRuns +import io.element.android.features.messages.impl.timeline.groups.computeGroupIdWith +import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.UniqueId +import kotlinx.collections.immutable.toImmutableList +import org.junit.Test + +class CollapseRedactedRunsTest { + private val groupIds = mutableMapOf() + + private fun redacted(id: String) = aTimelineItemEvent( + eventId = EventId(id), + content = TimelineItemRedactedContent, + ) + + @Test + fun `collapses a run of three or more redacted events into a single group`() { + val r1 = redacted("\$R1") + val r2 = redacted("\$R2") + val r3 = redacted("\$R3") + val result = listOf(r1, r2, r3).collapseRedactedRuns(groupIds) + assertThat(result).hasSize(1) + val group = result.single() as TimelineItem.GroupedEvents + // Stored oldest-first (the input is newest-first), like the existing grouper. + assertThat(group.events.map { it.eventId }).containsExactly( + EventId("\$R3"), + EventId("\$R2"), + EventId("\$R1"), + ).inOrder() + // Group id comes from the shared registry, keyed like the other groups (oldest member). + assertThat(group.id).isEqualTo(computeGroupIdWith(r3)) + } + + @Test + fun `leaves a run shorter than three as individual tiles`() { + val r1 = redacted("\$R1") + val r2 = redacted("\$R2") + val result = listOf(r1, r2).collapseRedactedRuns(groupIds) + assertThat(result).containsExactly(r1, r2).inOrder() + } + + @Test + fun `keeps surrounding events and only collapses the redacted run between them`() { + val newest = aTimelineItemEvent(eventId = EventId("\$NEW")) + val r1 = redacted("\$R1") + val r2 = redacted("\$R2") + val r3 = redacted("\$R3") + val oldest = aTimelineItemEvent(eventId = EventId("\$OLD")) + val result = listOf(newest, r1, r2, r3, oldest).collapseRedactedRuns(groupIds) + assertThat(result).hasSize(3) + assertThat(result.first()).isEqualTo(newest) + assertThat(result.last()).isEqualTo(oldest) + assertThat(result[1]).isInstanceOf(TimelineItem.GroupedEvents::class.java) + assertThat((result[1] as TimelineItem.GroupedEvents).events).hasSize(3) + } + + @Test + fun `a day separator breaks a run so each side is evaluated on its own`() { + val r1 = redacted("\$R1") + val r2 = redacted("\$R2") + val r3 = redacted("\$R3") + val sep = aTimelineItemDaySeparator() + val r4 = redacted("\$R4") + val r5 = redacted("\$R5") + val result = listOf(r1, r2, r3, sep, r4, r5).collapseRedactedRuns(groupIds) + // First run (3) collapses, the separator passes through, the second run (2) stays as tiles. + assertThat(result).hasSize(4) + assertThat(result.first()).isInstanceOf(TimelineItem.GroupedEvents::class.java) + assertThat(result[1]).isEqualTo(sep) + assertThat(result[2]).isEqualTo(r4) + assertThat(result[3]).isEqualTo(r5) + } + + @Test + fun `does not collapse a run interrupted by a non-redacted event`() { + val r1 = redacted("\$R1") + val r2 = redacted("\$R2") + val between = aTimelineItemEvent(eventId = EventId("\$MID")) + val r3 = redacted("\$R3") + val result = listOf(r1, r2, between, r3).collapseRedactedRuns(groupIds) + assertThat(result).containsExactly(r1, r2, between, r3).inOrder() + } + + @Test + fun `collapses a long run into a single group keeping every event`() { + val run = (1..5).map { redacted("\$R$it") } + val result = run.collapseRedactedRuns(groupIds) + assertThat(result).hasSize(1) + assertThat((result.single() as TimelineItem.GroupedEvents).events).hasSize(5) + } + + @Test + fun `collapses each qualifying run independently`() { + val firstRun = (1..3).map { redacted("\$A$it") } + val separator = aTimelineItemEvent(eventId = EventId("\$SEP")) + val secondRun = (1..3).map { redacted("\$B$it") } + val result = (firstRun + separator + secondRun).collapseRedactedRuns(groupIds) + assertThat(result).hasSize(3) + assertThat(result[0]).isInstanceOf(TimelineItem.GroupedEvents::class.java) + assertThat(result[1]).isEqualTo(separator) + assertThat(result[2]).isInstanceOf(TimelineItem.GroupedEvents::class.java) + // The two groups get distinct ids so their expand state does not bleed into one another. + assertThat((result[0] as TimelineItem.GroupedEvents).id) + .isNotEqualTo((result[2] as TimelineItem.GroupedEvents).id) + } + + @Test + fun `collapses a run that sits at the very end of the list`() { + val newest = aTimelineItemEvent(eventId = EventId("\$NEW")) + val run = (1..3).map { redacted("\$R$it") } + val result = (listOf(newest) + run).collapseRedactedRuns(groupIds) + assertThat(result).hasSize(2) + assertThat(result.first()).isEqualTo(newest) + assertThat(result.last()).isInstanceOf(TimelineItem.GroupedEvents::class.java) + } + + @Test + fun `leaves a single redacted event untouched`() { + val r1 = redacted("\$R1") + assertThat(listOf(r1).collapseRedactedRuns(groupIds)).containsExactly(r1) + } + + @Test + fun `aggregates the read receipts of every event in the collapsed group`() { + fun redactedWithReceipt(id: String, receiptIndex: Int) = aTimelineItemEvent( + eventId = EventId(id), + content = TimelineItemRedactedContent, + readReceiptState = TimelineItemReadReceipts( + receipts = listOf(aReadReceiptData(receiptIndex)).toImmutableList(), + ), + ) + val run = listOf( + redactedWithReceipt("\$R1", 0), + redactedWithReceipt("\$R2", 1), + redactedWithReceipt("\$R3", 2), + ) + val group = run.collapseRedactedRuns(groupIds).single() as TimelineItem.GroupedEvents + assertThat(group.aggregatedReadReceipts).hasSize(3) + } + + @Test + fun `an existing grouped item breaks the run and is passed through untouched`() { + val stateGroup = aGroupedEvents(id = UniqueId("state")) + val run = (1..3).map { redacted("\$R$it") } + val result = (run + stateGroup).collapseRedactedRuns(groupIds) + assertThat(result).hasSize(2) + assertThat(result.first()).isInstanceOf(TimelineItem.GroupedEvents::class.java) + assertThat(result.last()).isEqualTo(stateGroup) + } + + @Test + fun `is a no-op on an empty list`() { + assertThat(emptyList().collapseRedactedRuns(groupIds)).isEmpty() + } + + @Test + fun `the group id survives the run growing at either end`() { + val r1 = redacted("\$R1") + val r2 = redacted("\$R2") + val r3 = redacted("\$R3") + val first = listOf(r1, r2, r3).collapseRedactedRuns(groupIds).single() as TimelineItem.GroupedEvents + // A newer adjacent message gets redacted and older history loads in: the id must not move, + // or the user's expanded group would snap shut on the update. + val grown = listOf(redacted("\$R0"), r1, r2, r3, redacted("\$R4")) + .collapseRedactedRuns(groupIds) + .single() as TimelineItem.GroupedEvents + assertThat(grown.id).isEqualTo(first.id) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index f8f23b35f1..a8be3f3836 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -44,6 +44,7 @@ import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender import io.element.android.libraries.matrix.api.timeline.item.event.Receipt +import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -865,6 +866,28 @@ class TimelinePresenterTest { } } + @Test + fun `present - collapses a run of three or more redacted events into a single group`() = runTest { + val timeline = FakeTimeline( + timelineItems = flowOf( + (0 until 3).map { index -> + MatrixTimelineItem.Event( + uniqueId = UniqueId("redacted_$index"), + event = anEventTimelineItem(eventId = EventId("\$R$index"), content = RedactedContent), + ) + } + ), + ) + val presenter = createTimelinePresenter(timeline = timeline) + presenter.test { + val state = consumeItemsUntilPredicate { it.timelineItems.size == 1 }.last() + val group = state.timelineItems.single() + assertThat(group).isInstanceOf(TimelineItem.GroupedEvents::class.java) + assertThat((group as TimelineItem.GroupedEvents).events).hasSize(3) + cancelAndIgnoreRemainingEvents() + } + } + @Test fun `present - reaction ordering`() = runTest { val timelineItems = MutableStateFlow(emptyList()) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/GroupabilityTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/GroupabilityTest.kt new file mode 100644 index 0000000000..fde84ca77a --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/GroupabilityTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.timeline.groups + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.messages.impl.timeline.aGroupedEvents +import io.element.android.features.messages.impl.timeline.aTimelineItemEvent +import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.UniqueId +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import org.junit.Test + +class GroupabilityTest { + private fun redacted(id: String) = aTimelineItemEvent( + eventId = EventId(id), + content = TimelineItemRedactedContent, + ) + + private fun groupOf(vararg events: TimelineItem.Event) = TimelineItem.GroupedEvents( + id = UniqueId("group"), + events = events.toList().toImmutableList(), + aggregatedReadReceipts = persistentListOf(), + ) + + @Test + fun `a group made only of redacted events is a redacted messages group`() { + val group = groupOf(redacted("\$R1"), redacted("\$R2"), redacted("\$R3")) + assertThat(group.isRedactedMessagesGroup()).isTrue() + } + + @Test + fun `a group of state changes is not a redacted messages group`() { + // aGroupedEvents builds a run of state events. + assertThat(aGroupedEvents().isRedactedMessagesGroup()).isFalse() + } + + @Test + fun `a group mixing redacted and non-redacted events is not a redacted messages group`() { + val group = groupOf(redacted("\$R1"), aTimelineItemEvent(eventId = EventId("\$E")), redacted("\$R2")) + assertThat(group.isRedactedMessagesGroup()).isFalse() + } + + @Test + fun `an empty group is not a redacted messages group`() { + val group = TimelineItem.GroupedEvents( + id = UniqueId("group"), + events = persistentListOf(), + aggregatedReadReceipts = persistentListOf(), + ) + assertThat(group.isRedactedMessagesGroup()).isFalse() + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt index 726646f5e9..005eb5730e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt @@ -15,6 +15,7 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.model.ReadReceiptData import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel import io.element.android.libraries.designsystem.components.avatar.anAvatarData @@ -169,4 +170,31 @@ class TimelineItemGrouperTest { // Then assertThat(actualGroupId).isEqualTo(expectedGroupId) } + + @Test + fun `a run of three or more redacted events is collapsed into a single group as a final step`() { + val r0 = aGroupableItem.copy(id = UniqueId("r0"), content = TimelineItemRedactedContent) + val r1 = aGroupableItem.copy(id = UniqueId("r1"), content = TimelineItemRedactedContent) + val r2 = aGroupableItem.copy(id = UniqueId("r2"), content = TimelineItemRedactedContent) + val result = sut.group(listOf(r0, r1, r2)) + assertThat(result).isEqualTo( + listOf( + TimelineItem.GroupedEvents( + id = computeGroupIdWith(r2), + // Stored oldest-first (the input is newest-first) with the id keyed by the + // oldest member through the shared registry, like the other groups. + events = listOf(r2, r1, r0).toImmutableList(), + aggregatedReadReceipts = emptyList().toImmutableList(), + ) + ) + ) + } + + @Test + fun `a run of fewer than three redacted events is left untouched`() { + val r0 = aGroupableItem.copy(id = UniqueId("r0"), content = TimelineItemRedactedContent) + val r1 = aGroupableItem.copy(id = UniqueId("r1"), content = TimelineItemRedactedContent) + val result = sut.group(listOf(r0, r1)) + assertThat(result).isEqualTo(listOf(r0, r1)) + } } diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt index fdb4e27ef0..8a088e8e93 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt @@ -158,6 +158,7 @@ class KonsistPreviewTest { "TimelineItemGroupedEventsRowContentCollapsePreview", "TimelineItemGroupedEventsRowContentExpandedPreview", "TimelineItemImageViewHideMediaContentPreview", + "TimelineItemRedactedMessagesGroupPreview", "TimelineItemVideoViewHideMediaContentPreview", "TimelineItemVoiceViewUnifiedPreview", "TimelineVideoWithCaptionRowPreview", diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.customreaction_EmojiItemWithPopup_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.customreaction_EmojiItemWithPopup_Day_0_en.png index a2b35f18ca..912b90b537 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.customreaction_EmojiItemWithPopup_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.customreaction_EmojiItemWithPopup_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5572491b675a16724cd9e33ebb477c82edf285fa07d57ebef09c670fcaaddf1 -size 21285 +oid sha256:4746d11c30648f7c517b0880021e5a1dc6ed2819af6c800b081b0d842ac2c384 +size 5325 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.customreaction_EmojiItemWithPopup_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.customreaction_EmojiItemWithPopup_Night_0_en.png index 9c425cec6e..45011fea4d 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.customreaction_EmojiItemWithPopup_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.customreaction_EmojiItemWithPopup_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:290a2be6838445739b0181006f9fea87337a85cfed3573c153e9f503c66624a6 -size 4994 +oid sha256:eede6f990a7a1dbddee1cd9e722b30600160d475ad2ae72faebcb773f52aaa7b +size 20793 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png index 38a81d10b8..08169845d5 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4420dfe9f6f95411093eb1c17825a389a31d7a5d5ef146129e5872cdd5d87aff -size 295295 +oid sha256:86eac241e9084cc830ff13d231908da2eb0dff246aa5cac64dddaac68a5d5f13 +size 295298 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png index 06dd93b269..3f12875a5b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:578a3707102c7754f607c3a14f94f32cafa30ee570db174386533c484cb4773c -size 295031 +oid sha256:7076c9a44a433cd9eeca536365ae262d39b4ad99a8f854faadec272cb1b82031 +size 294992 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Day_0_en.png index fc985de87d..29e7582971 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f358da2b9b0521c14e525bcb2bd41c29c96aacaf5f007546e6d362bde2ee3fb -size 612526 +oid sha256:d6441c572f64d99bc119d649e5e6422fe516b9d097f310ccbd99a9ddded9609f +size 612494 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Night_0_en.png index 90bee6c117..176ebfa36b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineImageWithCaptionRow_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a709de55bc68cf12064ebdbde92c0a99bef3252958d7966540458d253e8c12c2 -size 611422 +oid sha256:5a867dda82242d2b13bd4bc84a9f1c9b8b7b99c030e19a6cce39b2f828b517f3 +size 611488 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Day_0_en.png index 80857358e7..f792a1663a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54e71f45db31e8664c42f8c05feb91ae839589e00cb89d12088c15c85fb9f25f -size 607824 +oid sha256:e3bb005a88e9a4d2bb850478ee8ac2c4f31d32625bd6d2abf455fade461cd399 +size 607806 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Night_0_en.png index 8733429e34..18319e718f 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bb505fe6d67669339c9366f8ee5bc75974e223837d95e76e637455a51c18711 -size 606787 +oid sha256:4a2a57f5547f98b9ac2d8bf7195a1e4b49e03834e8866c6cc76660a6a3b16e74 +size 606823 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemRedactedMessagesGroup_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemRedactedMessagesGroup_Day_0_en.png new file mode 100644 index 0000000000..b367521180 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemRedactedMessagesGroup_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a83859b4c8ef66b7d22008760b66af367affd8e6c1d7cdc099a122fea64f924 +size 7548 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemRedactedMessagesGroup_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemRedactedMessagesGroup_Night_0_en.png new file mode 100644 index 0000000000..52a7b44ea1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_TimelineItemRedactedMessagesGroup_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5d132c02fe441f9bc654ae5fad86e8409413869637f147a9853ceb8883d9f12 +size 7270 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png index 074820e157..7cca911c45 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7caacb336793b8ae4b83e9a10a82325e25e8b3c036f881898ed28688250eb6e0 -size 55744 +oid sha256:26e14b77fa3c3aeaea58782309e6de8b9047e07945009322990ebf5f4c283309 +size 52770 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png index 2af459c158..7cca911c45 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28a95d4b7ebca92c4e0467f3ecd9acdaf7f6257604d190f5d088a9a25e87d637 -size 55305 +oid sha256:26e14b77fa3c3aeaea58782309e6de8b9047e07945009322990ebf5f4c283309 +size 52770 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png index 616c06bf46..a68f886f73 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ce2519906f1aaf34700ca9ba4bdeaa72030f1f5aff3f7e24352a1435ac67d98 -size 53966 +oid sha256:e5a2e306087a967849f61872b56a83f194d1a76df5723f0bbae6e6e2f4a98c63 +size 55725 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png index 36d19c6b72..616c06bf46 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7dfa2767c55a81d3f121b8fa55443b7b05f9369e91639b80e0be349863799ad7 -size 55438 +oid sha256:0ce2519906f1aaf34700ca9ba4bdeaa72030f1f5aff3f7e24352a1435ac67d98 +size 53966 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png index 7ef9b7ae0a..1f73455d54 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d19e7a09114e76294a0626e5d617cf55fe7c13a0188a7d5b364f57c0d9daedc1 -size 12123 +oid sha256:b5dc5c492ed3fd552e77c0be85b639d55bc7e976f15b761f927b0b80ecca601d +size 12115 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png index 3e18858873..0ca6f66438 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42f03759fcffbb750181168256130b6a94a1fcf91ee028e69a19a6e3b68ed0e8 -size 11711 +oid sha256:5896eb5df6f5398bdb94e74280d0d5c04b6a5a53c2378b7377c60fa58eef544b +size 12013 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png index 1e04c3e7d7..a687241151 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6bbc8c05e6f6818d00282228f52424ebe93e6e3dca3431d742e8c652d56460d6 -size 11712 +oid sha256:e7ce8a8c6b4d1692e4b127b7f17f507552666c631893025082c385b93d835a73 +size 11726 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png index 8e1b9c99d2..a1d1c5ec8e 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66f6d0d6b03ee4c999cd4edc9c6ae567d31b8b537ca6f9cc8fbe436d06ce80ab -size 11643 +oid sha256:8b93c08531efbeacb55f84b17ac793a7b226e5a2db7bc5072e94bc040cd1e9bc +size 11386 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png index 73b8b8e50c..5c597300e4 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8a3ad99763929d2783147814e44fa9ec7e7efb7b99949e3580f23c38fb5d971 -size 9398 +oid sha256:21aee17ee9a292bb2f0d3d2ec2db565ebb8d5df753454a5a002f033ed5ed39c5 +size 3853 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png index 5c597300e4..f8a1f24b34 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21aee17ee9a292bb2f0d3d2ec2db565ebb8d5df753454a5a002f033ed5ed39c5 -size 3853 +oid sha256:1342b6427f5b2e3075ae3ccee60bf1d3b11afaccf3e152643d6ef2d65c21b54e +size 9108 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png index d47bf90664..4ae7279fe7 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:520cae2544153ba45f8fe4b021286c2d22da06aa1b60be7f1b13879723ff994c -size 3664 +oid sha256:410a8596d4c70b9a3856d959d6d71ade8e831e8581ff410c9900957a93b3eb50 +size 8114 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png index a87d89c701..d47bf90664 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f3a0cb5d9bae6725a1c527f3d9575aabf85d3edf63a28d9faa23f4f0b65e2d0 -size 7624 +oid sha256:520cae2544153ba45f8fe4b021286c2d22da06aa1b60be7f1b13879723ff994c +size 3664 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png index ee5c582240..58546b8522 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75de341f705bb22955f9399180519b65ad59c2e1917317d56e50ea4db78f35bc -size 12102 +oid sha256:30357bf18792db5ab20085010251170a724ffe8c00313facd04ee704e806af64 +size 10982 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png index fedead0fa8..52fd9b9693 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4811667dec0f32accc5e2c8a5dd5c0b34017fcff8d1e0376ee7a4708426bca0f -size 9838 +oid sha256:58b9491aaa5554b89d86c62a5c9c44975367538c6baa5562f8dc5768f76d8264 +size 8705 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png index 38ad0e4b56..83302d6ba4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:326d59cf768b2dbd96eb7964d54c8eda509a5cd57c6e62087625e3314da0bb4d -size 13658 +oid sha256:16dee1dfe746cd40dac9fe8f0af3ecb42cd14bab5cc490917b589beed065ed1b +size 14686 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png index 4da2a98aca..687e0d436a 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef0dc4d53daeb986f378b6e4d85928b4b6729336c89cf21e642c88c53688a3ab -size 10292 +oid sha256:d57df7110371c9224e232b707d6a1b10ce14afcb56c803a78220792f2a0d4cf2 +size 11354 diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 93e1c71759..5753fe5f47 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -271,6 +271,7 @@ "screen_room_message.*", "screen_room_retry.*", "screen_room_timeline.*", + "screen\\.room\\.timeline.*", "screen\\.room_timeline.*", "screen_room_typing.*", "screen\\.image_edition\\..*",