Do not show membership/profile events in public rooms (#6360)

* Filter some membership/profile/topic events in public rooms: don't display join and leave membership events in publicly joinable rooms, and hide display name and avatar url changes in non encrypted and publicly joinable rooms.

* Add empty day post-processing to the timeline based on bxdxnn's code, tweaked.

---------

Co-authored-by: Jorge Martín <jorgem@element.io>
This commit is contained in:
bxdxnn 2026-05-25 11:31:53 +03:00 committed by GitHub
parent f297ce3d52
commit ff263e611e
6 changed files with 339 additions and 8 deletions

View file

@ -16,6 +16,7 @@ import io.element.android.features.messages.impl.timeline.factories.event.Timeli
import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemVirtualFactory import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemVirtualFactory
import io.element.android.features.messages.impl.timeline.groups.TimelineItemGrouper import io.element.android.features.messages.impl.timeline.groups.TimelineItemGrouper
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.virtual.TimelineItemDaySeparatorModel
import io.element.android.libraries.androidutils.diff.DiffCacheUpdater import io.element.android.libraries.androidutils.diff.DiffCacheUpdater
import io.element.android.libraries.androidutils.diff.MutableListDiffCache import io.element.android.libraries.androidutils.diff.MutableListDiffCache
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
@ -96,7 +97,8 @@ class TimelineItemsFactory(
} }
} }
val result = timelineItemGrouper.group(newTimelineItemStates).toImmutableList() val result = timelineItemGrouper.group(newTimelineItemStates).toImmutableList()
this._timelineItems.emit(result) val filteredResult = filterEmptyDaySeparators(result)
this._timelineItems.emit(filteredResult)
} }
private suspend fun buildAndCacheItem( private suspend fun buildAndCacheItem(
@ -114,3 +116,25 @@ class TimelineItemsFactory(
return timelineItem return timelineItem
} }
} }
// Remove day separators for days with no events after the client-side event filtering
internal fun filterEmptyDaySeparators(items: List<TimelineItem>): ImmutableList<TimelineItem> {
return buildList {
var hasEventBefore = false
for (item in items) {
when (item) {
is TimelineItem.Event, is TimelineItem.GroupedEvents -> {
hasEventBefore = true
add(item)
}
is TimelineItem.Virtual if item.model is TimelineItemDaySeparatorModel -> {
if (hasEventBefore) {
add(item)
}
hasEventBefore = false
}
else -> add(item)
}
}
}.toImmutableList()
}

View file

@ -0,0 +1,160 @@
/*
* 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.factories
import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.fixtures.aMessageEvent
import io.element.android.features.messages.impl.timeline.aTimelineItemDebugInfo
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.virtual.TimelineItemReadMarkerModel
import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel
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.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
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.core.FakeSendHandle
import kotlinx.collections.immutable.toImmutableList
import org.junit.Test
class TimelineItemsFactoryTest {
private val anEvent = TimelineItem.Event(
id = UniqueId("event"),
eventId = AN_EVENT_ID,
senderId = A_USER_ID,
senderAvatar = anAvatarData(),
senderProfile = ProfileDetails.Ready(displayName = "User", displayNameAmbiguous = false, avatarUrl = null),
content = aMessageEvent().content,
reactionsState = aTimelineItemReactions(count = 0),
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),
localSendState = LocalEventSendState.Sent(AN_EVENT_ID),
isEditable = false,
canBeRepliedTo = false,
inReplyTo = null,
threadInfo = null,
origin = null,
timelineItemDebugInfoProvider = { aTimelineItemDebugInfo() },
messageShieldProvider = { null },
sendHandleProvider = { FakeSendHandle() },
forwarder = null,
forwarderProfile = null,
)
private fun aDaySeparator(date: String) = TimelineItem.Virtual(
id = UniqueId("day_$date"),
model = aTimelineItemDaySeparatorModel(date)
)
@Test
fun `filterEmptyDaySeparators keeps day separator with events after it`() {
val items = listOf(
anEvent,
aDaySeparator("Today"),
)
val result = filterEmptyDaySeparators(items)
assertThat(result).hasSize(2)
assertThat(result[0]).isEqualTo(anEvent)
assertThat(result[1]).isEqualTo(aDaySeparator("Today"))
}
@Test
fun `filterEmptyDaySeparators removes day separator with no events after it`() {
val items = listOf(
aDaySeparator("Today"),
aDaySeparator("Yesterday"),
)
val result = filterEmptyDaySeparators(items)
assertThat(result).isEmpty()
}
@Test
fun `filterEmptyDaySeparators removes first day separator and keeps second when only second has events`() {
val items = listOf(
aDaySeparator("Today"),
anEvent,
aDaySeparator("Yesterday"),
)
val result = filterEmptyDaySeparators(items)
assertThat(result).hasSize(2)
assertThat(result[0]).isEqualTo(anEvent)
assertThat(result[1]).isEqualTo(aDaySeparator("Yesterday"))
}
@Test
fun `filterEmptyDaySeparators handles multiple day separators in a row with no events`() {
val items = listOf(
aDaySeparator("Today"),
aDaySeparator("Yesterday"),
aDaySeparator("Last week"),
)
val result = filterEmptyDaySeparators(items)
assertThat(result).isEmpty()
}
@Test
fun `filterEmptyDaySeparators keeps all items when no day separators`() {
val items = listOf(
anEvent,
anEvent.copy(id = UniqueId("event2")),
)
val result = filterEmptyDaySeparators(items)
assertThat(result).hasSize(2)
}
@Test
fun `filterEmptyDaySeparators handles grouped events after day separator`() {
val groupedEvents = TimelineItem.GroupedEvents(
id = UniqueId("grouped"),
events = listOf(anEvent).toImmutableList(),
aggregatedReadReceipts = emptyList<ReadReceiptData>().toImmutableList(),
)
val items = listOf(
groupedEvents,
aDaySeparator("Today"),
)
val result = filterEmptyDaySeparators(items)
assertThat(result).hasSize(2)
assertThat(result[0]).isEqualTo(groupedEvents)
assertThat(result[1]).isEqualTo(aDaySeparator("Today"))
}
@Test
fun `filterEmptyDaySeparators removes day separator followed by non-event virtual item`() {
val readMarker = TimelineItem.Virtual(
id = UniqueId("readMarker"),
model = TimelineItemReadMarkerModel
)
val items = listOf(
aDaySeparator("Today"),
readMarker,
)
val result = filterEmptyDaySeparators(items)
assertThat(result).hasSize(1)
assertThat(result[0]).isEqualTo(readMarker)
}
@Test
fun `filterEmptyDaySeparators keeps day separator when non-event virtual items are between separator and event`() {
val readMarker = TimelineItem.Virtual(
id = UniqueId("readMarker"),
model = TimelineItemReadMarkerModel
)
val items = listOf(
anEvent,
readMarker,
aDaySeparator("Today"),
)
val result = filterEmptyDaySeparators(items)
assertThat(result).hasSize(3)
assertThat(result[2]).isEqualTo(aDaySeparator("Today"))
}
}

View file

@ -12,6 +12,7 @@ import io.element.android.libraries.androidutils.hash.hash
import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.ImageInfo
@ -20,6 +21,7 @@ import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.IntentionalMention import io.element.android.libraries.matrix.api.room.IntentionalMention
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.room.location.AssetType
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.MsgType import io.element.android.libraries.matrix.api.timeline.MsgType
@ -43,6 +45,7 @@ import io.element.android.libraries.matrix.impl.timeline.postprocessor.TypingNot
import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper
import io.element.android.libraries.matrix.impl.util.MessageEventContent import io.element.android.libraries.matrix.impl.util.MessageEventContent
import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -121,6 +124,13 @@ class RustTimeline(
private val lastForwardIndicatorsPostProcessor = LastForwardIndicatorsPostProcessor(mode) private val lastForwardIndicatorsPostProcessor = LastForwardIndicatorsPostProcessor(mode)
private val typingNotificationPostProcessor = TypingNotificationPostProcessor(mode) private val typingNotificationPostProcessor = TypingNotificationPostProcessor(mode)
private data class RoomTimelineInfo(
val roomCreators: ImmutableList<UserId>,
val isDm: Boolean,
val joinRule: JoinRule?,
val isEncrypted: Boolean?,
)
override val backwardPaginationStatus = MutableStateFlow( override val backwardPaginationStatus = MutableStateFlow(
Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode != Timeline.Mode.PinnedEvents) Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode != Timeline.Mode.PinnedEvents)
) )
@ -220,20 +230,23 @@ class RustTimeline(
_timelineItems, _timelineItems,
backwardPaginationStatus, backwardPaginationStatus,
forwardPaginationStatus, forwardPaginationStatus,
joinedRoom.roomInfoFlow.map { it.creators to it.isDm }.distinctUntilChanged(), joinedRoom.roomInfoFlow.map { RoomTimelineInfo(it.creators, it.isDm, it.joinRule, it.isEncrypted) }.distinctUntilChanged(),
) { ) {
timelineItems, timelineItems,
backwardPaginationStatus, backwardPaginationStatus,
forwardPaginationStatus, forwardPaginationStatus,
(roomCreators, isDm), roomInfo,
-> ->
withContext(dispatcher) { withContext(dispatcher) {
val (roomCreators, isDm, joinRule, isEncrypted) = roomInfo
timelineItems timelineItems
.let { items -> .let { items ->
roomBeginningPostProcessor.process( roomBeginningPostProcessor.process(
items = items, items = items,
isDm = isDm, isDm = isDm,
roomCreator = roomCreators.firstOrNull(), roomCreator = roomCreators.firstOrNull(),
joinRule = joinRule,
isEncrypted = isEncrypted,
hasMoreToLoadBackwards = backwardPaginationStatus.hasMoreToLoad, hasMoreToLoadBackwards = backwardPaginationStatus.hasMoreToLoad,
) )
} }

View file

@ -9,33 +9,49 @@
package io.element.android.libraries.matrix.impl.timeline.postprocessor package io.element.android.libraries.matrix.impl.timeline.postprocessor
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.join.JoinRule
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.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
import io.element.android.libraries.matrix.api.timeline.item.event.OtherState import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
/** /**
* This timeline post-processor removes the room creation event and the self-join event from the timeline for DMs * This timeline post-processor removes the room creation event and the self-join event from the timeline for DMs
* or add the RoomBeginning item. * or add the RoomBeginning item. For rooms that aren't invite-only and aren't encrypted, it also removes join/leave and profile change events.
*/ */
class RoomBeginningPostProcessor(private val mode: Timeline.Mode) { class RoomBeginningPostProcessor(private val mode: Timeline.Mode) {
fun process( fun process(
items: List<MatrixTimelineItem>, items: List<MatrixTimelineItem>,
isDm: Boolean, isDm: Boolean,
roomCreator: UserId?, roomCreator: UserId?,
joinRule: JoinRule?,
isEncrypted: Boolean?,
hasMoreToLoadBackwards: Boolean, hasMoreToLoadBackwards: Boolean,
): List<MatrixTimelineItem> { ): List<MatrixTimelineItem> {
return when { return when {
items.isEmpty() -> items items.isEmpty() -> items
mode == Timeline.Mode.PinnedEvents -> items mode == Timeline.Mode.PinnedEvents -> items
joinRule !is JoinRule.Invite && isEncrypted == false -> filterRoomMemberEvents(items)
isDm -> processForDM(items, roomCreator) isDm -> processForDM(items, roomCreator)
hasMoreToLoadBackwards -> items hasMoreToLoadBackwards -> items
else -> processForRoom(items) else -> processForRoom(items)
} }
} }
private fun filterRoomMemberEvents(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> {
return items.filter { item ->
val eventContent = (item as? MatrixTimelineItem.Event)?.event?.content
when (eventContent) {
is RoomMembershipContent -> eventContent.change !in listOf(MembershipChange.JOINED, MembershipChange.LEFT)
is ProfileChangeContent -> false
else -> true
}
}
}
private fun processForRoom(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> { private fun processForRoom(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> {
// No changes needed, timeline start item is already added by the SDK // No changes needed, timeline start item is already added by the SDK
return items return items

View file

@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTime
import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.aMessageContent
import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageContent
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent
@ -36,6 +37,14 @@ internal val otherMemberJoinEvent = MatrixTimelineItem.Event(
uniqueId = UniqueId("m.room.member_other"), uniqueId = UniqueId("m.room.member_other"),
event = anEventTimelineItem(content = aRoomMembershipContent(userId = A_USER_ID_2, change = MembershipChange.JOINED)) event = anEventTimelineItem(content = aRoomMembershipContent(userId = A_USER_ID_2, change = MembershipChange.JOINED))
) )
internal val otherMemberLeaveEvent = MatrixTimelineItem.Event(
uniqueId = UniqueId("m.room.member_leave"),
event = anEventTimelineItem(content = aRoomMembershipContent(userId = A_USER_ID_2, change = MembershipChange.LEFT))
)
internal val profileChangeEvent = MatrixTimelineItem.Event(
uniqueId = UniqueId("m.room.member_profile"),
event = anEventTimelineItem(content = aProfileChangeMessageContent(displayName = "New Name", prevDisplayName = "Old Name"))
)
internal val messageEvent = MatrixTimelineItem.Event( internal val messageEvent = MatrixTimelineItem.Event(
uniqueId = UniqueId("m.room.message"), uniqueId = UniqueId("m.room.message"),
event = anEventTimelineItem(content = aMessageContent("hi")) event = anEventTimelineItem(content = aMessageContent("hi"))

View file

@ -9,6 +9,7 @@
package io.element.android.libraries.matrix.impl.timeline.postprocessor package io.element.android.libraries.matrix.impl.timeline.postprocessor
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID
import org.junit.Test import org.junit.Test
@ -21,6 +22,8 @@ class RoomBeginningPostProcessorTest {
items = emptyList(), items = emptyList(),
isDm = true, isDm = true,
roomCreator = A_USER_ID, roomCreator = A_USER_ID,
joinRule = null,
isEncrypted = null,
hasMoreToLoadBackwards = false, hasMoreToLoadBackwards = false,
) )
assertThat(processedItems).isEmpty() assertThat(processedItems).isEmpty()
@ -33,6 +36,8 @@ class RoomBeginningPostProcessorTest {
items = listOf(messageEvent), items = listOf(messageEvent),
isDm = true, isDm = true,
roomCreator = A_USER_ID, roomCreator = A_USER_ID,
joinRule = null,
isEncrypted = null,
hasMoreToLoadBackwards = false, hasMoreToLoadBackwards = false,
) )
assertThat(processedItems).isEqualTo(listOf(messageEvent)) assertThat(processedItems).isEqualTo(listOf(messageEvent))
@ -45,6 +50,8 @@ class RoomBeginningPostProcessorTest {
items = listOf(messageEvent), items = listOf(messageEvent),
isDm = true, isDm = true,
roomCreator = null, roomCreator = null,
joinRule = null,
isEncrypted = null,
hasMoreToLoadBackwards = false, hasMoreToLoadBackwards = false,
) )
assertThat(processedItems).isEqualTo(listOf(messageEvent)) assertThat(processedItems).isEqualTo(listOf(messageEvent))
@ -62,6 +69,8 @@ class RoomBeginningPostProcessorTest {
items = timelineItems, items = timelineItems,
isDm = true, isDm = true,
roomCreator = A_USER_ID, roomCreator = A_USER_ID,
joinRule = null,
isEncrypted = null,
hasMoreToLoadBackwards = false, hasMoreToLoadBackwards = false,
) )
assertThat(processedItems).containsExactly(timelineStartEvent) assertThat(processedItems).containsExactly(timelineStartEvent)
@ -78,6 +87,8 @@ class RoomBeginningPostProcessorTest {
items = timelineItems, items = timelineItems,
isDm = true, isDm = true,
roomCreator = A_USER_ID, roomCreator = A_USER_ID,
joinRule = null,
isEncrypted = null,
hasMoreToLoadBackwards = false, hasMoreToLoadBackwards = false,
) )
assertThat(processedItems).isEqualTo(timelineItems) assertThat(processedItems).isEqualTo(timelineItems)
@ -96,7 +107,14 @@ class RoomBeginningPostProcessorTest {
messageEvent, messageEvent,
) )
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live) val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = false) val processedItems = processor.process(
timelineItems,
isDm = true,
roomCreator = A_USER_ID,
joinRule = null,
isEncrypted = null,
hasMoreToLoadBackwards = false
)
assertThat(processedItems).isEqualTo(expected) assertThat(processedItems).isEqualTo(expected)
} }
@ -107,7 +125,14 @@ class RoomBeginningPostProcessorTest {
roomCreatorJoinEvent, roomCreatorJoinEvent,
) )
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live) val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true) val processedItems = processor.process(
timelineItems,
isDm = true,
roomCreator = A_USER_ID,
joinRule = null,
isEncrypted = null,
hasMoreToLoadBackwards = true
)
assertThat(processedItems).isEmpty() assertThat(processedItems).isEmpty()
} }
@ -117,7 +142,14 @@ class RoomBeginningPostProcessorTest {
roomCreatorJoinEvent, roomCreatorJoinEvent,
) )
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live) val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true) val processedItems = processor.process(
timelineItems,
isDm = true,
roomCreator = A_USER_ID,
joinRule = null,
isEncrypted = null,
hasMoreToLoadBackwards = true
)
assertThat(processedItems).isEmpty() assertThat(processedItems).isEmpty()
} }
@ -128,7 +160,84 @@ class RoomBeginningPostProcessorTest {
otherMemberJoinEvent, otherMemberJoinEvent,
) )
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live) val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true) val processedItems = processor.process(
timelineItems,
isDm = true,
roomCreator = A_USER_ID,
joinRule = null,
isEncrypted = null,
hasMoreToLoadBackwards = true
)
assertThat(processedItems).isEqualTo(listOf(otherMemberJoinEvent)) assertThat(processedItems).isEqualTo(listOf(otherMemberJoinEvent))
} }
@Test
fun `processor removes join, leave, and profile events in unencrypted public rooms`() {
val timelineItems = listOf(
roomCreateEvent,
roomCreatorJoinEvent,
otherMemberJoinEvent,
messageEvent,
otherMemberLeaveEvent,
profileChangeEvent,
)
val expected = listOf(
roomCreateEvent,
messageEvent,
)
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
val processedItems = processor.process(
timelineItems,
isDm = false,
roomCreator = A_USER_ID,
joinRule = JoinRule.Public,
isEncrypted = false,
hasMoreToLoadBackwards = false
)
assertThat(processedItems).isEqualTo(expected)
}
@Test
fun `processor keeps all events in encrypted public rooms`() {
val timelineItems = listOf(
roomCreateEvent,
roomCreatorJoinEvent,
otherMemberJoinEvent,
messageEvent,
otherMemberLeaveEvent,
profileChangeEvent,
)
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
val processedItems = processor.process(
timelineItems,
isDm = false,
roomCreator = A_USER_ID,
joinRule = JoinRule.Public,
isEncrypted = true,
hasMoreToLoadBackwards = false
)
assertThat(processedItems).isEqualTo(timelineItems)
}
@Test
fun `processor keeps membership events in invite-only rooms`() {
val timelineItems = listOf(
roomCreateEvent,
roomCreatorJoinEvent,
otherMemberJoinEvent,
messageEvent,
otherMemberLeaveEvent,
profileChangeEvent,
)
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
val processedItems = processor.process(
timelineItems,
isDm = false,
roomCreator = A_USER_ID,
joinRule = JoinRule.Invite,
isEncrypted = null,
hasMoreToLoadBackwards = false
)
assertThat(processedItems).isEqualTo(timelineItems)
}
} }