Remove some state events at the start of DMs (#2252)
* Remove some initial events for DMs
This commit is contained in:
parent
854622b85f
commit
ac3201b317
5 changed files with 175 additions and 1 deletions
1
changelog.d/2217.misc
Normal file
1
changelog.d/2217.misc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Remove room creation, self-join of room creator and 'this is the beginning of X' timeline items for DMs.
|
||||||
|
|
@ -145,7 +145,7 @@ fun TimelineView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (state.paginationState.beginningOfRoomReached) {
|
if (state.paginationState.beginningOfRoomReached && !state.timelineRoomInfo.isDirect) {
|
||||||
item(contentType = "BeginningOfRoomReached") {
|
item(contentType = "BeginningOfRoomReached") {
|
||||||
TimelineItemRoomBeginningView(roomName = roomName)
|
TimelineItemRoomBeginningView(roomName = roomName)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessage
|
||||||
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
|
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
|
||||||
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
|
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
|
||||||
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
|
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
|
||||||
|
import io.element.android.libraries.matrix.impl.timeline.postprocessor.DmBeginningTimelineProcessor
|
||||||
import io.element.android.libraries.matrix.impl.timeline.postprocessor.FilterHiddenStateEventsProcessor
|
import io.element.android.libraries.matrix.impl.timeline.postprocessor.FilterHiddenStateEventsProcessor
|
||||||
import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor
|
import io.element.android.libraries.matrix.impl.timeline.postprocessor.TimelineEncryptedHistoryPostProcessor
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
|
@ -85,6 +86,8 @@ class RustMatrixTimeline(
|
||||||
|
|
||||||
private val filterHiddenStateEventsProcessor = FilterHiddenStateEventsProcessor()
|
private val filterHiddenStateEventsProcessor = FilterHiddenStateEventsProcessor()
|
||||||
|
|
||||||
|
private val dmBeginningTimelineProcessor = DmBeginningTimelineProcessor()
|
||||||
|
|
||||||
private val timelineItemFactory = MatrixTimelineItemMapper(
|
private val timelineItemFactory = MatrixTimelineItemMapper(
|
||||||
fetchDetailsForEvent = this::fetchDetailsForEvent,
|
fetchDetailsForEvent = this::fetchDetailsForEvent,
|
||||||
roomCoroutineScope = roomCoroutineScope,
|
roomCoroutineScope = roomCoroutineScope,
|
||||||
|
|
@ -107,6 +110,13 @@ class RustMatrixTimeline(
|
||||||
override val timelineItems: Flow<List<MatrixTimelineItem>> = _timelineItems
|
override val timelineItems: Flow<List<MatrixTimelineItem>> = _timelineItems
|
||||||
.mapLatest { items -> encryptedHistoryPostProcessor.process(items) }
|
.mapLatest { items -> encryptedHistoryPostProcessor.process(items) }
|
||||||
.mapLatest { items -> filterHiddenStateEventsProcessor.process(items) }
|
.mapLatest { items -> filterHiddenStateEventsProcessor.process(items) }
|
||||||
|
.mapLatest { items ->
|
||||||
|
dmBeginningTimelineProcessor.process(
|
||||||
|
items = items,
|
||||||
|
isDm = matrixRoom.isDirect && matrixRoom.isOneToOne,
|
||||||
|
isAtStartOfTimeline = paginationState.value.beginningOfRoomReached
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Timber.d("Initialize timeline for room ${matrixRoom.roomId}")
|
Timber.d("Initialize timeline for room ${matrixRoom.roomId}")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.timeline.postprocessor
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||||
|
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.RoomMembershipContent
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
class DmBeginningTimelineProcessor {
|
||||||
|
fun process(
|
||||||
|
items: List<MatrixTimelineItem>,
|
||||||
|
isDm: Boolean,
|
||||||
|
isAtStartOfTimeline: Boolean
|
||||||
|
): List<MatrixTimelineItem> {
|
||||||
|
if (!isDm || !isAtStartOfTimeline) return items
|
||||||
|
|
||||||
|
// Find room creation event. This is usually index 0
|
||||||
|
val roomCreationEventIndex = items.indexOfFirst {
|
||||||
|
val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? StateContent
|
||||||
|
stateEventContent?.content is OtherState.RoomCreate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find self-join event for room creator. This is usually index 1
|
||||||
|
val roomCreatorUserId = (items.getOrNull(roomCreationEventIndex) as? MatrixTimelineItem.Event)?.event?.sender
|
||||||
|
val selfUserJoinedEventIndex = roomCreatorUserId?.let { creatorUserId ->
|
||||||
|
items.indexOfFirst {
|
||||||
|
val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? RoomMembershipContent
|
||||||
|
stateEventContent?.change == MembershipChange.JOINED && stateEventContent.userId == creatorUserId
|
||||||
|
}
|
||||||
|
} ?: -1
|
||||||
|
|
||||||
|
// Remove items at the indices we found
|
||||||
|
val newItems = items.toMutableList()
|
||||||
|
if (selfUserJoinedEventIndex in newItems.indices) {
|
||||||
|
newItems.removeAt(selfUserJoinedEventIndex)
|
||||||
|
}
|
||||||
|
if (roomCreationEventIndex in newItems.indices) {
|
||||||
|
newItems.removeAt(roomCreationEventIndex)
|
||||||
|
}
|
||||||
|
return newItems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.impl.timeline.postprocessor
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||||
|
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.RoomMembershipContent
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
|
||||||
|
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.timeline.aMessageContent
|
||||||
|
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class DmBeginningTimelineProcessorTest {
|
||||||
|
@Test
|
||||||
|
fun `processor removes room creation event and self-join event from DM timeline`() {
|
||||||
|
val timelineItems = listOf(
|
||||||
|
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||||
|
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
|
||||||
|
)
|
||||||
|
val processor = DmBeginningTimelineProcessor()
|
||||||
|
val processedItems = processor.process(timelineItems, isDm = true, isAtStartOfTimeline = true)
|
||||||
|
assertThat(processedItems).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `processor removes room creation event and self-join event from DM timeline even if they're not the first items`() {
|
||||||
|
val timelineItems = listOf(
|
||||||
|
MatrixTimelineItem.Event("m.room.member_other", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, MembershipChange.JOINED))),
|
||||||
|
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||||
|
MatrixTimelineItem.Event("m.room.message", anEventTimelineItem(content = aMessageContent("hi"))),
|
||||||
|
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
|
||||||
|
)
|
||||||
|
val expected = listOf(
|
||||||
|
MatrixTimelineItem.Event("m.room.member_other", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, MembershipChange.JOINED))),
|
||||||
|
MatrixTimelineItem.Event("m.room.message", anEventTimelineItem(content = aMessageContent("hi"))),
|
||||||
|
)
|
||||||
|
val processor = DmBeginningTimelineProcessor()
|
||||||
|
val processedItems = processor.process(timelineItems, isDm = true, isAtStartOfTimeline = true)
|
||||||
|
assertThat(processedItems).isEqualTo(expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `processor won't remove items if it's not a DM`() {
|
||||||
|
val timelineItems = listOf(
|
||||||
|
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||||
|
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
|
||||||
|
)
|
||||||
|
val processor = DmBeginningTimelineProcessor()
|
||||||
|
val processedItems = processor.process(timelineItems, isDm = false, isAtStartOfTimeline = true)
|
||||||
|
assertThat(processedItems).isEqualTo(timelineItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `processor won't remove items if it's not at the start of the timeline`() {
|
||||||
|
val timelineItems = listOf(
|
||||||
|
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||||
|
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
|
||||||
|
)
|
||||||
|
val processor = DmBeginningTimelineProcessor()
|
||||||
|
val processedItems = processor.process(timelineItems, isDm = true, isAtStartOfTimeline = false)
|
||||||
|
assertThat(processedItems).isEqualTo(timelineItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `processor won't remove the first member join event if it can't find the room creation event`() {
|
||||||
|
val timelineItems = listOf(
|
||||||
|
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))),
|
||||||
|
)
|
||||||
|
val processor = DmBeginningTimelineProcessor()
|
||||||
|
val processedItems = processor.process(timelineItems, isDm = true, isAtStartOfTimeline = false)
|
||||||
|
assertThat(processedItems).isEqualTo(timelineItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `processor won't remove the first member join event if it's not from the room creator`() {
|
||||||
|
val timelineItems = listOf(
|
||||||
|
MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))),
|
||||||
|
MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, MembershipChange.JOINED))),
|
||||||
|
)
|
||||||
|
val processor = DmBeginningTimelineProcessor()
|
||||||
|
val processedItems = processor.process(timelineItems, isDm = true, isAtStartOfTimeline = false)
|
||||||
|
assertThat(processedItems).isEqualTo(timelineItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue