Browse Source

Fix filtering of Event at the beginning of DM.

pull/3449/head
Benoit Marty 1 month ago committed by Benoit Marty
parent
commit
f87422a022
  1. 3
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt
  2. 1
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt
  3. 6
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt
  4. 21
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt
  5. 3
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt
  6. 157
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt
  7. 2
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt

3
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt

@ -44,5 +44,6 @@ data class MatrixRoomInfo(
val hasRoomCall: Boolean, val hasRoomCall: Boolean,
val activeRoomCallParticipants: ImmutableList<String>, val activeRoomCallParticipants: ImmutableList<String>,
val heroes: ImmutableList<MatrixUser>, val heroes: ImmutableList<MatrixUser>,
val pinnedEventIds: ImmutableList<EventId> val pinnedEventIds: ImmutableList<EventId>,
val creator: UserId?,
) )

1
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt

@ -28,6 +28,7 @@ class MatrixRoomInfoMapper {
fun map(rustRoomInfo: RustRoomInfo): MatrixRoomInfo = rustRoomInfo.let { fun map(rustRoomInfo: RustRoomInfo): MatrixRoomInfo = rustRoomInfo.let {
return MatrixRoomInfo( return MatrixRoomInfo(
id = RoomId(it.id), id = RoomId(it.id),
creator = it.creator?.let(::UserId),
name = it.displayName, name = it.displayName,
rawName = it.rawName, rawName = it.rawName,
topic = it.topic, topic = it.topic,

6
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt

@ -207,15 +207,17 @@ class RustTimeline(
_timelineItems, _timelineItems,
backPaginationStatus.map { it.hasMoreToLoad }.distinctUntilChanged(), backPaginationStatus.map { it.hasMoreToLoad }.distinctUntilChanged(),
forwardPaginationStatus.map { it.hasMoreToLoad }.distinctUntilChanged(), forwardPaginationStatus.map { it.hasMoreToLoad }.distinctUntilChanged(),
matrixRoom.roomInfoFlow.map { it.creator },
isInit, isInit,
) { timelineItems, hasMoreToLoadBackward, hasMoreToLoadForward, isInit -> ) { timelineItems, hasMoreToLoadBackward, hasMoreToLoadForward, roomCreator, isInit ->
withContext(dispatcher) { withContext(dispatcher) {
timelineItems timelineItems
.process { items -> .process { items ->
roomBeginningPostProcessor.process( roomBeginningPostProcessor.process(
items = items, items = items,
isDm = matrixRoom.isDm, isDm = matrixRoom.isDm,
hasMoreToLoadBackwards = hasMoreToLoadBackward roomCreator = roomCreator,
hasMoreToLoadBackwards = hasMoreToLoadBackward,
) )
} }
.process(predicate = isInit) { items -> .process(predicate = isInit) { items ->

21
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt

@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.timeline.postprocessor
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
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.core.UserId
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
@ -25,12 +26,14 @@ class RoomBeginningPostProcessor(private val mode: Timeline.Mode) {
fun process( fun process(
items: List<MatrixTimelineItem>, items: List<MatrixTimelineItem>,
isDm: Boolean, isDm: Boolean,
hasMoreToLoadBackwards: Boolean roomCreator: UserId?,
hasMoreToLoadBackwards: Boolean,
): List<MatrixTimelineItem> { ): List<MatrixTimelineItem> {
return when { return when {
items.isEmpty() -> items
mode == Timeline.Mode.PINNED_EVENTS -> items mode == Timeline.Mode.PINNED_EVENTS -> items
isDm -> processForDM(items, roomCreator)
hasMoreToLoadBackwards -> items hasMoreToLoadBackwards -> items
isDm -> processForDM(items)
else -> processForRoom(items) else -> processForRoom(items)
} }
} }
@ -40,15 +43,18 @@ class RoomBeginningPostProcessor(private val mode: Timeline.Mode) {
return listOf(roomBeginningItem) + items return listOf(roomBeginningItem) + items
} }
private fun processForDM(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> { private fun processForDM(items: List<MatrixTimelineItem>, roomCreator: UserId?): List<MatrixTimelineItem> {
// Find room creation event. This is usually index 0 // Find room creation event.
// This is usually the first MatrixTimelineItem.Event (so index 1, index 0 is a date)
val roomCreationEventIndex = items.indexOfFirst { val roomCreationEventIndex = items.indexOfFirst {
val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? StateContent val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? StateContent
stateEventContent?.content is OtherState.RoomCreate stateEventContent?.content is OtherState.RoomCreate
} }
// Find self-join event for room creator. This is usually index 1 // If the parameter roomCreator is null, the creator is the sender of the RoomCreate Event.
val roomCreatorUserId = (items.getOrNull(roomCreationEventIndex) as? MatrixTimelineItem.Event)?.event?.sender val roomCreatorUserId = roomCreator ?: (items.getOrNull(roomCreationEventIndex) as? MatrixTimelineItem.Event)?.event?.sender
// Find self-join event for the room creator.
// This is usually the second MatrixTimelineItem.Event (so index 2)
val selfUserJoinedEventIndex = roomCreatorUserId?.let { creatorUserId -> val selfUserJoinedEventIndex = roomCreatorUserId?.let { creatorUserId ->
items.indexOfFirst { items.indexOfFirst {
val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? RoomMembershipContent val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? RoomMembershipContent
@ -56,6 +62,9 @@ class RoomBeginningPostProcessor(private val mode: Timeline.Mode) {
} }
} ?: -1 } ?: -1
if (roomCreationEventIndex == -1 && selfUserJoinedEventIndex == -1) {
return items
}
// Remove items at the indices we found // Remove items at the indices we found
val newItems = items.toMutableList() val newItems = items.toMutableList()
if (selfUserJoinedEventIndex in newItems.indices) { if (selfUserJoinedEventIndex in newItems.indices) {

3
libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt

@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.roomlist
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.sun.jna.Pointer import com.sun.jna.Pointer
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.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.A_ROOM_ID_2
@ -215,6 +216,7 @@ private fun aRustRoomInfo(
numUnreadNotifications: ULong = 0uL, numUnreadNotifications: ULong = 0uL,
numUnreadMentions: ULong = 0uL, numUnreadMentions: ULong = 0uL,
pinnedEventIds: List<String> = listOf(), pinnedEventIds: List<String> = listOf(),
roomCreator: UserId? = null,
) = RoomInfo( ) = RoomInfo(
id = id, id = id,
displayName = displayName, displayName = displayName,
@ -245,6 +247,7 @@ private fun aRustRoomInfo(
numUnreadNotifications = numUnreadNotifications, numUnreadNotifications = numUnreadNotifications,
numUnreadMentions = numUnreadMentions, numUnreadMentions = numUnreadMentions,
pinnedEventIds = pinnedEventIds, pinnedEventIds = pinnedEventIds,
creator = roomCreator?.value,
) )
class FakeRoomListItem( class FakeRoomListItem(

157
libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt

@ -22,85 +22,174 @@ import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import org.junit.Test import org.junit.Test
class RoomBeginningPostProcessorTest { class RoomBeginningPostProcessorTest {
private val roomCreateEvent = MatrixTimelineItem.Event(
uniqueId = UniqueId("m.room.create"),
event = anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))
)
private val roomCreatorJoinEvent = MatrixTimelineItem.Event(
uniqueId = UniqueId("m.room.member"),
event = anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))
)
private val otherMemberJoinEvent = MatrixTimelineItem.Event(
uniqueId = UniqueId("m.room.member_other"),
event = anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))
)
private val messageEvent = MatrixTimelineItem.Event(
uniqueId = UniqueId("m.room.message"),
event = anEventTimelineItem(content = aMessageContent("hi"))
)
@Test
fun `processor returns empty list when empty list is provided`() {
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(
items = emptyList(),
isDm = true,
roomCreator = A_USER_ID,
hasMoreToLoadBackwards = false,
)
assertThat(processedItems).isEmpty()
}
@Test
fun `processor returns the provided list when it only contains a message`() {
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(
items = listOf(messageEvent),
isDm = true,
roomCreator = A_USER_ID,
hasMoreToLoadBackwards = false,
)
assertThat(processedItems).isEqualTo(listOf(messageEvent))
}
@Test
fun `processor returns the provided list when it only contains a message and the roomCreator is not provided`() {
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(
items = listOf(messageEvent),
isDm = true,
roomCreator = null,
hasMoreToLoadBackwards = false,
)
assertThat(processedItems).isEqualTo(listOf(messageEvent))
}
@Test @Test
fun `processor removes room creation event and self-join event from DM timeline`() { fun `processor removes room creation event and self-join event from DM timeline`() {
val timelineItems = listOf( val timelineItems = listOf(
MatrixTimelineItem.Event(UniqueId("m.room.create"), anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))), roomCreateEvent,
MatrixTimelineItem.Event(UniqueId("m.room.member"), anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))), roomCreatorJoinEvent,
) )
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE) val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = false) val processedItems = processor.process(
items = timelineItems,
isDm = true,
roomCreator = A_USER_ID,
hasMoreToLoadBackwards = false,
)
assertThat(processedItems).isEmpty() assertThat(processedItems).isEmpty()
} }
@Test
fun `processor does not remove anything with PINNED_EVENTS mode`() {
val timelineItems = listOf(
roomCreateEvent,
roomCreatorJoinEvent,
)
val processor = RoomBeginningPostProcessor(Timeline.Mode.PINNED_EVENTS)
val processedItems = processor.process(
items = timelineItems,
isDm = true,
roomCreator = A_USER_ID,
hasMoreToLoadBackwards = false,
)
assertThat(processedItems).isEqualTo(timelineItems)
}
@Test @Test
fun `processor removes room creation event and self-join event from DM timeline even if they're not the first items`() { fun `processor removes room creation event and self-join event from DM timeline even if they're not the first items`() {
val timelineItems = listOf( val timelineItems = listOf(
MatrixTimelineItem.Event( otherMemberJoinEvent,
UniqueId("m.room.member_other"), roomCreateEvent,
anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED)) messageEvent,
), roomCreatorJoinEvent,
MatrixTimelineItem.Event(UniqueId("m.room.create"), anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
MatrixTimelineItem.Event(UniqueId("m.room.message"), anEventTimelineItem(content = aMessageContent("hi"))),
MatrixTimelineItem.Event(UniqueId("m.room.member"), anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))),
) )
val expected = listOf( val expected = listOf(
MatrixTimelineItem.Event( otherMemberJoinEvent,
UniqueId("m.room.member_other"), messageEvent,
anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))
),
MatrixTimelineItem.Event(UniqueId("m.room.message"), anEventTimelineItem(content = aMessageContent("hi"))),
) )
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE) val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = false) val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = false)
assertThat(processedItems).isEqualTo(expected) assertThat(processedItems).isEqualTo(expected)
} }
@Test @Test
fun `processor will add beginning of room item if it's not a DM`() { fun `processor will add beginning of room item if it's not a DM`() {
val timelineItems = listOf( val timelineItems = listOf(
MatrixTimelineItem.Event(UniqueId("m.room.create"), anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))), roomCreateEvent,
MatrixTimelineItem.Event(UniqueId("m.room.member"), anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))), roomCreatorJoinEvent,
) )
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE) val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(timelineItems, isDm = false, hasMoreToLoadBackwards = false) val processedItems = processor.process(timelineItems, isDm = false, roomCreator = A_USER_ID, hasMoreToLoadBackwards = false)
assertThat(processedItems).isEqualTo( assertThat(processedItems).isEqualTo(
listOf(processor.createRoomBeginningItem()) + timelineItems listOf(processor.createRoomBeginningItem()) + timelineItems
) )
} }
@Test @Test
fun `processor won't remove items if it's not at the start of the timeline`() { fun `processor will not add beginning of room item if it's not a DM but the room has more to load`() {
val timelineItems = listOf( val timelineItems = listOf(
MatrixTimelineItem.Event(UniqueId("m.room.create"), anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))), roomCreateEvent,
MatrixTimelineItem.Event(UniqueId("m.room.member"), anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))), roomCreatorJoinEvent,
) )
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE) val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true) val processedItems = processor.process(timelineItems, isDm = false, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true)
assertThat(processedItems).isEqualTo(timelineItems) assertThat(processedItems).isEqualTo(timelineItems)
} }
@Test @Test
fun `processor won't remove the first member join event if it can't find the room creation event`() { fun `processor will add beginning of room item if it's not a DM, when the parameter roomCreator is null`() {
val timelineItems = listOf( val timelineItems = listOf(
MatrixTimelineItem.Event(UniqueId("m.room.member"), anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED))), roomCreateEvent,
roomCreatorJoinEvent,
) )
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE) val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true) val processedItems = processor.process(timelineItems, isDm = false, roomCreator = null, hasMoreToLoadBackwards = false)
assertThat(processedItems).isEqualTo(timelineItems) assertThat(processedItems).isEqualTo(
listOf(processor.createRoomBeginningItem()) + timelineItems
)
}
@Test
fun `processor removes items event it's not at the start of the timeline`() {
val timelineItems = listOf(
roomCreateEvent,
roomCreatorJoinEvent,
)
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true)
assertThat(processedItems).isEmpty()
}
@Test
fun `processor removes the first member join event if it matches the roomCreator parameter`() {
val timelineItems = listOf(
roomCreatorJoinEvent,
)
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true)
assertThat(processedItems).isEmpty()
} }
@Test @Test
fun `processor won't remove the first member join event if it's not from the room creator`() { fun `processor won't remove the first member join event if it's not from the room creator`() {
val timelineItems = listOf( val timelineItems = listOf(
MatrixTimelineItem.Event(UniqueId("m.room.create"), anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))), roomCreateEvent,
MatrixTimelineItem.Event( otherMemberJoinEvent,
UniqueId("m.room.member"),
anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, null, MembershipChange.JOINED))
),
) )
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE) val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true) val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true)
assertThat(processedItems).isEqualTo(timelineItems) assertThat(processedItems).isEqualTo(listOf(otherMemberJoinEvent))
} }
} }

2
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt

@ -523,6 +523,7 @@ fun aRoomInfo(
activeRoomCallParticipants: List<String> = emptyList(), activeRoomCallParticipants: List<String> = emptyList(),
heroes: List<MatrixUser> = emptyList(), heroes: List<MatrixUser> = emptyList(),
pinnedEventIds: List<EventId> = emptyList(), pinnedEventIds: List<EventId> = emptyList(),
roomCreator: UserId? = null,
) = MatrixRoomInfo( ) = MatrixRoomInfo(
id = id, id = id,
name = name, name = name,
@ -549,6 +550,7 @@ fun aRoomInfo(
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(), activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
heroes = heroes.toImmutableList(), heroes = heroes.toImmutableList(),
pinnedEventIds = pinnedEventIds.toImmutableList(), pinnedEventIds = pinnedEventIds.toImmutableList(),
creator = roomCreator,
) )
fun defaultRoomPowerLevels() = MatrixRoomPowerLevels( fun defaultRoomPowerLevels() = MatrixRoomPowerLevels(

Loading…
Cancel
Save