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:
parent
f297ce3d52
commit
ff263e611e
6 changed files with 339 additions and 8 deletions
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"))
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue