mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-04 10:32:34 +00:00
Merge pull request #7571 from hayaksi1/fix/7397-keep-thread-after-redaction
Don't hide the thread of a deleted message inside the removed messages group
This commit is contained in:
+4
-2
@@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting
|
|||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import dev.zacsweers.metro.SingleIn
|
import dev.zacsweers.metro.SingleIn
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||||
@@ -99,7 +100,8 @@ internal const val MIN_REDACTED_RUN_SIZE = 3
|
|||||||
* Fold runs of [MIN_REDACTED_RUN_SIZE] or more consecutive deleted (redacted) messages into a single
|
* Fold runs of [MIN_REDACTED_RUN_SIZE] or more consecutive deleted (redacted) messages into a single
|
||||||
* [TimelineItem.GroupedEvents], so a long stretch of "Message removed" tiles shows as one expandable
|
* [TimelineItem.GroupedEvents], so a long stretch of "Message removed" tiles shows as one expandable
|
||||||
* "N removed messages" line, like element-web. Shorter runs, and anything that is not a redacted event
|
* "N removed messages" line, like element-web. Shorter runs, and anything that is not a redacted event
|
||||||
* (day dividers included), are left untouched.
|
* (day dividers included), are left untouched. A deleted message that still heads a thread is left
|
||||||
|
* untouched too, so that its thread summary stays reachable.
|
||||||
*
|
*
|
||||||
* This runs as the final step of [TimelineItemGrouper.group], after the state and membership events
|
* This runs as the final step of [TimelineItemGrouper.group], after the state and membership events
|
||||||
* have been grouped. It is deliberately kept out of the normal grouping: a run needs a higher minimum
|
* have been grouped. It is deliberately kept out of the normal grouping: a run needs a higher minimum
|
||||||
@@ -134,7 +136,7 @@ internal fun List<TimelineItem>.collapseRedactedRuns(groupIds: MutableMap<String
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (item in this) {
|
for (item in this) {
|
||||||
if (item is TimelineItem.Event && item.content is TimelineItemRedactedContent) {
|
if (item is TimelineItem.Event && item.content is TimelineItemRedactedContent && item.threadInfo !is TimelineItemThreadInfo.ThreadRoot) {
|
||||||
run.add(item)
|
run.add(item)
|
||||||
} else {
|
} else {
|
||||||
flushRun()
|
flushRun()
|
||||||
|
|||||||
+39
@@ -14,9 +14,11 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
|||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType
|
import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType
|
||||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
@@ -168,6 +170,43 @@ class TimelineItemsFactoryTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a deleted message keeps the summary of the thread it heads`() = runTest {
|
||||||
|
val factory = aTimelineItemsFactory(
|
||||||
|
config = TimelineItemsFactoryConfig(
|
||||||
|
computeReadReceipts = false,
|
||||||
|
computeReactions = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val items = listOf(
|
||||||
|
MatrixTimelineItem.Event(
|
||||||
|
uniqueId = UniqueId("event-0"),
|
||||||
|
event = anEventTimelineItem(
|
||||||
|
sender = A_USER_ID,
|
||||||
|
content = aRedactedContent(
|
||||||
|
threadInfo = EventThreadInfo.ThreadRoot(
|
||||||
|
summary = ThreadSummary(latestEvent = AsyncData.Uninitialized, numberOfReplies = 3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
factory.timelineItems.test {
|
||||||
|
factory.replaceWith(
|
||||||
|
timelineItems = items,
|
||||||
|
roomMembers = emptyList(),
|
||||||
|
renderReadReceipts = false,
|
||||||
|
)
|
||||||
|
val threadInfo = awaitItem()
|
||||||
|
.filterIsInstance<TimelineItem.Event>()
|
||||||
|
.single()
|
||||||
|
.threadInfo
|
||||||
|
assertThat(threadInfo).isInstanceOf(TimelineItemThreadInfo.ThreadRoot::class.java)
|
||||||
|
assertThat((threadInfo as TimelineItemThreadInfo.ThreadRoot).summary.numberOfReplies).isEqualTo(3)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun TestScope.groupPositionsOf(timestamps: List<Long>): List<TimelineItemGroupPosition> {
|
private suspend fun TestScope.groupPositionsOf(timestamps: List<Long>): List<TimelineItemGroupPosition> {
|
||||||
val factory = aTimelineItemsFactory(
|
val factory = aTimelineItemsFactory(
|
||||||
config = TimelineItemsFactoryConfig(
|
config = TimelineItemsFactoryConfig(
|
||||||
|
|||||||
+19
@@ -15,11 +15,14 @@ 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.ReadReceiptData
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
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.TimelineItemReadReceipts
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
|
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.event.TimelineItemStateEventContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
|
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
|
||||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
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.test.A_USER_ID
|
||||||
@@ -190,6 +193,22 @@ class TimelineItemGrouperTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a deleted message that still heads a thread is left out of the removed messages group`() {
|
||||||
|
val threadRoot = aGroupableItem.copy(
|
||||||
|
id = UniqueId("r1"),
|
||||||
|
content = TimelineItemRedactedContent,
|
||||||
|
threadInfo = TimelineItemThreadInfo.ThreadRoot(
|
||||||
|
summary = ThreadSummary(latestEvent = AsyncData.Uninitialized, numberOfReplies = 2),
|
||||||
|
latestEventText = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val r0 = aGroupableItem.copy(id = UniqueId("r0"), content = TimelineItemRedactedContent)
|
||||||
|
val r2 = aGroupableItem.copy(id = UniqueId("r2"), content = TimelineItemRedactedContent)
|
||||||
|
val result = sut.group(listOf(r0, threadRoot, r2))
|
||||||
|
assertThat(result).isEqualTo(listOf(r0, threadRoot, r2))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `a run of fewer than three redacted events is left untouched`() {
|
fun `a run of fewer than three redacted events is left untouched`() {
|
||||||
val r0 = aGroupableItem.copy(id = UniqueId("r0"), content = TimelineItemRedactedContent)
|
val r0 = aGroupableItem.copy(id = UniqueId("r0"), content = TimelineItemRedactedContent)
|
||||||
|
|||||||
Reference in New Issue
Block a user