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
@@ -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,