feature(spaces) : introduce SpaceRoomList matrix api
This commit is contained in:
@@ -13,7 +13,6 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.features.invite.api.seenSpaceIds
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
@@ -34,9 +33,9 @@ class HomeSpacesPresenter(
|
||||
.mediaPreviewConfigFlow
|
||||
.mapState { config -> config.hideInviteAvatar }
|
||||
}.collectAsState()
|
||||
val spaceRooms by client.spaceService.spaceRooms.collectAsState(emptyList())
|
||||
val spaceRooms by client.spaceService.spaceRoomsFlow.collectAsState(emptyList())
|
||||
val seenSpaceInvites by remember {
|
||||
seenInvitesStore.seenSpaceIds().map { it.toPersistentSet() }
|
||||
seenInvitesStore.seenRoomIds().map { it.toPersistentSet() }
|
||||
}.collectAsState(persistentSetOf())
|
||||
|
||||
fun handleEvents(event: HomeSpacesEvents) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
package io.element.android.features.home.impl.spaces
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SpaceId
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
@@ -14,7 +15,7 @@ import kotlinx.collections.immutable.ImmutableSet
|
||||
data class HomeSpacesState(
|
||||
val space: CurrentSpace,
|
||||
val spaceRooms: List<SpaceRoom>,
|
||||
val seenSpaceInvites: ImmutableSet<SpaceId>,
|
||||
val seenSpaceInvites: ImmutableSet<RoomId>,
|
||||
val hideInvitesAvatar: Boolean,
|
||||
val eventSink: (HomeSpacesEvents) -> Unit,
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
package io.element.android.features.home.impl.spaces
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.core.SpaceId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
|
||||
@@ -18,12 +18,12 @@ open class HomeSpacesStateProvider : PreviewParameterProvider<HomeSpacesState> {
|
||||
aHomeSpacesState(
|
||||
spaceRooms = SpaceRoomProvider().values.toList(),
|
||||
seenSpaceInvites = setOf(
|
||||
SpaceId("!spaceId3:example.com"),
|
||||
RoomId("!spaceId3:example.com"),
|
||||
),
|
||||
),
|
||||
aHomeSpacesState(
|
||||
space = CurrentSpace.Space(
|
||||
spaceRoom = aSpaceRooms(spaceId = SpaceId("!mySpace:example.com"))
|
||||
spaceRoom = aSpaceRoom(roomId = RoomId("!mySpace:example.com"))
|
||||
),
|
||||
spaceRooms = aListOfSpaceRooms(),
|
||||
),
|
||||
@@ -33,7 +33,7 @@ open class HomeSpacesStateProvider : PreviewParameterProvider<HomeSpacesState> {
|
||||
internal fun aHomeSpacesState(
|
||||
space: CurrentSpace = CurrentSpace.Root,
|
||||
spaceRooms: List<SpaceRoom> = aListOfSpaceRooms(),
|
||||
seenSpaceInvites: Set<SpaceId> = emptySet(),
|
||||
seenSpaceInvites: Set<RoomId> = emptySet(),
|
||||
hideInvitesAvatar: Boolean = false,
|
||||
eventSink: (HomeSpacesEvents) -> Unit = {},
|
||||
) = HomeSpacesState(
|
||||
@@ -46,8 +46,8 @@ internal fun aHomeSpacesState(
|
||||
|
||||
fun aListOfSpaceRooms(): List<SpaceRoom> {
|
||||
return listOf(
|
||||
aSpaceRooms(spaceId = SpaceId("!spaceId0:example.com")),
|
||||
aSpaceRooms(spaceId = SpaceId("!spaceId1:example.com")),
|
||||
aSpaceRooms(spaceId = SpaceId("!spaceId2:example.com")),
|
||||
aSpaceRoom(roomId = RoomId("!spaceId0:example.com")),
|
||||
aSpaceRoom(roomId = RoomId("!spaceId1:example.com")),
|
||||
aSpaceRoom(roomId = RoomId("!spaceId2:example.com")),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SpaceId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.ui.components.SpaceHeaderRootView
|
||||
@@ -24,7 +25,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||
@Composable
|
||||
fun HomeSpacesView(
|
||||
state: HomeSpacesState,
|
||||
onSpaceClick: (SpaceId) -> Unit,
|
||||
onSpaceClick: (RoomId) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(modifier) {
|
||||
@@ -52,14 +53,14 @@ fun HomeSpacesView(
|
||||
}
|
||||
}
|
||||
state.spaceRooms.forEach {
|
||||
item(it.spaceId) {
|
||||
item(it.roomId) {
|
||||
val isInvitation = it.state == CurrentUserMembership.INVITED
|
||||
HomeSpaceItemView(
|
||||
spaceRoom = it,
|
||||
showUnreadIndicator = isInvitation && it.spaceId !in state.seenSpaceInvites,
|
||||
showUnreadIndicator = isInvitation && it.roomId !in state.seenSpaceInvites,
|
||||
hideAvatars = isInvitation && state.hideInvitesAvatar,
|
||||
onClick = {
|
||||
onSpaceClick(it.spaceId)
|
||||
onSpaceClick(it.roomId)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ package io.element.android.features.home.impl.spaces
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.SpaceId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
@@ -18,42 +18,42 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
class SpaceRoomProvider : PreviewParameterProvider<SpaceRoom> {
|
||||
override val values: Sequence<SpaceRoom> = sequenceOf(
|
||||
aSpaceRooms(),
|
||||
aSpaceRooms(
|
||||
aSpaceRoom(),
|
||||
aSpaceRoom(
|
||||
numJoinedMembers = 5,
|
||||
childrenCount = 10,
|
||||
worldReadable = true,
|
||||
spaceId = SpaceId("!spaceId0:example.com"),
|
||||
roomId = RoomId("!spaceId0:example.com"),
|
||||
),
|
||||
aSpaceRooms(
|
||||
aSpaceRoom(
|
||||
numJoinedMembers = 5,
|
||||
childrenCount = 10,
|
||||
worldReadable = true,
|
||||
avatarUrl = "anUrl",
|
||||
spaceId = SpaceId("!spaceId1:example.com"),
|
||||
roomId = RoomId("!spaceId1:example.com"),
|
||||
),
|
||||
aSpaceRooms(
|
||||
aSpaceRoom(
|
||||
name = null,
|
||||
numJoinedMembers = 5,
|
||||
childrenCount = 10,
|
||||
worldReadable = true,
|
||||
avatarUrl = "anUrl",
|
||||
spaceId = SpaceId("!spaceId2:example.com"),
|
||||
roomId = RoomId("!spaceId2:example.com"),
|
||||
state = CurrentUserMembership.INVITED,
|
||||
),
|
||||
aSpaceRooms(
|
||||
aSpaceRoom(
|
||||
name = null,
|
||||
numJoinedMembers = 5,
|
||||
childrenCount = 10,
|
||||
worldReadable = true,
|
||||
avatarUrl = "anUrl",
|
||||
spaceId = SpaceId("!spaceId3:example.com"),
|
||||
roomId = RoomId("!spaceId3:example.com"),
|
||||
state = CurrentUserMembership.INVITED,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aSpaceRooms(
|
||||
fun aSpaceRoom(
|
||||
name: String? = "Space name",
|
||||
avatarUrl: String? = null,
|
||||
canonicalAlias: RoomAlias? = null,
|
||||
@@ -62,7 +62,7 @@ fun aSpaceRooms(
|
||||
heroes: List<MatrixUser> = emptyList(),
|
||||
joinRule: JoinRule? = null,
|
||||
numJoinedMembers: Int = 0,
|
||||
spaceId: SpaceId = SpaceId("!spaceId:example.com"),
|
||||
roomId: RoomId = RoomId("!roomId:example.com"),
|
||||
roomType: RoomType = RoomType.Space,
|
||||
state: CurrentUserMembership? = null,
|
||||
topic: String? = null,
|
||||
@@ -76,7 +76,7 @@ fun aSpaceRooms(
|
||||
heroes = heroes,
|
||||
joinRule = joinRule,
|
||||
numJoinedMembers = numJoinedMembers,
|
||||
spaceId = spaceId,
|
||||
roomId = roomId,
|
||||
roomType = roomType,
|
||||
state = state,
|
||||
topic = topic,
|
||||
|
||||
@@ -38,9 +38,3 @@ interface SeenInvitesStore {
|
||||
*/
|
||||
suspend fun clear()
|
||||
}
|
||||
|
||||
fun SeenInvitesStore.seenSpaceIds(): Flow<Set<SpaceId>> {
|
||||
return seenRoomIds().map { roomIds ->
|
||||
roomIds.map { it.toSpaceId() }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
package io.element.android.libraries.matrix.api.spaces
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.SpaceId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
@@ -23,7 +23,7 @@ data class SpaceRoom(
|
||||
val heroes: List<MatrixUser>,
|
||||
val joinRule: JoinRule?,
|
||||
val numJoinedMembers: Int,
|
||||
val spaceId: SpaceId,
|
||||
val roomId: RoomId,
|
||||
val roomType: RoomType,
|
||||
val state: CurrentUserMembership?,
|
||||
val topic: String?,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 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.api.spaces
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface SpaceRoomList {
|
||||
sealed interface PaginationStatus {
|
||||
data object Loading : PaginationStatus
|
||||
data class Idle(val hasMoreToLoad: Boolean) : PaginationStatus
|
||||
}
|
||||
|
||||
val spaceRoomsFlow: Flow<List<SpaceRoom>>
|
||||
val paginationStatusFlow: StateFlow<PaginationStatus>
|
||||
suspend fun paginate(): Result<Unit>
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
|
||||
package io.element.android.libraries.matrix.api.spaces
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SpaceId
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
||||
interface SpaceService {
|
||||
val spaceRooms: SharedFlow<List<SpaceRoom>>
|
||||
val spaceRoomsFlow: SharedFlow<List<SpaceRoom>>
|
||||
suspend fun joinedSpaces(): Result<List<SpaceRoom>>
|
||||
|
||||
suspend fun spaceRoomList(spaceId: SpaceId): SpaceRoomList
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import uniffi.matrix_sdk_ui.SpaceRoomListPaginationState
|
||||
import org.matrix.rustcomponents.sdk.SpaceRoomList as InnerSpaceRoomList
|
||||
|
||||
class RustSpaceRoomList(
|
||||
private val inner: InnerSpaceRoomList,
|
||||
sessionCoroutineScope: CoroutineScope,
|
||||
spaceRoomMapper: SpaceRoomMapper,
|
||||
) : SpaceRoomList {
|
||||
override val spaceRoomsFlow = MutableSharedFlow<List<SpaceRoom>>(replay = 1, extraBufferCapacity = Int.MAX_VALUE)
|
||||
override val paginationStatusFlow = MutableStateFlow(inner.paginationState().into())
|
||||
private val spaceListUpdateProcessor = SpaceListUpdateProcessor(spaceRoomsFlow, spaceRoomMapper)
|
||||
|
||||
init {
|
||||
inner.paginationStateFlow()
|
||||
.onEach { paginationStatus ->
|
||||
paginationStatusFlow.emit(paginationStatus.into())
|
||||
}
|
||||
.launchIn(sessionCoroutineScope)
|
||||
|
||||
inner.spaceListUpdateFlow()
|
||||
.onEach { updates ->
|
||||
spaceListUpdateProcessor.postUpdates(updates)
|
||||
}
|
||||
.launchIn(sessionCoroutineScope)
|
||||
}
|
||||
|
||||
override suspend fun paginate(): Result<Unit> {
|
||||
return runCatchingExceptions {
|
||||
inner.paginate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun SpaceRoomListPaginationState.into(): SpaceRoomList.PaginationStatus {
|
||||
return when (this) {
|
||||
is SpaceRoomListPaginationState.Idle -> SpaceRoomList.PaginationStatus.Idle(hasMoreToLoad = !endReached)
|
||||
SpaceRoomListPaginationState.Loading -> SpaceRoomList.PaginationStatus.Loading
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,9 @@
|
||||
package io.element.android.libraries.matrix.impl.spaces
|
||||
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.matrix.api.core.SpaceId
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
||||
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
@@ -21,11 +23,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.buffer
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.SpaceListUpdate
|
||||
import org.matrix.rustcomponents.sdk.SpaceServiceInterface
|
||||
@@ -38,104 +37,49 @@ class RustSpaceService(
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val sessionDispatcher: CoroutineDispatcher,
|
||||
) : SpaceService {
|
||||
private val mapper = SpaceRoomMapper()
|
||||
private val mutex = Mutex()
|
||||
|
||||
override val spaceRooms = MutableSharedFlow<List<SpaceRoom>>(replay = 1, extraBufferCapacity = 1)
|
||||
private val spaceRoomMapper = SpaceRoomMapper()
|
||||
override val spaceRoomsFlow = MutableSharedFlow<List<SpaceRoom>>(replay = 1, extraBufferCapacity = 1)
|
||||
private val spaceListUpdateProcessor = SpaceListUpdateProcessor(spaceRoomsFlow, spaceRoomMapper)
|
||||
|
||||
override suspend fun joinedSpaces(): Result<List<SpaceRoom>> = withContext(sessionDispatcher) {
|
||||
runCatchingExceptions {
|
||||
innerSpaceService.joinedSpaces()
|
||||
.map {
|
||||
it.let(mapper::map)
|
||||
it.let(spaceRoomMapper::map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// override suspend fun spaceRoomList(spaceId: SpaceId): Result<List<SpaceRoom>> = withContext(sessionDispatcher) {
|
||||
// runCatchingExceptions {
|
||||
// innerSpaceService.spaceRoomList(spaceId.value)
|
||||
// }
|
||||
// }
|
||||
override suspend fun spaceRoomList(spaceId: SpaceId): SpaceRoomList {
|
||||
val innerSpaceRoomList = innerSpaceService.spaceRoomList(spaceId.value)
|
||||
return RustSpaceRoomList(
|
||||
inner = innerSpaceRoomList,
|
||||
sessionCoroutineScope = sessionCoroutineScope,
|
||||
spaceRoomMapper = spaceRoomMapper
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
innerSpaceService
|
||||
.spaceDiffFlow()
|
||||
.onEach {
|
||||
handeUpdate(it)
|
||||
.spaceListUpdate()
|
||||
.onEach { updates ->
|
||||
spaceListUpdateProcessor.postUpdates(updates)
|
||||
}
|
||||
.launchIn(sessionCoroutineScope)
|
||||
}
|
||||
|
||||
private suspend fun handeUpdate(spaceListUpdates: List<SpaceListUpdate>) {
|
||||
mutex.withLock {
|
||||
val current = if (spaceRooms.replayCache.isNotEmpty()) {
|
||||
spaceRooms.first().toMutableList()
|
||||
} else {
|
||||
mutableListOf()
|
||||
}
|
||||
spaceListUpdates.forEach { update ->
|
||||
current.applyUpdate(update)
|
||||
}
|
||||
spaceRooms.emit(current)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<SpaceRoom>.applyUpdate(update: SpaceListUpdate) {
|
||||
when (update) {
|
||||
is SpaceListUpdate.Append -> {
|
||||
val newSpaces = update.values.map(mapper::map)
|
||||
addAll(newSpaces)
|
||||
}
|
||||
SpaceListUpdate.Clear -> clear()
|
||||
is SpaceListUpdate.Insert -> {
|
||||
val newSpace = mapper.map(update.value)
|
||||
add(update.index.toInt(), newSpace)
|
||||
}
|
||||
SpaceListUpdate.PopBack -> {
|
||||
removeAt(lastIndex)
|
||||
}
|
||||
SpaceListUpdate.PopFront -> {
|
||||
removeAt(0)
|
||||
}
|
||||
is SpaceListUpdate.PushBack -> {
|
||||
val newSpace = mapper.map(update.value)
|
||||
add(newSpace)
|
||||
}
|
||||
is SpaceListUpdate.PushFront -> {
|
||||
val newSpace = mapper.map(update.value)
|
||||
add(0, newSpace)
|
||||
}
|
||||
is SpaceListUpdate.Remove -> {
|
||||
removeAt(update.index.toInt())
|
||||
}
|
||||
is SpaceListUpdate.Reset -> {
|
||||
clear()
|
||||
val newSpaces = update.values.map(mapper::map)
|
||||
addAll(newSpaces)
|
||||
}
|
||||
is SpaceListUpdate.Set -> {
|
||||
val newSpace = mapper.map(update.value)
|
||||
this[update.index.toInt()] = newSpace
|
||||
}
|
||||
is SpaceListUpdate.Truncate -> {
|
||||
subList(update.length.toInt(), size).clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun SpaceServiceInterface.spaceDiffFlow(): Flow<List<SpaceListUpdate>> =
|
||||
internal fun SpaceServiceInterface.spaceListUpdate(): Flow<List<SpaceListUpdate>> =
|
||||
callbackFlow {
|
||||
val listener = object : SpaceServiceJoinedSpacesListener {
|
||||
override fun onUpdate(roomUpdates: List<SpaceListUpdate>) {
|
||||
trySendBlocking(roomUpdates)
|
||||
}
|
||||
}
|
||||
Timber.d("Open spaceDiffFlow for SpaceServiceInterface ${this@spaceDiffFlow}")
|
||||
Timber.d("Open spaceDiffFlow for SpaceServiceInterface ${this@spaceListUpdate}")
|
||||
val taskHandle = subscribeToJoinedSpaces(listener)
|
||||
awaitClose {
|
||||
Timber.d("Close spaceDiffFlow for SpaceServiceInterface ${this@spaceDiffFlow}")
|
||||
Timber.d("Close spaceDiffFlow for SpaceServiceInterface ${this@spaceListUpdate}")
|
||||
taskHandle.cancelAndDestroy()
|
||||
}
|
||||
}.catch {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 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.matrix.api.spaces.SpaceRoom
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.matrix.rustcomponents.sdk.SpaceListUpdate
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SpaceListUpdateProcessor(
|
||||
private val spaceRoomsFlow: MutableSharedFlow<List<SpaceRoom>>,
|
||||
private val mapper: SpaceRoomMapper,
|
||||
) {
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend fun postUpdates(updates: List<SpaceListUpdate>) {
|
||||
Timber.v("Update space rooms from postUpdates (with ${updates.size} items) on ${Thread.currentThread()}")
|
||||
updateSpaceRooms {
|
||||
updates.forEach { update -> applyUpdate(update) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateSpaceRooms(block: MutableList<SpaceRoom>.() -> Unit) =
|
||||
mutex.withLock {
|
||||
val spaceRooms = if (spaceRoomsFlow.replayCache.isNotEmpty()) {
|
||||
spaceRoomsFlow.first().toMutableList()
|
||||
} else {
|
||||
mutableListOf()
|
||||
}
|
||||
block(spaceRooms)
|
||||
spaceRoomsFlow.tryEmit(spaceRooms)
|
||||
}
|
||||
|
||||
private fun MutableList<SpaceRoom>.applyUpdate(update: SpaceListUpdate) {
|
||||
when (update) {
|
||||
is SpaceListUpdate.Append -> {
|
||||
val newSpaces = update.values.map { it ->
|
||||
it.let(mapper::map)
|
||||
}
|
||||
addAll(newSpaces)
|
||||
}
|
||||
SpaceListUpdate.Clear -> clear()
|
||||
is SpaceListUpdate.Insert -> {
|
||||
val newSpace = mapper.map(update.value)
|
||||
add(update.index.toInt(), newSpace)
|
||||
}
|
||||
SpaceListUpdate.PopBack -> {
|
||||
removeAt(lastIndex)
|
||||
}
|
||||
SpaceListUpdate.PopFront -> {
|
||||
removeAt(0)
|
||||
}
|
||||
is SpaceListUpdate.PushBack -> {
|
||||
val newSpace = mapper.map(update.value)
|
||||
add(newSpace)
|
||||
}
|
||||
is SpaceListUpdate.PushFront -> {
|
||||
val newSpace = mapper.map(update.value)
|
||||
add(0, newSpace)
|
||||
}
|
||||
is SpaceListUpdate.Remove -> {
|
||||
removeAt(update.index.toInt())
|
||||
}
|
||||
is SpaceListUpdate.Reset -> {
|
||||
clear()
|
||||
val newSpaces = update.values.map(mapper::map)
|
||||
addAll(newSpaces)
|
||||
}
|
||||
is SpaceListUpdate.Set -> {
|
||||
val newSpace = mapper.map(update.value)
|
||||
this[update.index.toInt()] = newSpace
|
||||
}
|
||||
is SpaceListUpdate.Truncate -> {
|
||||
subList(update.length.toInt(), size).clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.matrix.impl.util.cancelAndDestroy
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
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.SpaceRoomListEntriesListener
|
||||
import org.matrix.rustcomponents.sdk.SpaceRoomListInterface
|
||||
import org.matrix.rustcomponents.sdk.SpaceRoomListPaginationStateListener
|
||||
import timber.log.Timber
|
||||
import uniffi.matrix_sdk_ui.SpaceRoomListPaginationState
|
||||
|
||||
internal fun SpaceRoomListInterface.paginationStateFlow(): Flow<SpaceRoomListPaginationState> = callbackFlow {
|
||||
val listener = object : SpaceRoomListPaginationStateListener {
|
||||
override fun onUpdate(paginationState: SpaceRoomListPaginationState) {
|
||||
trySend(paginationState)
|
||||
}
|
||||
}
|
||||
val result = subscribeToPaginationStateUpdates(listener)
|
||||
awaitClose {
|
||||
result.cancelAndDestroy()
|
||||
}
|
||||
}.catch {
|
||||
Timber.d(it, "paginationStateFlow() failed")
|
||||
}.buffer(Channel.UNLIMITED)
|
||||
|
||||
internal fun SpaceRoomListInterface.spaceListUpdateFlow(): Flow<List<SpaceListUpdate>> =
|
||||
callbackFlow {
|
||||
val listener = object : SpaceRoomListEntriesListener {
|
||||
override fun onUpdate(rooms: List<SpaceListUpdate>) {
|
||||
trySendBlocking(rooms)
|
||||
}
|
||||
}
|
||||
Timber.d("Open spaceListUpdateFlow for SpaceRoomListInterface ${this@spaceListUpdateFlow}")
|
||||
val taskHandle = subscribeToRoomUpdate(listener)
|
||||
awaitClose {
|
||||
Timber.d("Close spaceListUpdateFlow for SpaceRoomListInterface ${this@spaceListUpdateFlow}")
|
||||
taskHandle.cancelAndDestroy()
|
||||
}
|
||||
}.catch {
|
||||
Timber.d(it, "spaceListUpdateFlow() failed")
|
||||
}.buffer(Channel.UNLIMITED)
|
||||
@@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.spaces
|
||||
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SpaceId
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.impl.room.join.map
|
||||
@@ -26,7 +27,7 @@ class SpaceRoomMapper {
|
||||
joinRule = spaceRoom.joinRule?.map(),
|
||||
name = spaceRoom.name,
|
||||
numJoinedMembers = spaceRoom.numJoinedMembers.toInt(),
|
||||
spaceId = spaceRoom.roomId.let(::SpaceId),
|
||||
roomId = RoomId(spaceRoom.roomId),
|
||||
roomType = spaceRoom.roomType.map(),
|
||||
state = spaceRoom.state?.map(),
|
||||
topic = spaceRoom.topic,
|
||||
|
||||
@@ -12,7 +12,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
|
||||
fun SpaceRoom.getAvatarData(size: AvatarSize) = AvatarData(
|
||||
id = spaceId.value,
|
||||
id = roomId.value,
|
||||
name = name,
|
||||
url = avatarUrl,
|
||||
size = size,
|
||||
|
||||
Reference in New Issue
Block a user