RoomDirectory : continue improving interactions
This commit is contained in:
parent
bd01dacd85
commit
425eadd98f
14 changed files with 171 additions and 86 deletions
|
|
@ -25,4 +25,5 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
implementation(projects.libraries.designsystem)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.roomdirectory.api
|
||||||
|
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
|
data class RoomDescription(
|
||||||
|
val roomId: RoomId,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val avatarData: AvatarData,
|
||||||
|
val canBeJoined: Boolean,
|
||||||
|
)
|
||||||
|
|
@ -22,12 +22,12 @@ import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import io.element.android.features.roomdirectory.impl.root.model.toUiModel
|
import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryListState
|
||||||
|
import io.element.android.features.roomdirectory.impl.root.model.toFeatureModel
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runUpdatingState
|
import io.element.android.libraries.architecture.runUpdatingState
|
||||||
|
|
@ -36,9 +36,9 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
|
||||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -47,34 +47,43 @@ import javax.inject.Inject
|
||||||
class RoomDirectoryPresenter @Inject constructor(
|
class RoomDirectoryPresenter @Inject constructor(
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
roomDirectoryService: RoomDirectoryService,
|
private val roomDirectoryService: RoomDirectoryService,
|
||||||
) : Presenter<RoomDirectoryState> {
|
) : Presenter<RoomDirectoryState> {
|
||||||
|
|
||||||
private val roomDirectoryList = roomDirectoryService.createRoomDirectoryList()
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomDirectoryState {
|
override fun present(): RoomDirectoryState {
|
||||||
|
var loadingMore by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
var searchQuery by rememberSaveable {
|
var searchQuery by rememberSaveable {
|
||||||
mutableStateOf("")
|
mutableStateOf<String?>(null)
|
||||||
}
|
}
|
||||||
val allRooms by roomDirectoryList.collectItemsAsState()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val hasMoreToLoad by produceState(initialValue = true, allRooms) {
|
val roomDirectoryList = remember {
|
||||||
value = roomDirectoryList.hasMoreToLoad()
|
roomDirectoryService.createRoomDirectoryList(coroutineScope)
|
||||||
}
|
}
|
||||||
|
val listState by roomDirectoryList.collectState()
|
||||||
val joinRoomAction: MutableState<AsyncAction<RoomId>> = remember {
|
val joinRoomAction: MutableState<AsyncAction<RoomId>> = remember {
|
||||||
mutableStateOf(AsyncAction.Uninitialized)
|
mutableStateOf(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
LaunchedEffect(searchQuery) {
|
LaunchedEffect(searchQuery) {
|
||||||
|
if (searchQuery == null) return@LaunchedEffect
|
||||||
|
//debounce search query
|
||||||
|
delay(300)
|
||||||
|
//cancel load more right away
|
||||||
|
loadingMore = false
|
||||||
roomDirectoryList.filter(searchQuery, 20)
|
roomDirectoryList.filter(searchQuery, 20)
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(loadingMore) {
|
||||||
|
if (loadingMore) {
|
||||||
|
roomDirectoryList.loadMore()
|
||||||
|
loadingMore = false
|
||||||
|
}
|
||||||
|
}
|
||||||
fun handleEvents(event: RoomDirectoryEvents) {
|
fun handleEvents(event: RoomDirectoryEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
RoomDirectoryEvents.LoadMore -> {
|
RoomDirectoryEvents.LoadMore -> {
|
||||||
coroutineScope.launch {
|
loadingMore = true
|
||||||
roomDirectoryList.loadMore()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is RoomDirectoryEvents.Search -> {
|
is RoomDirectoryEvents.Search -> {
|
||||||
searchQuery = event.query
|
searchQuery = event.query
|
||||||
|
|
@ -89,9 +98,9 @@ class RoomDirectoryPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
return RoomDirectoryState(
|
return RoomDirectoryState(
|
||||||
query = searchQuery,
|
query = searchQuery.orEmpty(),
|
||||||
roomDescriptions = allRooms,
|
roomDescriptions = listState.items,
|
||||||
displayLoadMoreIndicator = hasMoreToLoad,
|
displayLoadMoreIndicator = listState.hasMoreToLoad,
|
||||||
joinRoomAction = joinRoomAction.value,
|
joinRoomAction = joinRoomAction.value,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
|
|
@ -104,11 +113,12 @@ class RoomDirectoryPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomDirectoryList.collectItemsAsState() = remember {
|
private fun RoomDirectoryList.collectState() = remember {
|
||||||
items.map { list ->
|
state.map {
|
||||||
list
|
val items = it.items
|
||||||
.map { roomDescription -> roomDescription.toUiModel() }
|
.map { roomDescription -> roomDescription.toFeatureModel() }
|
||||||
.toImmutableList()
|
.toImmutableList()
|
||||||
|
RoomDirectoryListState(items = items, hasMoreToLoad = it.hasMoreToLoad)
|
||||||
}.flowOn(dispatchers.computation)
|
}.flowOn(dispatchers.computation)
|
||||||
}.collectAsState(persistentListOf())
|
}.collectAsState(RoomDirectoryListState.Default)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,14 @@
|
||||||
|
|
||||||
package io.element.android.features.roomdirectory.impl.root
|
package io.element.android.features.roomdirectory.impl.root
|
||||||
|
|
||||||
import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class RoomDirectoryState(
|
data class RoomDirectoryState(
|
||||||
val query: String,
|
val query: String,
|
||||||
val roomDescriptions: ImmutableList<RoomDescriptionUiModel>,
|
val roomDescriptions: ImmutableList<RoomDescription>,
|
||||||
val displayLoadMoreIndicator: Boolean,
|
val displayLoadMoreIndicator: Boolean,
|
||||||
val joinRoomAction: AsyncAction<RoomId>,
|
val joinRoomAction: AsyncAction<RoomId>,
|
||||||
val eventSink: (RoomDirectoryEvents) -> Unit
|
val eventSink: (RoomDirectoryEvents) -> Unit
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package io.element.android.features.roomdirectory.impl.root
|
package io.element.android.features.roomdirectory.impl.root
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
|
@ -32,7 +32,7 @@ open class RoomDirectorySearchStateProvider : PreviewParameterProvider<RoomDirec
|
||||||
aRoomDirectoryState(
|
aRoomDirectoryState(
|
||||||
query = "Element",
|
query = "Element",
|
||||||
roomDescriptions = persistentListOf(
|
roomDescriptions = persistentListOf(
|
||||||
RoomDescriptionUiModel(
|
RoomDescription(
|
||||||
roomId = RoomId("@exa:matrix.org"),
|
roomId = RoomId("@exa:matrix.org"),
|
||||||
name = "Element X Android",
|
name = "Element X Android",
|
||||||
description = "Element X is a secure, private and decentralized messenger.",
|
description = "Element X is a secure, private and decentralized messenger.",
|
||||||
|
|
@ -44,7 +44,7 @@ open class RoomDirectorySearchStateProvider : PreviewParameterProvider<RoomDirec
|
||||||
),
|
),
|
||||||
canBeJoined = true,
|
canBeJoined = true,
|
||||||
),
|
),
|
||||||
RoomDescriptionUiModel(
|
RoomDescription(
|
||||||
roomId = RoomId("@exi:matrix.org"),
|
roomId = RoomId("@exi:matrix.org"),
|
||||||
name = "Element X iOS",
|
name = "Element X iOS",
|
||||||
description = "Element X is a secure, private and decentralized messenger.",
|
description = "Element X is a secure, private and decentralized messenger.",
|
||||||
|
|
@ -64,7 +64,7 @@ open class RoomDirectorySearchStateProvider : PreviewParameterProvider<RoomDirec
|
||||||
fun aRoomDirectoryState(
|
fun aRoomDirectoryState(
|
||||||
query: String = "",
|
query: String = "",
|
||||||
displayLoadMoreIndicator: Boolean = false,
|
displayLoadMoreIndicator: Boolean = false,
|
||||||
roomDescriptions: ImmutableList<RoomDescriptionUiModel> = persistentListOf(),
|
roomDescriptions: ImmutableList<RoomDescription> = persistentListOf(),
|
||||||
joinRoomAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
joinRoomAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||||
) = RoomDirectoryState(
|
) = RoomDirectoryState(
|
||||||
query = query,
|
query = query,
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
|
|
@ -144,7 +144,7 @@ private fun RoomDirectoryContent(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomDirectoryRoomList(
|
private fun RoomDirectoryRoomList(
|
||||||
roomDescriptions: ImmutableList<RoomDescriptionUiModel>,
|
roomDescriptions: ImmutableList<RoomDescription>,
|
||||||
displayLoadMoreIndicator: Boolean,
|
displayLoadMoreIndicator: Boolean,
|
||||||
displayEmptyState: Boolean,
|
displayEmptyState: Boolean,
|
||||||
onResultClicked: (RoomId) -> Unit,
|
onResultClicked: (RoomId) -> Unit,
|
||||||
|
|
@ -185,7 +185,7 @@ private fun LoadMoreIndicator(modifier: Modifier = Modifier) {
|
||||||
modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.padding(8.dp),
|
.padding(24.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
|
|
@ -249,7 +249,7 @@ private fun SearchTextField(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomDirectoryRoomRow(
|
private fun RoomDirectoryRoomRow(
|
||||||
roomDescription: RoomDescriptionUiModel,
|
roomDescription: RoomDescription,
|
||||||
onClick: (RoomId) -> Unit,
|
onClick: (RoomId) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -16,30 +16,22 @@
|
||||||
|
|
||||||
package io.element.android.features.roomdirectory.impl.root.model
|
package io.element.android.features.roomdirectory.impl.root.model
|
||||||
|
|
||||||
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription as MatrixRoomDescription
|
||||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription
|
|
||||||
|
|
||||||
data class RoomDescriptionUiModel(
|
fun MatrixRoomDescription.toFeatureModel(): RoomDescription {
|
||||||
val roomId: RoomId,
|
return RoomDescription(
|
||||||
val name: String,
|
|
||||||
val description: String,
|
|
||||||
val avatarData: AvatarData,
|
|
||||||
val canBeJoined: Boolean,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun RoomDescription.toUiModel(): RoomDescriptionUiModel {
|
|
||||||
return RoomDescriptionUiModel(
|
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
name = name ?: "",
|
name = name ?: "",
|
||||||
description = topic ?: "",
|
description = topic ?: alias ?: roomId.value,
|
||||||
avatarData = AvatarData(
|
avatarData = AvatarData(
|
||||||
id = roomId.value,
|
id = roomId.value,
|
||||||
name = name ?: "",
|
name = name ?: "",
|
||||||
url = avatarUrl,
|
url = avatarUrl,
|
||||||
size = AvatarSize.RoomDirectoryItem,
|
size = AvatarSize.RoomDirectoryItem,
|
||||||
),
|
),
|
||||||
canBeJoined = joinRule == RoomDescription.JoinRule.PUBLIC,
|
canBeJoined = joinRule == MatrixRoomDescription.JoinRule.PUBLIC,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.roomdirectory.impl.root.model
|
||||||
|
|
||||||
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
internal data class RoomDirectoryListState(
|
||||||
|
val hasMoreToLoad: Boolean,
|
||||||
|
val items: ImmutableList<RoomDescription>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val Default = RoomDirectoryListState(
|
||||||
|
hasMoreToLoad = true,
|
||||||
|
items = persistentListOf()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,10 @@ import kotlinx.coroutines.flow.Flow
|
||||||
interface RoomDirectoryList {
|
interface RoomDirectoryList {
|
||||||
suspend fun filter(filter: String?, batchSize: Int): Result<Unit>
|
suspend fun filter(filter: String?, batchSize: Int): Result<Unit>
|
||||||
suspend fun loadMore(): Result<Unit>
|
suspend fun loadMore(): Result<Unit>
|
||||||
suspend fun hasMoreToLoad(): Boolean
|
val state: Flow<State>
|
||||||
val items: Flow<List<RoomDescription>>
|
|
||||||
|
data class State(
|
||||||
|
val hasMoreToLoad: Boolean,
|
||||||
|
val items: List<RoomDescription>,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.roomdirectory
|
package io.element.android.libraries.matrix.api.roomdirectory
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
interface RoomDirectoryService {
|
interface RoomDirectoryService {
|
||||||
fun createRoomDirectoryList(): RoomDirectoryList
|
fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
@ -158,7 +159,6 @@ class RustMatrixClient(
|
||||||
|
|
||||||
private val roomDirectoryService = RustRoomDirectoryService(
|
private val roomDirectoryService = RustRoomDirectoryService(
|
||||||
client = client,
|
client = client,
|
||||||
sessionCoroutineScope = sessionCoroutineScope,
|
|
||||||
sessionDispatcher = sessionDispatcher,
|
sessionDispatcher = sessionDispatcher,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -441,7 +441,7 @@ class RustMatrixClient(
|
||||||
runCatching { client.removeAvatar() }
|
runCatching { client.removeAvatar() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun joinRoom(roomId: RoomId): Result<RoomId> = withContext(sessionDispatcher) {
|
override suspend fun joinRoom(roomId: RoomId): Result<RoomId> = withContext(sessionDispatcher) {
|
||||||
runCatching {
|
runCatching {
|
||||||
client.joinRoomById(roomId.value).destroy()
|
client.joinRoomById(roomId.value).destroy()
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -19,64 +19,79 @@ package io.element.android.libraries.matrix.impl.roomdirectory
|
||||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription
|
||||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.FlowPreview
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import org.matrix.rustcomponents.sdk.RoomDirectorySearch
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.coroutines.CoroutineContext
|
||||||
import org.matrix.rustcomponents.sdk.RoomDirectorySearch as InnerRoomDirectorySearch
|
|
||||||
|
|
||||||
class RustRoomDirectoryList(
|
class RustRoomDirectoryList(
|
||||||
private val inner: InnerRoomDirectorySearch,
|
private val inner: RoomDirectorySearch,
|
||||||
sessionCoroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
sessionDispatcher: CoroutineDispatcher,
|
private val coroutineContext: CoroutineContext,
|
||||||
) : RoomDirectoryList {
|
) : RoomDirectoryList {
|
||||||
|
|
||||||
private val _items = MutableSharedFlow<List<RoomDescription>>(replay = 1)
|
private val hasMoreToLoad = MutableStateFlow(true)
|
||||||
private val processor = RoomDirectorySearchProcessor(_items, sessionDispatcher, RoomDescriptionMapper())
|
private val items = MutableSharedFlow<List<RoomDescription>>(replay = 1)
|
||||||
|
private val processor = RoomDirectorySearchProcessor(items, coroutineContext, RoomDescriptionMapper())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
sessionCoroutineScope.launch(sessionDispatcher) {
|
launchIn(coroutineScope)
|
||||||
inner
|
}
|
||||||
.resultsFlow()
|
|
||||||
.onEach { updates ->
|
private fun launchIn(coroutineScope: CoroutineScope) {
|
||||||
processor.postUpdates(updates)
|
inner
|
||||||
}
|
.resultsFlow()
|
||||||
.launchIn(this)
|
.onEach { updates ->
|
||||||
}
|
processor.postUpdates(updates)
|
||||||
|
}
|
||||||
|
.flowOn(coroutineContext)
|
||||||
|
.launchIn(coroutineScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun filter(filter: String?, batchSize: Int): Result<Unit> {
|
override suspend fun filter(filter: String?, batchSize: Int): Result<Unit> {
|
||||||
return try {
|
return execute {
|
||||||
inner.search(filter = filter, batchSize = batchSize.toUInt())
|
inner.search(filter = filter, batchSize = batchSize.toUInt())
|
||||||
Result.success(Unit)
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
throw e
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Result.failure(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadMore(): Result<Unit> {
|
override suspend fun loadMore(): Result<Unit> {
|
||||||
return try {
|
return execute {
|
||||||
inner.nextPage()
|
inner.nextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun execute(action: suspend () -> Unit): Result<Unit> {
|
||||||
|
return try {
|
||||||
|
// We always assume there is more to load until we know there isn't.
|
||||||
|
// As accessing hasMoreToLoad is otherwise blocked by the current action.
|
||||||
|
hasMoreToLoad.value = true
|
||||||
|
action()
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
|
} finally {
|
||||||
|
hasMoreToLoad.value = hasMoreToLoad()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun hasMoreToLoad(): Boolean {
|
private suspend fun hasMoreToLoad(): Boolean {
|
||||||
return !inner.isAtLastPage()
|
return !inner.isAtLastPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
override val state: Flow<RoomDirectoryList.State> =
|
||||||
override val items: Flow<List<RoomDescription>> = _items.debounce(200.milliseconds)
|
combine(hasMoreToLoad, items) { hasMoreToLoad, items ->
|
||||||
|
RoomDirectoryList.State(
|
||||||
|
hasMoreToLoad = hasMoreToLoad,
|
||||||
|
items = items
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.flowOn(coroutineContext)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,10 @@ import org.matrix.rustcomponents.sdk.Client
|
||||||
|
|
||||||
class RustRoomDirectoryService(
|
class RustRoomDirectoryService(
|
||||||
private val client: Client,
|
private val client: Client,
|
||||||
private val sessionCoroutineScope: CoroutineScope,
|
|
||||||
private val sessionDispatcher: CoroutineDispatcher,
|
private val sessionDispatcher: CoroutineDispatcher,
|
||||||
) : RoomDirectoryService {
|
) : RoomDirectoryService {
|
||||||
|
|
||||||
override fun createRoomDirectoryList(): RoomDirectoryList {
|
override fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList {
|
||||||
return RustRoomDirectoryList(client.roomDirectorySearch(), sessionCoroutineScope, sessionDispatcher)
|
return RustRoomDirectoryList(client.roomDirectorySearch(), scope, sessionDispatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,10 @@ package io.element.android.libraries.matrix.test.roomdirectory
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
|
||||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
class FakeRoomDirectoryService : RoomDirectoryService {
|
class FakeRoomDirectoryService : RoomDirectoryService {
|
||||||
override fun createRoomDirectoryList(): RoomDirectoryList {
|
override fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue