Show jump-to-unread when read marker is outside the loaded window

When the m.fully_read marker event is older than the loaded timeline
window, no virtual ReadMarker item is inserted and the chevron-up FAB
silently no-ops. Surfaces the SDK's new fully_read_event_id on RoomInfo
and routes the tap through the existing FocusOnEvent flow so the FAB
appears in catch-up scenarios and lands the user on the marker.

Bumps matrix-rust-components-kotlin to 26.05.20 to pick up the FFI
field; absorbs the ClientBuildException.InvalidRawKey and suspend
SpaceRoomList.rooms / subscribeToRoomUpdate API breaks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jenna Vassar
2026-05-21 07:11:53 -07:00
parent 257040f5ca
commit bdb42759de
15 changed files with 254 additions and 39 deletions
@@ -291,20 +291,32 @@ class TimelinePresenter(
// without changing the list length, e.g. when [markRoomAsFullyRead] is sent while at the
// bottom of the room.
//
// Limitation: when the read marker is outside the loaded window (gaps, pagination), this
// returns null and the jump-to-unread button stays hidden. Proper fix needs an SDK
// accessor for the m.fully_read marker plus FocusedOnEvent navigation on click; gated
// behind FeatureFlags.JumpToUnread until that lands.
val readMarkerIndex = remember { mutableStateOf<Int?>(null) }
LaunchedEffect(timelineItems, displayJumpToUnread) {
// The state has three shapes:
// - InWindow: the SDK has materialised a virtual ReadMarker item in the loaded window;
// tapping the FAB smoothly scrolls to its index.
// - OutOfWindow: the marker event is older than the loaded window, so the SDK gives us
// only the event id via RoomInfo.fullyReadEventId; tapping triggers a focused-event
// load via the existing TimelineEvent.FocusOnEvent path.
// - Hidden: feature flag off, no marker, caught-up (marker loaded but no virtual item),
// or initial load (no items yet).
val jumpToUnread = remember { mutableStateOf<JumpToUnreadState>(JumpToUnreadState.Hidden) }
LaunchedEffect(timelineItems, displayJumpToUnread, roomInfo.fullyReadEventId) {
if (!displayJumpToUnread) {
readMarkerIndex.value = null
jumpToUnread.value = JumpToUnreadState.Hidden
return@LaunchedEffect
}
val items = timelineItems
readMarkerIndex.value = withContext(dispatchers.computation) {
items.indexOfFirst { (it as? TimelineItem.Virtual)?.model is TimelineItemReadMarkerModel }
.takeIf { it >= 0 }
val fullyReadEventId = roomInfo.fullyReadEventId
jumpToUnread.value = withContext(dispatchers.computation) {
val markerIndex = items.indexOfFirst {
(it as? TimelineItem.Virtual)?.model is TimelineItemReadMarkerModel
}
when {
markerIndex >= 0 -> JumpToUnreadState.InWindow(markerIndex)
fullyReadEventId != null && items.isNotEmpty() && !timelineItemIndexer.isKnown(fullyReadEventId) ->
JumpToUnreadState.OutOfWindow(fullyReadEventId)
else -> JumpToUnreadState.Hidden
}
}
}
@@ -358,7 +370,7 @@ class TimelinePresenter(
displayThreadSummaries = displayThreadSummaries,
displayFloatingDateBadge = displayFloatingDateBadge,
displayJumpToUnread = displayJumpToUnread,
readMarkerIndex = readMarkerIndex.value,
jumpToUnread = jumpToUnread.value,
eventSink = ::handleEvent,
)
}
@@ -36,7 +36,7 @@ data class TimelineState(
val displayThreadSummaries: Boolean,
val displayFloatingDateBadge: Boolean,
val displayJumpToUnread: Boolean,
val readMarkerIndex: Int?,
val jumpToUnread: JumpToUnreadState,
val eventSink: (TimelineEvent) -> Unit,
) {
private val lastTimelineEvent = timelineItems.firstOrNull { it is TimelineItem.Event } as? TimelineItem.Event
@@ -85,3 +85,18 @@ data class TimelineRoomInfo(
val typingNotificationState: TypingNotificationState,
val predecessorRoom: PredecessorRoom?,
)
/**
* Whether the jump-to-unread FAB should be shown, and if so, how tapping it
* should bring the user to the read marker.
*/
@Immutable
sealed interface JumpToUnreadState {
data object Hidden : JumpToUnreadState
/** The read marker is materialised at [index] in the loaded timeline — smooth scroll to it. */
data class InWindow(val index: Int) : JumpToUnreadState
/** The read marker event is older than the loaded window — load it via focused-event navigation. */
data class OutOfWindow(val eventId: EventId) : JumpToUnreadState
}
@@ -60,7 +60,7 @@ fun aTimelineState(
displayThreadSummaries: Boolean = false,
displayFloatingDateBadge: Boolean = false,
displayJumpToUnread: Boolean = false,
readMarkerIndex: Int? = null,
jumpToUnread: JumpToUnreadState = JumpToUnreadState.Hidden,
newEventState: NewEventState = NewEventState.None,
eventSink: (TimelineEvent) -> Unit = {},
): TimelineState {
@@ -83,7 +83,7 @@ fun aTimelineState(
displayThreadSummaries = displayThreadSummaries,
displayFloatingDateBadge = displayFloatingDateBadge,
displayJumpToUnread = displayJumpToUnread,
readMarkerIndex = readMarkerIndex,
jumpToUnread = jumpToUnread,
eventSink = eventSink,
)
}
@@ -155,6 +155,10 @@ fun TimelineView(
state.eventSink(TimelineEvent.MarkAllAsRead)
}
fun onFocusOnEvent(eventId: EventId) {
state.eventSink(TimelineEvent.FocusOnEvent(eventId))
}
val context = LocalContext.current
val toastMessage = stringResource(CommonStrings.common_copied_to_clipboard)
val view = LocalView.current
@@ -240,11 +244,12 @@ fun TimelineView(
isLive = state.isLive,
focusRequestState = state.focusRequestState,
displayJumpToUnread = state.displayJumpToUnread,
readMarkerIndex = state.readMarkerIndex,
jumpToUnread = state.jumpToUnread,
onScrollFinishAt = ::onScrollFinishAt,
onJumpToLive = ::onJumpToLive,
onFocusEventRender = ::onFocusEventRender,
onMarkAllAsRead = ::onMarkAllAsRead,
onFocusOnEvent = ::onFocusOnEvent,
)
if (state.displayFloatingDateBadge && useReverseLayout) {
@@ -326,11 +331,12 @@ private fun BoxScope.TimelineScrollHelper(
forceJumpToReadMarkerVisibility: Boolean,
focusRequestState: FocusRequestState,
displayJumpToUnread: Boolean,
readMarkerIndex: Int?,
jumpToUnread: JumpToUnreadState,
onScrollFinishAt: (Int) -> Unit,
onJumpToLive: () -> Unit,
onFocusEventRender: () -> Unit,
onMarkAllAsRead: () -> Unit,
onFocusOnEvent: (EventId) -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
val isScrollFinished by remember { derivedStateOf { !lazyListState.isScrollInProgress } }
@@ -339,12 +345,19 @@ private fun BoxScope.TimelineScrollHelper(
lazyListState.firstVisibleItemIndex < 3 && isLive
}
}
val isJumpToUnreadVisible by remember(readMarkerIndex, forceJumpToReadMarkerVisibility) {
val isJumpToUnreadVisible by remember(jumpToUnread, forceJumpToReadMarkerVisibility) {
derivedStateOf {
if (forceJumpToReadMarkerVisibility) return@derivedStateOf true
val markerIndex = readMarkerIndex ?: return@derivedStateOf false
val lastVisibleIndex = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: return@derivedStateOf false
markerIndex > lastVisibleIndex
when (val jtu = jumpToUnread) {
JumpToUnreadState.Hidden -> false
// Marker is outside the loaded window — we have no on-screen anchor, so just show.
is JumpToUnreadState.OutOfWindow -> true
// Marker is in the loaded window — hide once it's scrolled into the visible range.
is JumpToUnreadState.InWindow -> {
val lastVisibleIndex = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: return@derivedStateOf false
jtu.index > lastVisibleIndex
}
}
}
}
val isJumpToBottomVisible = !canAutoScroll || forceJumpToBottomVisibility || !isLive
@@ -375,9 +388,12 @@ private fun BoxScope.TimelineScrollHelper(
}
fun jumpToReadMarker() {
val markerIndex = readMarkerIndex ?: return
coroutineScope.launch {
lazyListState.animateScrollToItemCenter(markerIndex)
when (val jtu = jumpToUnread) {
JumpToUnreadState.Hidden -> Unit
is JumpToUnreadState.InWindow -> coroutineScope.launch {
lazyListState.animateScrollToItemCenter(jtu.index)
}
is JumpToUnreadState.OutOfWindow -> onFocusOnEvent(jtu.eventId)
}
}
@@ -630,7 +646,7 @@ private fun TimelineViewWithReadMarker(
// Index points past the loaded items, mirroring the real-world state the FAB
// represents: the user has scrolled past the read marker, so it's no longer in
// view. The actual scroll target doesn't matter for a static preview.
readMarkerIndex = if (hasUnreadAbove) timelineItems.size else null,
jumpToUnread = if (hasUnreadAbove) JumpToUnreadState.InWindow(timelineItems.size) else JumpToUnreadState.Hidden,
newEventState = if (hasUnreadBelow) NewEventState.FromOther else NewEventState.None,
),
timelineProtectionState = aTimelineProtectionState(),
@@ -56,6 +56,7 @@ import io.element.android.libraries.matrix.test.A_UNIQUE_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
@@ -371,7 +372,7 @@ class TimelinePresenterTest {
}
@Test
fun `present - readMarkerIndex points at the read marker virtual item`() = runTest {
fun `present - jumpToUnread is InWindow at the read marker virtual item index`() = runTest {
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
val timeline = FakeTimeline(timelineItems = timelineItems)
val presenter = createTimelinePresenter(
@@ -391,8 +392,8 @@ class TimelinePresenterTest {
MatrixTimelineItem.Event(UniqueId("msg-newest"), anEventTimelineItem(content = aMessageContent())),
)
)
consumeItemsUntilPredicate { it.readMarkerIndex != null }.last().also { state ->
assertThat(state.readMarkerIndex).isEqualTo(3)
consumeItemsUntilPredicate { it.jumpToUnread is JumpToUnreadState.InWindow }.last().also { state ->
assertThat(state.jumpToUnread).isEqualTo(JumpToUnreadState.InWindow(index = 3))
assertThat(state.displayJumpToUnread).isTrue()
}
cancelAndIgnoreRemainingEvents()
@@ -400,7 +401,7 @@ class TimelinePresenterTest {
}
@Test
fun `present - readMarkerIndex is null when no read marker present`() = runTest {
fun `present - jumpToUnread is Hidden when no read marker and no fullyReadEventId`() = runTest {
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
val timeline = FakeTimeline(timelineItems = timelineItems)
val presenter = createTimelinePresenter(
@@ -416,16 +417,16 @@ class TimelinePresenterTest {
)
)
consumeItemsUntilPredicate {
it.timelineItems.size == 2 && it.readMarkerIndex == null
it.timelineItems.size == 2 && it.jumpToUnread == JumpToUnreadState.Hidden
}.last().also { state ->
assertThat(state.readMarkerIndex).isNull()
assertThat(state.jumpToUnread).isEqualTo(JumpToUnreadState.Hidden)
}
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - readMarkerIndex stays null when JumpToUnread feature flag is disabled`() = runTest {
fun `present - jumpToUnread is Hidden when JumpToUnread feature flag is disabled`() = runTest {
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
val timeline = FakeTimeline(timelineItems = timelineItems)
val presenter = createTimelinePresenter(
@@ -443,7 +444,7 @@ class TimelinePresenterTest {
)
consumeItemsUntilPredicate { it.timelineItems.size == 3 }.last().also { state ->
assertThat(state.displayJumpToUnread).isFalse()
assertThat(state.readMarkerIndex).isNull()
assertThat(state.jumpToUnread).isEqualTo(JumpToUnreadState.Hidden)
}
cancelAndIgnoreRemainingEvents()
}
@@ -579,7 +580,7 @@ class TimelinePresenterTest {
}
@Test
fun `present - readMarkerIndex is 0 when the read marker is the only item`() = runTest {
fun `present - jumpToUnread is InWindow at index 0 when the read marker is the only item`() = runTest {
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
val timeline = FakeTimeline(timelineItems = timelineItems)
val presenter = createTimelinePresenter(
@@ -591,8 +592,163 @@ class TimelinePresenterTest {
timelineItems.emit(
listOf(MatrixTimelineItem.Virtual(UniqueId("read-marker"), VirtualTimelineItem.ReadMarker))
)
consumeItemsUntilPredicate { it.readMarkerIndex != null }.last().also { state ->
assertThat(state.readMarkerIndex).isEqualTo(0)
consumeItemsUntilPredicate { it.jumpToUnread is JumpToUnreadState.InWindow }.last().also { state ->
assertThat(state.jumpToUnread).isEqualTo(JumpToUnreadState.InWindow(index = 0))
}
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - jumpToUnread is OutOfWindow when fullyReadEventId is set but not in the loaded window`() = runTest {
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
val timeline = FakeTimeline(timelineItems = timelineItems)
val fullyReadEventId = EventId("\$older-than-loaded-window")
val room = FakeJoinedRoom(
liveTimeline = timeline,
baseRoom = FakeBaseRoom(
roomPermissions = roomPermissions(),
initialRoomInfo = aRoomInfo(fullyReadEventId = fullyReadEventId),
),
)
val presenter = createTimelinePresenter(
timeline = timeline,
room = room,
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.JumpToUnread.key to true)),
)
presenter.test {
awaitFirstItem()
// Loaded items don't include the fullyReadEventId and the SDK didn't materialise a ReadMarker.
timelineItems.emit(
listOf(
MatrixTimelineItem.Event(UniqueId("1"), anEventTimelineItem(eventId = AN_EVENT_ID, content = aMessageContent())),
MatrixTimelineItem.Event(UniqueId("2"), anEventTimelineItem(eventId = AN_EVENT_ID_2, content = aMessageContent())),
)
)
consumeItemsUntilPredicate { it.jumpToUnread is JumpToUnreadState.OutOfWindow }.last().also { state ->
assertThat(state.jumpToUnread).isEqualTo(JumpToUnreadState.OutOfWindow(eventId = fullyReadEventId))
}
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - jumpToUnread is Hidden when fullyReadEventId IS in the loaded window`() = runTest {
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
val timeline = FakeTimeline(timelineItems = timelineItems)
val room = FakeJoinedRoom(
liveTimeline = timeline,
baseRoom = FakeBaseRoom(
roomPermissions = roomPermissions(),
// The user is caught up: the marker event is loaded, but the SDK didn't insert a
// virtual ReadMarker because there are no items newer than it.
initialRoomInfo = aRoomInfo(fullyReadEventId = AN_EVENT_ID),
),
)
val presenter = createTimelinePresenter(
timeline = timeline,
room = room,
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.JumpToUnread.key to true)),
)
presenter.test {
awaitFirstItem()
// A loaded item has eventId == AN_EVENT_ID (default of anEventTimelineItem).
timelineItems.emit(
listOf(
MatrixTimelineItem.Event(UniqueId("1"), anEventTimelineItem(content = aMessageContent())),
)
)
consumeItemsUntilPredicate { it.timelineItems.size == 1 }.last().also { state ->
assertThat(state.jumpToUnread).isEqualTo(JumpToUnreadState.Hidden)
}
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - jumpToUnread is Hidden when fullyReadEventId is set but the timeline is empty`() = runTest {
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
val timeline = FakeTimeline(timelineItems = timelineItems)
val room = FakeJoinedRoom(
liveTimeline = timeline,
baseRoom = FakeBaseRoom(
roomPermissions = roomPermissions(),
initialRoomInfo = aRoomInfo(fullyReadEventId = AN_EVENT_ID),
),
)
val presenter = createTimelinePresenter(
timeline = timeline,
room = room,
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.JumpToUnread.key to true)),
)
presenter.test {
val initialState = awaitFirstItem()
// Without any timeline items, the FAB must stay hidden — the user is mid-load.
assertThat(initialState.jumpToUnread).isEqualTo(JumpToUnreadState.Hidden)
advanceUntilIdle()
val drained = consumeItemsUntilTimeout()
assertThat(drained.any { it.jumpToUnread != JumpToUnreadState.Hidden }).isFalse()
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - jumpToUnread is Hidden when fullyReadEventId is set but the feature flag is off`() = runTest {
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
val timeline = FakeTimeline(timelineItems = timelineItems)
val room = FakeJoinedRoom(
liveTimeline = timeline,
baseRoom = FakeBaseRoom(
roomPermissions = roomPermissions(),
initialRoomInfo = aRoomInfo(fullyReadEventId = AN_EVENT_ID),
),
)
val presenter = createTimelinePresenter(
timeline = timeline,
room = room,
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.JumpToUnread.key to false)),
)
presenter.test {
awaitFirstItem()
timelineItems.emit(
listOf(
MatrixTimelineItem.Event(UniqueId("1"), anEventTimelineItem(content = aMessageContent())),
)
)
consumeItemsUntilPredicate { it.timelineItems.size == 1 }.last().also { state ->
assertThat(state.jumpToUnread).isEqualTo(JumpToUnreadState.Hidden)
}
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - jumpToUnread prefers InWindow when both a virtual marker and fullyReadEventId are present`() = runTest {
val timelineItems = MutableStateFlow(emptyList<MatrixTimelineItem>())
val timeline = FakeTimeline(timelineItems = timelineItems)
val room = FakeJoinedRoom(
liveTimeline = timeline,
baseRoom = FakeBaseRoom(
roomPermissions = roomPermissions(),
initialRoomInfo = aRoomInfo(fullyReadEventId = AN_EVENT_ID),
),
)
val presenter = createTimelinePresenter(
timeline = timeline,
room = room,
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.JumpToUnread.key to true)),
)
presenter.test {
awaitFirstItem()
timelineItems.emit(
listOf(
MatrixTimelineItem.Event(UniqueId("msg-old"), anEventTimelineItem(content = aMessageContent())),
MatrixTimelineItem.Virtual(UniqueId("read-marker"), VirtualTimelineItem.ReadMarker),
MatrixTimelineItem.Event(UniqueId("msg-newest"), anEventTimelineItem(content = aMessageContent())),
)
)
consumeItemsUntilPredicate { it.jumpToUnread is JumpToUnreadState.InWindow }.last().also { state ->
assertThat(state.jumpToUnread).isInstanceOf(JumpToUnreadState.InWindow::class.java)
}
cancelAndIgnoreRemainingEvents()
}
@@ -131,6 +131,7 @@ private fun aSpaceInfo(
numUnreadMessages = 0,
numUnreadNotifications = 0,
numUnreadMentions = 0,
fullyReadEventId = null,
heroes = persistentListOf(),
pinnedEventIds = persistentListOf(),
creators = persistentListOf(),
+1 -1
View File
@@ -178,7 +178,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
# All new features should not be implemented in the pull request that upgrades the version, developers should
# only fix API breaks and may add some TODOs.
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.05.7"
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.05.20"
# Others
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
@@ -70,6 +70,11 @@ data class RoomInfo(
* notification settings.
*/
val numUnreadMentions: Long,
/**
* Event ID of the user's `m.fully_read` marker for this room, if any.
* Can be set even when the event is older than the loaded timeline window.
*/
val fullyReadEventId: EventId?,
val heroes: ImmutableList<MatrixUser>,
val pinnedEventIds: ImmutableList<EventId>,
val creators: ImmutableList<UserId>,
@@ -28,6 +28,7 @@ fun Throwable.mapAuthenticationException(): AuthenticationException {
}
is ClientBuildException.WellKnownLookupFailed -> AuthenticationException.Generic(message)
is ClientBuildException.EventCache -> AuthenticationException.Generic(message)
is ClientBuildException.InvalidRawKey -> AuthenticationException.Generic(message)
}
is OAuthException -> when (this) {
is OAuthException.Generic -> AuthenticationException.OAuth(message)
@@ -71,6 +71,7 @@ class RoomInfoMapper {
numUnreadMessages = it.numUnreadMessages.toLong(),
numUnreadMentions = it.numUnreadMentions.toLong(),
numUnreadNotifications = it.numUnreadNotifications.toLong(),
fullyReadEventId = it.fullyReadEventId?.let(::EventId),
historyVisibility = it.historyVisibility.map(),
successorRoom = it.successorRoom?.map(),
roomVersion = it.roomVersion,
@@ -52,6 +52,7 @@ internal fun aRustRoomInfo(
numUnreadMessages: ULong = 0uL,
numUnreadNotifications: ULong = 0uL,
numUnreadMentions: ULong = 0uL,
fullyReadEventId: String? = null,
pinnedEventIds: List<String> = listOf(),
roomCreators: List<String>? = emptyList(),
joinRule: JoinRule? = null,
@@ -93,6 +94,7 @@ internal fun aRustRoomInfo(
numUnreadMessages = numUnreadMessages,
numUnreadNotifications = numUnreadNotifications,
numUnreadMentions = numUnreadMentions,
fullyReadEventId = fullyReadEventId,
pinnedEventIds = pinnedEventIds,
creators = roomCreators,
joinRule = joinRule,
@@ -40,8 +40,8 @@ class FakeFfiSpaceRoomList(
return paginationStateResult()
}
override fun rooms(): List<SpaceRoom> {
return roomsResult()
override suspend fun rooms(): List<SpaceRoom> = simulateLongTask {
roomsResult()
}
override fun subscribeToPaginationStateUpdates(listener: SpaceRoomListPaginationStateListener): TaskHandle {
@@ -53,7 +53,7 @@ class FakeFfiSpaceRoomList(
spaceRoomListPaginationStateListener?.onUpdate(state)
}
override fun subscribeToRoomUpdate(listener: SpaceRoomListEntriesListener): TaskHandle {
override suspend fun subscribeToRoomUpdate(listener: SpaceRoomListEntriesListener): TaskHandle {
spaceRoomListEntriesListener = listener
return FakeFfiTaskHandle()
}
@@ -79,6 +79,7 @@ class RoomInfoMapperTest {
numUnreadMessages = 12uL,
numUnreadNotifications = 13uL,
numUnreadMentions = 14uL,
fullyReadEventId = AN_EVENT_ID.value,
pinnedEventIds = listOf(AN_EVENT_ID.value),
roomCreators = listOf(A_USER_ID.value),
historyVisibility = RustRoomHistoryVisibility.Joined,
@@ -131,6 +132,7 @@ class RoomInfoMapperTest {
numUnreadMessages = 12L,
numUnreadNotifications = 13L,
numUnreadMentions = 14L,
fullyReadEventId = AN_EVENT_ID,
historyVisibility = RoomHistoryVisibility.Joined,
successorRoom = null,
roomVersion = "12",
@@ -223,6 +225,7 @@ class RoomInfoMapperTest {
numUnreadMessages = 12L,
numUnreadNotifications = 13L,
numUnreadMentions = 14L,
fullyReadEventId = null,
historyVisibility = RoomHistoryVisibility.Joined,
roomVersion = "12",
privilegedCreatorRole = true,
@@ -66,6 +66,7 @@ fun aRoomInfo(
numUnreadMessages: Long = 0,
numUnreadNotifications: Long = 0,
numUnreadMentions: Long = 0,
fullyReadEventId: EventId? = null,
historyVisibility: RoomHistoryVisibility = RoomHistoryVisibility.Joined,
roomVersion: String? = "11",
privilegedCreatorRole: Boolean = false,
@@ -105,6 +106,7 @@ fun aRoomInfo(
numUnreadMessages = numUnreadMessages,
numUnreadNotifications = numUnreadNotifications,
numUnreadMentions = numUnreadMentions,
fullyReadEventId = fullyReadEventId,
historyVisibility = historyVisibility,
roomVersion = roomVersion,
privilegedCreatorRole = privilegedCreatorRole,
@@ -115,6 +115,7 @@ fun aRoomSummary(
numUnreadMessages = numUnreadMessages,
numUnreadNotifications = numUnreadNotifications,
numUnreadMentions = numUnreadMentions,
fullyReadEventId = null,
historyVisibility = historyVisibility,
roomVersion = roomVersion,
privilegedCreatorRole = privilegedCreatorRole,