From e4c711842838ee50181bbb88b8c074349c3a609b Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 25 Mar 2024 18:21:03 +0100 Subject: [PATCH] Room directory : fix pagination and add empty state. --- .../impl/root/RoomDirectoryEvents.kt | 1 - .../impl/root/RoomDirectoryPresenter.kt | 23 ------ .../impl/root/RoomDirectoryView.kt | 75 ++++++++++++++----- .../api/roomdirectory/RoomDirectoryList.kt | 4 +- .../RoomDirectorySearchProcessor.kt | 18 ++--- .../roomdirectory/RustRoomDirectoryList.kt | 30 ++++++-- 6 files changed, 91 insertions(+), 60 deletions(-) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt index 267c889f61..771a8ceb6c 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt @@ -22,5 +22,4 @@ sealed interface RoomDirectoryEvents { data class Search(val query: String) : RoomDirectoryEvents data object LoadMore : RoomDirectoryEvents data class JoinRoom(val roomId: RoomId) : RoomDirectoryEvents - data class SearchActiveChange(val isActive: Boolean) : RoomDirectoryEvents } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index 78300287bf..70d65bfb37 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -26,14 +26,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel import io.element.android.features.roomdirectory.impl.root.model.toUiModel import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers @@ -76,9 +73,6 @@ class RoomDirectoryPresenter @Inject constructor( is RoomDirectoryEvents.Search -> { searchQuery = event.query } - is RoomDirectoryEvents.SearchActiveChange -> { - - } } } @@ -90,23 +84,6 @@ class RoomDirectoryPresenter @Inject constructor( ) } - @Composable - private fun searchResults( - filteredRooms: ImmutableList, - hasMoreToLoad: Boolean, - isSearchActive: Boolean, - ): SearchBarResultState> { - return if (!isSearchActive) { - SearchBarResultState.Initial() - } else { - if (filteredRooms.isEmpty() && !hasMoreToLoad) { - SearchBarResultState.NoResultsFound() - } else { - SearchBarResultState.Results(filteredRooms) - } - } - } - @Composable private fun RoomDirectoryList.collectItemsAsState() = remember { items.map { list -> diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index 0bff02cdc2..3bc7a0886c 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -17,6 +17,7 @@ package io.element.android.features.roomdirectory.impl.root import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row @@ -26,6 +27,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.KeyboardActions @@ -33,6 +35,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -49,6 +52,7 @@ import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.aliasScreenTitle +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.Scaffold @@ -77,8 +81,8 @@ fun RoomDirectoryView( state.eventSink(RoomDirectoryEvents.JoinRoom(roomId)) }, modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding) + .padding(padding) + .consumeWindowInsets(padding) ) } ) @@ -119,7 +123,10 @@ private fun RoomDirectoryContent( ) RoomDirectoryRoomList( roomDescriptions = state.roomDescriptions, + displayLoadMoreIndicator = state.displayLoadMoreIndicator, + displayEmptyState = state.displayEmptyState, onResultClicked = onResultClicked, + onReachedLoadMore = { state.eventSink(RoomDirectoryEvents.LoadMore) }, ) } } @@ -127,18 +134,52 @@ private fun RoomDirectoryContent( @Composable private fun RoomDirectoryRoomList( roomDescriptions: ImmutableList, + displayLoadMoreIndicator: Boolean, + displayEmptyState: Boolean, onResultClicked: (RoomId) -> Unit, + onReachedLoadMore: () -> Unit, modifier: Modifier = Modifier, ) { - LazyColumn( - modifier = modifier, - ) { + LazyColumn(modifier = modifier) { items(roomDescriptions) { roomDescription -> RoomDirectoryRoomRow( roomDescription = roomDescription, onClick = onResultClicked, ) } + if (displayEmptyState) { + item { + Text( + text = stringResource(id = CommonStrings.common_no_results), + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPlaceholder, + modifier = Modifier.padding(16.dp) + ) + } + } + if (displayLoadMoreIndicator) { + item { + LoadMoreIndicator(modifier = Modifier.fillMaxWidth()) + LaunchedEffect(Unit) { + onReachedLoadMore() + } + } + } + } +} + +@Composable +private fun LoadMoreIndicator(modifier: Modifier = Modifier) { + Box( + modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(8.dp), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator( + strokeWidth = 2.dp, + ) } } @@ -203,14 +244,14 @@ private fun RoomDirectoryRoomRow( ) { Row( modifier = modifier - .fillMaxWidth() - .clickable { onClick(roomDescription.roomId) } - .padding( - top = 12.dp, - bottom = 12.dp, - start = 16.dp, - ) - .height(IntrinsicSize.Min), + .fillMaxWidth() + .clickable { onClick(roomDescription.roomId) } + .padding( + top = 12.dp, + bottom = 12.dp, + start = 16.dp, + ) + .height(IntrinsicSize.Min), ) { Avatar( avatarData = roomDescription.avatarData, @@ -218,8 +259,8 @@ private fun RoomDirectoryRoomRow( ) Column( modifier = Modifier - .weight(1f) - .padding(start = 16.dp) + .weight(1f) + .padding(start = 16.dp) ) { Text( text = roomDescription.name, @@ -241,8 +282,8 @@ private fun RoomDirectoryRoomRow( text = stringResource(id = CommonStrings.action_join), color = ElementTheme.colors.textSuccessPrimary, modifier = Modifier - .align(Alignment.CenterVertically) - .padding(start = 4.dp, end = 12.dp) + .align(Alignment.CenterVertically) + .padding(start = 4.dp, end = 12.dp) ) } else { Spacer(modifier = Modifier.width(24.dp)) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt index ce50125b65..f232983620 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt @@ -19,8 +19,8 @@ package io.element.android.libraries.matrix.api.roomdirectory import kotlinx.coroutines.flow.Flow interface RoomDirectoryList { - suspend fun filter(filter: String?, batchSize: Int) - suspend fun loadMore() + suspend fun filter(filter: String?, batchSize: Int): Result + suspend fun loadMore(): Result suspend fun hasMoreToLoad(): Boolean val items: Flow> } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt index bfa996ac0d..f060635cdf 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt @@ -42,27 +42,27 @@ class RoomDirectorySearchProcessor( } } - private suspend fun MutableList.applyUpdate(update: RoomDirectorySearchEntryUpdate) { + private fun MutableList.applyUpdate(update: RoomDirectorySearchEntryUpdate) { when (update) { is RoomDirectorySearchEntryUpdate.Append -> { val roomSummaries = update.values.map(roomDescriptionMapper::map) addAll(roomSummaries) } is RoomDirectorySearchEntryUpdate.PushBack -> { - val roomSummary = roomDescriptionMapper.map(update.value) - add(roomSummary) + val roomDescription = roomDescriptionMapper.map(update.value) + add(roomDescription) } is RoomDirectorySearchEntryUpdate.PushFront -> { - val roomSummary = roomDescriptionMapper.map(update.value) - add(0, roomSummary) + val roomDescription = roomDescriptionMapper.map(update.value) + add(0, roomDescription) } is RoomDirectorySearchEntryUpdate.Set -> { - val roomSummary = roomDescriptionMapper.map(update.value) - this[update.index.toInt()] = roomSummary + val roomDescription = roomDescriptionMapper.map(update.value) + this[update.index.toInt()] = roomDescription } is RoomDirectorySearchEntryUpdate.Insert -> { - val roomSummary = roomDescriptionMapper.map(update.value) - add(update.index.toInt(), roomSummary) + val roomDescription = roomDescriptionMapper.map(update.value) + add(update.index.toInt(), roomDescription) } is RoomDirectorySearchEntryUpdate.Remove -> { removeAt(update.index.toInt()) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt index 1dbd62317c..7b7296db8e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt @@ -18,6 +18,7 @@ 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.RoomDirectoryList +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview @@ -32,12 +33,11 @@ import org.matrix.rustcomponents.sdk.RoomDirectorySearch as InnerRoomDirectorySe class RustRoomDirectoryList( private val inner: InnerRoomDirectorySearch, - private val sessionCoroutineScope: CoroutineScope, - private val sessionDispatcher: CoroutineDispatcher, + sessionCoroutineScope: CoroutineScope, + sessionDispatcher: CoroutineDispatcher, ) : RoomDirectoryList { - private val _items = MutableSharedFlow>() - + private val _items = MutableSharedFlow>(replay = 1) private val processor = RoomDirectorySearchProcessor(_items, sessionDispatcher, RoomDescriptionMapper()) init { @@ -51,12 +51,26 @@ class RustRoomDirectoryList( } } - override suspend fun filter(filter: String?, batchSize: Int) { - inner.search(filter, batchSize.toUInt()) + override suspend fun filter(filter: String?, batchSize: Int): Result { + return try { + 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() { - inner.nextPage() + override suspend fun loadMore(): Result { + return try { + inner.nextPage() + Result.success(Unit) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + Result.failure(e) + } } override suspend fun hasMoreToLoad(): Boolean {