sdk (space) : use the new space/spaceUpdate api from space list

This commit is contained in:
ganfra
2025-09-24 12:48:24 +02:00
parent 4a093bea67
commit 718b4dd11b
12 changed files with 38 additions and 128 deletions

View File

@@ -94,9 +94,7 @@ class JoinRoomPresenter(
val roomInfo by remember {
matrixClient.getRoomInfoFlow(roomId)
}.collectAsState(initial = Optional.empty())
val spaceRoom by remember {
spaceList.currentSpaceFlow()
}.collectAsState()
val spaceRoom by spaceList.currentSpaceFlow.collectAsState()
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
val cancelKnockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }

View File

@@ -29,7 +29,6 @@ import kotlinx.collections.immutable.toPersistentSet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
@Inject
@@ -66,7 +65,7 @@ class SpacePresenter(
}
}.collectAsState()
val currentSpace by remember { spaceRoomList.currentSpaceFlow() }.collectAsState(Optional.empty())
val currentSpace by spaceRoomList.currentSpaceFlow.collectAsState()
fun handleEvents(event: SpaceEvents) {
when (event) {

View File

@@ -17,7 +17,7 @@ interface SpaceRoomList {
data class Idle(val hasMoreToLoad: Boolean) : PaginationStatus
}
fun currentSpaceFlow(): StateFlow<Optional<SpaceRoom>>
val currentSpaceFlow: StateFlow<Optional<SpaceRoom>>
val spaceRoomsFlow: Flow<List<SpaceRoom>>
val paginationStatusFlow: StateFlow<PaginationStatus>

View File

@@ -8,15 +8,14 @@
package io.element.android.libraries.matrix.impl.spaces
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import uniffi.matrix_sdk_ui.SpaceRoomListPaginationState
@@ -24,25 +23,21 @@ import java.util.Optional
import org.matrix.rustcomponents.sdk.SpaceRoomList as InnerSpaceRoomList
class RustSpaceRoomList(
private val roomId: RoomId,
private val innerProvider: suspend () -> InnerSpaceRoomList,
sessionCoroutineScope: CoroutineScope,
spaceRoomMapper: SpaceRoomMapper,
private val spaceRoomCache: SpaceRoomCache,
) : SpaceRoomList {
private val inner = CompletableDeferred<InnerSpaceRoomList>()
override fun currentSpaceFlow(): StateFlow<Optional<SpaceRoom>> {
return spaceRoomCache.getSpaceRoomFlow(roomId)
}
override val currentSpaceFlow = MutableStateFlow<Optional<SpaceRoom>>(Optional.empty())
override val spaceRoomsFlow = MutableSharedFlow<List<SpaceRoom>>(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
override val paginationStatusFlow: MutableStateFlow<SpaceRoomList.PaginationStatus> =
MutableStateFlow(SpaceRoomList.PaginationStatus.Idle(hasMoreToLoad = false))
private val spaceListUpdateProcessor = SpaceListUpdateProcessor(
spaceRoomsFlow = spaceRoomsFlow,
mapper = spaceRoomMapper,
spaceRoomCache = spaceRoomCache
mapper = spaceRoomMapper
)
init {
@@ -64,6 +59,14 @@ class RustSpaceRoomList(
}
.collect()
}
sessionCoroutineScope.launch {
inner.await().spaceUpdateFlow()
.map { space -> space.map(spaceRoomMapper::map) }
.onEach { space ->
currentSpaceFlow.emit(space)
}
.collect()
}
}
override suspend fun paginate(): Result<Unit> {

View File

@@ -38,12 +38,10 @@ class RustSpaceService(
private val sessionDispatcher: CoroutineDispatcher,
) : SpaceService {
private val spaceRoomMapper = SpaceRoomMapper()
private val spaceRoomCache = SpaceRoomCache()
override val spaceRoomsFlow = MutableSharedFlow<List<SpaceRoom>>(replay = 1, extraBufferCapacity = 1)
private val spaceListUpdateProcessor = SpaceListUpdateProcessor(
spaceRoomsFlow = spaceRoomsFlow,
mapper = spaceRoomMapper,
spaceRoomCache = spaceRoomCache
mapper = spaceRoomMapper
)
override suspend fun joinedSpaces(): Result<List<SpaceRoom>> = withContext(sessionDispatcher) {
@@ -57,11 +55,9 @@ class RustSpaceService(
override fun spaceRoomList(id: RoomId): SpaceRoomList {
return RustSpaceRoomList(
roomId = id,
innerProvider = { innerSpaceService.spaceRoomList(id.value) },
sessionCoroutineScope = sessionCoroutineScope,
spaceRoomMapper = spaceRoomMapper,
spaceRoomCache = spaceRoomCache,
)
}

View File

@@ -18,7 +18,6 @@ import timber.log.Timber
internal class SpaceListUpdateProcessor(
private val spaceRoomsFlow: MutableSharedFlow<List<SpaceRoom>>,
private val mapper: SpaceRoomMapper,
private val spaceRoomCache: SpaceRoomCache,
) {
private val mutex = Mutex()
@@ -37,7 +36,6 @@ internal class SpaceListUpdateProcessor(
mutableListOf()
}
block(spaceRooms)
spaceRoomCache.update(spaceRooms)
spaceRoomsFlow.emit(spaceRooms)
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2025 New Vector 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.libraries.matrix.impl.spaces
import io.element.android.libraries.core.coroutine.mapState
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import java.util.Optional
/**
* An in memory cache of space rooms.
* Only caches Rooms with roomType [io.element.android.libraries.matrix.api.room.RoomType.Space].
*/
class SpaceRoomCache {
private val inMemoryCache = MutableStateFlow<Map<RoomId, SpaceRoom?>>(emptyMap())
fun getSpaceRoomFlow(roomId: RoomId): StateFlow<Optional<SpaceRoom>> {
return inMemoryCache.mapState { Optional.ofNullable(it[roomId]) }
}
fun update(spaceRooms: List<SpaceRoom>) {
inMemoryCache.update { currentValues ->
val newValues = spaceRooms
.filter { it.isSpace }
.associateBy { it.roomId }
currentValues + newValues
}
}
}

View File

@@ -16,11 +16,14 @@ import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
import org.matrix.rustcomponents.sdk.SpaceListUpdate
import org.matrix.rustcomponents.sdk.SpaceRoom
import org.matrix.rustcomponents.sdk.SpaceRoomListEntriesListener
import org.matrix.rustcomponents.sdk.SpaceRoomListInterface
import org.matrix.rustcomponents.sdk.SpaceRoomListPaginationStateListener
import org.matrix.rustcomponents.sdk.SpaceRoomListSpaceListener
import timber.log.Timber
import uniffi.matrix_sdk_ui.SpaceRoomListPaginationState
import java.util.Optional
internal fun SpaceRoomListInterface.paginationStateFlow(): Flow<SpaceRoomListPaginationState> = callbackFlow {
val listener = object : SpaceRoomListPaginationStateListener {
@@ -55,3 +58,21 @@ internal fun SpaceRoomListInterface.spaceListUpdateFlow(): Flow<List<SpaceListUp
}.catch {
Timber.d(it, "spaceListUpdateFlow() failed")
}.buffer(Channel.UNLIMITED)
internal fun SpaceRoomListInterface.spaceUpdateFlow(): Flow<Optional<SpaceRoom>> =
callbackFlow {
val listener = object : SpaceRoomListSpaceListener {
override fun onUpdate(space: SpaceRoom?) {
trySendBlocking(Optional.ofNullable(space))
}
}
Timber.d("Open spaceUpdateFlow for SpaceRoomListInterface ${this@spaceUpdateFlow}")
trySendBlocking(Optional.ofNullable(space()))
val taskHandle = subscribeToSpaceUpdates(listener)
awaitClose {
Timber.d("Close spaceUpdateFlow for SpaceRoomListInterface ${this@spaceUpdateFlow}")
taskHandle.cancelAndDestroy()
}
}.catch {
Timber.d(it, "spaceUpdateFlow() failed")
}.buffer(Channel.UNLIMITED)

View File

@@ -185,6 +185,5 @@ class RoomSummaryListProcessorTest {
) = SpaceListUpdateProcessor(
spaceRoomsFlow = spaceRoomsFlow,
mapper = SpaceRoomMapper(),
spaceRoomCache = SpaceRoomCache(),
)
}

View File

@@ -11,13 +11,10 @@ package io.element.android.libraries.matrix.impl.spaces
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustSpaceRoom
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSpaceRoomList
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.previewutils.room.aSpaceRoom
import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -26,7 +23,6 @@ import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.matrix.rustcomponents.sdk.SpaceListUpdate
import uniffi.matrix_sdk_ui.SpaceRoomListPaginationState
import kotlin.jvm.optionals.getOrNull
import org.matrix.rustcomponents.sdk.SpaceRoomList as InnerSpaceRoomList
class RustSpaceRoomListTest {
@@ -87,33 +83,15 @@ class RustSpaceRoomListTest {
paginateResult.assertions().isCalledOnce()
}
@Test
fun `currentSpaceFlow reads value from the SpaceRoomCache`() = runTest {
val spaceRoomCache = SpaceRoomCache()
val sut = createRustSpaceRoomList(
spaceRoomCache = spaceRoomCache,
)
sut.currentSpaceFlow().test {
assertThat(awaitItem().getOrNull()).isNull()
val spaceRoom = aSpaceRoom(roomId = A_ROOM_ID)
spaceRoomCache.update(listOf(spaceRoom))
assertThat(awaitItem().getOrNull()).isEqualTo(spaceRoom)
}
}
private fun TestScope.createRustSpaceRoomList(
roomId: RoomId = A_ROOM_ID,
innerSpaceRoomList: InnerSpaceRoomList = FakeFfiSpaceRoomList(),
innerProvider: suspend () -> InnerSpaceRoomList = { innerSpaceRoomList },
spaceRoomMapper: SpaceRoomMapper = SpaceRoomMapper(),
spaceRoomCache: SpaceRoomCache = SpaceRoomCache(),
): RustSpaceRoomList {
return RustSpaceRoomList(
roomId = roomId,
innerProvider = innerProvider,
sessionCoroutineScope = backgroundScope,
spaceRoomMapper = spaceRoomMapper,
spaceRoomCache = spaceRoomCache,
)
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2025 New Vector 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.libraries.matrix.impl.spaces
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.room.RoomType
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.previewutils.room.aSpaceRoom
import kotlinx.coroutines.test.runTest
import org.junit.Test
class SpaceRoomCacheTest {
@Test
fun `getSpaceRoomFlow emits items`() = runTest {
val sut = SpaceRoomCache()
sut.getSpaceRoomFlow(A_ROOM_ID).test {
assertThat(awaitItem().isEmpty).isTrue()
val room = aSpaceRoom(
roomId = A_ROOM_ID,
roomType = RoomType.Room,
)
sut.update(listOf(room))
// Not a space, should not be cached
expectNoEvents()
val space = aSpaceRoom(
roomId = A_ROOM_ID,
roomType = RoomType.Space,
)
sut.update(listOf(space))
assertThat(awaitItem().get()).isEqualTo(space)
val spaceOther = aSpaceRoom(
roomId = A_ROOM_ID_2,
roomType = RoomType.Space,
)
sut.update(listOf(spaceOther))
expectNoEvents()
}
}
}

View File

@@ -24,7 +24,7 @@ class FakeSpaceRoomList(
private val paginateResult: () -> Result<Unit> = { lambdaError() },
) : SpaceRoomList {
private val currentSpaceMutableStateFlow: MutableStateFlow<Optional<SpaceRoom>> = MutableStateFlow(Optional.ofNullable(initialSpaceFlowValue))
override fun currentSpaceFlow(): StateFlow<Optional<SpaceRoom>> = currentSpaceMutableStateFlow.asStateFlow()
override val currentSpaceFlow: StateFlow<Optional<SpaceRoom>> = currentSpaceMutableStateFlow.asStateFlow()
fun emitCurrentSpace(value: SpaceRoom?) {
currentSpaceMutableStateFlow.value = Optional.ofNullable(value)