feature(spaces) : introduce SpaceRoomList matrix api
This commit is contained in:
parent
47ca80a939
commit
f56ccdc786
15 changed files with 277 additions and 115 deletions
|
|
@ -13,7 +13,6 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.features.invite.api.SeenInvitesStore
|
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.architecture.Presenter
|
||||||
import io.element.android.libraries.core.coroutine.mapState
|
import io.element.android.libraries.core.coroutine.mapState
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
|
@ -34,9 +33,9 @@ class HomeSpacesPresenter(
|
||||||
.mediaPreviewConfigFlow
|
.mediaPreviewConfigFlow
|
||||||
.mapState { config -> config.hideInviteAvatar }
|
.mapState { config -> config.hideInviteAvatar }
|
||||||
}.collectAsState()
|
}.collectAsState()
|
||||||
val spaceRooms by client.spaceService.spaceRooms.collectAsState(emptyList())
|
val spaceRooms by client.spaceService.spaceRoomsFlow.collectAsState(emptyList())
|
||||||
val seenSpaceInvites by remember {
|
val seenSpaceInvites by remember {
|
||||||
seenInvitesStore.seenSpaceIds().map { it.toPersistentSet() }
|
seenInvitesStore.seenRoomIds().map { it.toPersistentSet() }
|
||||||
}.collectAsState(persistentSetOf())
|
}.collectAsState(persistentSetOf())
|
||||||
|
|
||||||
fun handleEvents(event: HomeSpacesEvents) {
|
fun handleEvents(event: HomeSpacesEvents) {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
package io.element.android.features.home.impl.spaces
|
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.core.SpaceId
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
|
|
@ -14,7 +15,7 @@ import kotlinx.collections.immutable.ImmutableSet
|
||||||
data class HomeSpacesState(
|
data class HomeSpacesState(
|
||||||
val space: CurrentSpace,
|
val space: CurrentSpace,
|
||||||
val spaceRooms: List<SpaceRoom>,
|
val spaceRooms: List<SpaceRoom>,
|
||||||
val seenSpaceInvites: ImmutableSet<SpaceId>,
|
val seenSpaceInvites: ImmutableSet<RoomId>,
|
||||||
val hideInvitesAvatar: Boolean,
|
val hideInvitesAvatar: Boolean,
|
||||||
val eventSink: (HomeSpacesEvents) -> Unit,
|
val eventSink: (HomeSpacesEvents) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
package io.element.android.features.home.impl.spaces
|
package io.element.android.features.home.impl.spaces
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
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 io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
|
|
||||||
|
|
@ -18,12 +18,12 @@ open class HomeSpacesStateProvider : PreviewParameterProvider<HomeSpacesState> {
|
||||||
aHomeSpacesState(
|
aHomeSpacesState(
|
||||||
spaceRooms = SpaceRoomProvider().values.toList(),
|
spaceRooms = SpaceRoomProvider().values.toList(),
|
||||||
seenSpaceInvites = setOf(
|
seenSpaceInvites = setOf(
|
||||||
SpaceId("!spaceId3:example.com"),
|
RoomId("!spaceId3:example.com"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
aHomeSpacesState(
|
aHomeSpacesState(
|
||||||
space = CurrentSpace.Space(
|
space = CurrentSpace.Space(
|
||||||
spaceRoom = aSpaceRooms(spaceId = SpaceId("!mySpace:example.com"))
|
spaceRoom = aSpaceRoom(roomId = RoomId("!mySpace:example.com"))
|
||||||
),
|
),
|
||||||
spaceRooms = aListOfSpaceRooms(),
|
spaceRooms = aListOfSpaceRooms(),
|
||||||
),
|
),
|
||||||
|
|
@ -33,7 +33,7 @@ open class HomeSpacesStateProvider : PreviewParameterProvider<HomeSpacesState> {
|
||||||
internal fun aHomeSpacesState(
|
internal fun aHomeSpacesState(
|
||||||
space: CurrentSpace = CurrentSpace.Root,
|
space: CurrentSpace = CurrentSpace.Root,
|
||||||
spaceRooms: List<SpaceRoom> = aListOfSpaceRooms(),
|
spaceRooms: List<SpaceRoom> = aListOfSpaceRooms(),
|
||||||
seenSpaceInvites: Set<SpaceId> = emptySet(),
|
seenSpaceInvites: Set<RoomId> = emptySet(),
|
||||||
hideInvitesAvatar: Boolean = false,
|
hideInvitesAvatar: Boolean = false,
|
||||||
eventSink: (HomeSpacesEvents) -> Unit = {},
|
eventSink: (HomeSpacesEvents) -> Unit = {},
|
||||||
) = HomeSpacesState(
|
) = HomeSpacesState(
|
||||||
|
|
@ -46,8 +46,8 @@ internal fun aHomeSpacesState(
|
||||||
|
|
||||||
fun aListOfSpaceRooms(): List<SpaceRoom> {
|
fun aListOfSpaceRooms(): List<SpaceRoom> {
|
||||||
return listOf(
|
return listOf(
|
||||||
aSpaceRooms(spaceId = SpaceId("!spaceId0:example.com")),
|
aSpaceRoom(roomId = RoomId("!spaceId0:example.com")),
|
||||||
aSpaceRooms(spaceId = SpaceId("!spaceId1:example.com")),
|
aSpaceRoom(roomId = RoomId("!spaceId1:example.com")),
|
||||||
aSpaceRooms(spaceId = SpaceId("!spaceId2: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.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
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.core.SpaceId
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.ui.components.SpaceHeaderRootView
|
import io.element.android.libraries.matrix.ui.components.SpaceHeaderRootView
|
||||||
|
|
@ -24,7 +25,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeSpacesView(
|
fun HomeSpacesView(
|
||||||
state: HomeSpacesState,
|
state: HomeSpacesState,
|
||||||
onSpaceClick: (SpaceId) -> Unit,
|
onSpaceClick: (RoomId) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
LazyColumn(modifier) {
|
LazyColumn(modifier) {
|
||||||
|
|
@ -52,14 +53,14 @@ fun HomeSpacesView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.spaceRooms.forEach {
|
state.spaceRooms.forEach {
|
||||||
item(it.spaceId) {
|
item(it.roomId) {
|
||||||
val isInvitation = it.state == CurrentUserMembership.INVITED
|
val isInvitation = it.state == CurrentUserMembership.INVITED
|
||||||
HomeSpaceItemView(
|
HomeSpaceItemView(
|
||||||
spaceRoom = it,
|
spaceRoom = it,
|
||||||
showUnreadIndicator = isInvitation && it.spaceId !in state.seenSpaceInvites,
|
showUnreadIndicator = isInvitation && it.roomId !in state.seenSpaceInvites,
|
||||||
hideAvatars = isInvitation && state.hideInvitesAvatar,
|
hideAvatars = isInvitation && state.hideInvitesAvatar,
|
||||||
onClick = {
|
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 androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
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.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.api.room.RoomType
|
import io.element.android.libraries.matrix.api.room.RoomType
|
||||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
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> {
|
class SpaceRoomProvider : PreviewParameterProvider<SpaceRoom> {
|
||||||
override val values: Sequence<SpaceRoom> = sequenceOf(
|
override val values: Sequence<SpaceRoom> = sequenceOf(
|
||||||
aSpaceRooms(),
|
aSpaceRoom(),
|
||||||
aSpaceRooms(
|
aSpaceRoom(
|
||||||
numJoinedMembers = 5,
|
numJoinedMembers = 5,
|
||||||
childrenCount = 10,
|
childrenCount = 10,
|
||||||
worldReadable = true,
|
worldReadable = true,
|
||||||
spaceId = SpaceId("!spaceId0:example.com"),
|
roomId = RoomId("!spaceId0:example.com"),
|
||||||
),
|
),
|
||||||
aSpaceRooms(
|
aSpaceRoom(
|
||||||
numJoinedMembers = 5,
|
numJoinedMembers = 5,
|
||||||
childrenCount = 10,
|
childrenCount = 10,
|
||||||
worldReadable = true,
|
worldReadable = true,
|
||||||
avatarUrl = "anUrl",
|
avatarUrl = "anUrl",
|
||||||
spaceId = SpaceId("!spaceId1:example.com"),
|
roomId = RoomId("!spaceId1:example.com"),
|
||||||
),
|
),
|
||||||
aSpaceRooms(
|
aSpaceRoom(
|
||||||
name = null,
|
name = null,
|
||||||
numJoinedMembers = 5,
|
numJoinedMembers = 5,
|
||||||
childrenCount = 10,
|
childrenCount = 10,
|
||||||
worldReadable = true,
|
worldReadable = true,
|
||||||
avatarUrl = "anUrl",
|
avatarUrl = "anUrl",
|
||||||
spaceId = SpaceId("!spaceId2:example.com"),
|
roomId = RoomId("!spaceId2:example.com"),
|
||||||
state = CurrentUserMembership.INVITED,
|
state = CurrentUserMembership.INVITED,
|
||||||
),
|
),
|
||||||
aSpaceRooms(
|
aSpaceRoom(
|
||||||
name = null,
|
name = null,
|
||||||
numJoinedMembers = 5,
|
numJoinedMembers = 5,
|
||||||
childrenCount = 10,
|
childrenCount = 10,
|
||||||
worldReadable = true,
|
worldReadable = true,
|
||||||
avatarUrl = "anUrl",
|
avatarUrl = "anUrl",
|
||||||
spaceId = SpaceId("!spaceId3:example.com"),
|
roomId = RoomId("!spaceId3:example.com"),
|
||||||
state = CurrentUserMembership.INVITED,
|
state = CurrentUserMembership.INVITED,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aSpaceRooms(
|
fun aSpaceRoom(
|
||||||
name: String? = "Space name",
|
name: String? = "Space name",
|
||||||
avatarUrl: String? = null,
|
avatarUrl: String? = null,
|
||||||
canonicalAlias: RoomAlias? = null,
|
canonicalAlias: RoomAlias? = null,
|
||||||
|
|
@ -62,7 +62,7 @@ fun aSpaceRooms(
|
||||||
heroes: List<MatrixUser> = emptyList(),
|
heroes: List<MatrixUser> = emptyList(),
|
||||||
joinRule: JoinRule? = null,
|
joinRule: JoinRule? = null,
|
||||||
numJoinedMembers: Int = 0,
|
numJoinedMembers: Int = 0,
|
||||||
spaceId: SpaceId = SpaceId("!spaceId:example.com"),
|
roomId: RoomId = RoomId("!roomId:example.com"),
|
||||||
roomType: RoomType = RoomType.Space,
|
roomType: RoomType = RoomType.Space,
|
||||||
state: CurrentUserMembership? = null,
|
state: CurrentUserMembership? = null,
|
||||||
topic: String? = null,
|
topic: String? = null,
|
||||||
|
|
@ -76,7 +76,7 @@ fun aSpaceRooms(
|
||||||
heroes = heroes,
|
heroes = heroes,
|
||||||
joinRule = joinRule,
|
joinRule = joinRule,
|
||||||
numJoinedMembers = numJoinedMembers,
|
numJoinedMembers = numJoinedMembers,
|
||||||
spaceId = spaceId,
|
roomId = roomId,
|
||||||
roomType = roomType,
|
roomType = roomType,
|
||||||
state = state,
|
state = state,
|
||||||
topic = topic,
|
topic = topic,
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,3 @@ interface SeenInvitesStore {
|
||||||
*/
|
*/
|
||||||
suspend fun clear()
|
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
|
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.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.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.api.room.RoomType
|
import io.element.android.libraries.matrix.api.room.RoomType
|
||||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||||
|
|
@ -23,7 +23,7 @@ data class SpaceRoom(
|
||||||
val heroes: List<MatrixUser>,
|
val heroes: List<MatrixUser>,
|
||||||
val joinRule: JoinRule?,
|
val joinRule: JoinRule?,
|
||||||
val numJoinedMembers: Int,
|
val numJoinedMembers: Int,
|
||||||
val spaceId: SpaceId,
|
val roomId: RoomId,
|
||||||
val roomType: RoomType,
|
val roomType: RoomType,
|
||||||
val state: CurrentUserMembership?,
|
val state: CurrentUserMembership?,
|
||||||
val topic: String?,
|
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
|
package io.element.android.libraries.matrix.api.spaces
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.SpaceId
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
|
||||||
interface SpaceService {
|
interface SpaceService {
|
||||||
val spaceRooms: SharedFlow<List<SpaceRoom>>
|
val spaceRoomsFlow: SharedFlow<List<SpaceRoom>>
|
||||||
suspend fun joinedSpaces(): Result<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
|
package io.element.android.libraries.matrix.impl.spaces
|
||||||
|
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
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.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.api.spaces.SpaceService
|
||||||
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
|
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
|
@ -21,11 +23,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.buffer
|
import kotlinx.coroutines.flow.buffer
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.rustcomponents.sdk.SpaceListUpdate
|
import org.matrix.rustcomponents.sdk.SpaceListUpdate
|
||||||
import org.matrix.rustcomponents.sdk.SpaceServiceInterface
|
import org.matrix.rustcomponents.sdk.SpaceServiceInterface
|
||||||
|
|
@ -38,104 +37,49 @@ class RustSpaceService(
|
||||||
private val sessionCoroutineScope: CoroutineScope,
|
private val sessionCoroutineScope: CoroutineScope,
|
||||||
private val sessionDispatcher: CoroutineDispatcher,
|
private val sessionDispatcher: CoroutineDispatcher,
|
||||||
) : SpaceService {
|
) : SpaceService {
|
||||||
private val mapper = SpaceRoomMapper()
|
private val spaceRoomMapper = SpaceRoomMapper()
|
||||||
private val mutex = Mutex()
|
override val spaceRoomsFlow = MutableSharedFlow<List<SpaceRoom>>(replay = 1, extraBufferCapacity = 1)
|
||||||
|
private val spaceListUpdateProcessor = SpaceListUpdateProcessor(spaceRoomsFlow, spaceRoomMapper)
|
||||||
override val spaceRooms = MutableSharedFlow<List<SpaceRoom>>(replay = 1, extraBufferCapacity = 1)
|
|
||||||
|
|
||||||
override suspend fun joinedSpaces(): Result<List<SpaceRoom>> = withContext(sessionDispatcher) {
|
override suspend fun joinedSpaces(): Result<List<SpaceRoom>> = withContext(sessionDispatcher) {
|
||||||
runCatchingExceptions {
|
runCatchingExceptions {
|
||||||
innerSpaceService.joinedSpaces()
|
innerSpaceService.joinedSpaces()
|
||||||
.map {
|
.map {
|
||||||
it.let(mapper::map)
|
it.let(spaceRoomMapper::map)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// override suspend fun spaceRoomList(spaceId: SpaceId): Result<List<SpaceRoom>> = withContext(sessionDispatcher) {
|
override suspend fun spaceRoomList(spaceId: SpaceId): SpaceRoomList {
|
||||||
// runCatchingExceptions {
|
val innerSpaceRoomList = innerSpaceService.spaceRoomList(spaceId.value)
|
||||||
// innerSpaceService.spaceRoomList(spaceId.value)
|
return RustSpaceRoomList(
|
||||||
// }
|
inner = innerSpaceRoomList,
|
||||||
// }
|
sessionCoroutineScope = sessionCoroutineScope,
|
||||||
|
spaceRoomMapper = spaceRoomMapper
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
innerSpaceService
|
innerSpaceService
|
||||||
.spaceDiffFlow()
|
.spaceListUpdate()
|
||||||
.onEach {
|
.onEach { updates ->
|
||||||
handeUpdate(it)
|
spaceListUpdateProcessor.postUpdates(updates)
|
||||||
}
|
}
|
||||||
.launchIn(sessionCoroutineScope)
|
.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 {
|
callbackFlow {
|
||||||
val listener = object : SpaceServiceJoinedSpacesListener {
|
val listener = object : SpaceServiceJoinedSpacesListener {
|
||||||
override fun onUpdate(roomUpdates: List<SpaceListUpdate>) {
|
override fun onUpdate(roomUpdates: List<SpaceListUpdate>) {
|
||||||
trySendBlocking(roomUpdates)
|
trySendBlocking(roomUpdates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timber.d("Open spaceDiffFlow for SpaceServiceInterface ${this@spaceDiffFlow}")
|
Timber.d("Open spaceDiffFlow for SpaceServiceInterface ${this@spaceListUpdate}")
|
||||||
val taskHandle = subscribeToJoinedSpaces(listener)
|
val taskHandle = subscribeToJoinedSpaces(listener)
|
||||||
awaitClose {
|
awaitClose {
|
||||||
Timber.d("Close spaceDiffFlow for SpaceServiceInterface ${this@spaceDiffFlow}")
|
Timber.d("Close spaceDiffFlow for SpaceServiceInterface ${this@spaceListUpdate}")
|
||||||
taskHandle.cancelAndDestroy()
|
taskHandle.cancelAndDestroy()
|
||||||
}
|
}
|
||||||
}.catch {
|
}.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.core.bool.orFalse
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
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.core.SpaceId
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||||
import io.element.android.libraries.matrix.impl.room.join.map
|
import io.element.android.libraries.matrix.impl.room.join.map
|
||||||
|
|
@ -26,7 +27,7 @@ class SpaceRoomMapper {
|
||||||
joinRule = spaceRoom.joinRule?.map(),
|
joinRule = spaceRoom.joinRule?.map(),
|
||||||
name = spaceRoom.name,
|
name = spaceRoom.name,
|
||||||
numJoinedMembers = spaceRoom.numJoinedMembers.toInt(),
|
numJoinedMembers = spaceRoom.numJoinedMembers.toInt(),
|
||||||
spaceId = spaceRoom.roomId.let(::SpaceId),
|
roomId = RoomId(spaceRoom.roomId),
|
||||||
roomType = spaceRoom.roomType.map(),
|
roomType = spaceRoom.roomType.map(),
|
||||||
state = spaceRoom.state?.map(),
|
state = spaceRoom.state?.map(),
|
||||||
topic = spaceRoom.topic,
|
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
|
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||||
|
|
||||||
fun SpaceRoom.getAvatarData(size: AvatarSize) = AvatarData(
|
fun SpaceRoom.getAvatarData(size: AvatarSize) = AvatarData(
|
||||||
id = spaceId.value,
|
id = roomId.value,
|
||||||
name = name,
|
name = name,
|
||||||
url = avatarUrl,
|
url = avatarUrl,
|
||||||
size = size,
|
size = size,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue