Browse Source

RoomDirectory : continue improving interactions

pull/2620/head
ganfra 6 months ago
parent
commit
b900818001
  1. 1
      features/roomdirectory/api/build.gradle.kts
  2. 28
      features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDescription.kt
  3. 56
      features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt
  4. 4
      features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt
  5. 8
      features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt
  6. 8
      features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt
  7. 20
      features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt
  8. 34
      features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt
  9. 8
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt
  10. 4
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt
  11. 4
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
  12. 73
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt
  13. 6
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt
  14. 3
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt

1
features/roomdirectory/api/build.gradle.kts

@ -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)
} }

28
features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDescription.kt

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

56
features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt

@ -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)
} }

4
features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt

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

8
features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt

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

8
features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt

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

20
features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescriptionUiModel.kt → features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt

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

34
features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt

@ -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()
)
}
}

8
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt

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

4
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt

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

4
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt

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

73
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt

@ -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)
} }

6
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt

@ -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)
} }
} }

3
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt

@ -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…
Cancel
Save