Room directory : fix pagination and add empty state.
This commit is contained in:
parent
089fbe697a
commit
967c1b1a02
6 changed files with 91 additions and 60 deletions
|
|
@ -22,5 +22,4 @@ sealed interface RoomDirectoryEvents {
|
||||||
data class Search(val query: String) : RoomDirectoryEvents
|
data class Search(val query: String) : RoomDirectoryEvents
|
||||||
data object LoadMore : RoomDirectoryEvents
|
data object LoadMore : RoomDirectoryEvents
|
||||||
data class JoinRoom(val roomId: RoomId) : RoomDirectoryEvents
|
data class JoinRoom(val roomId: RoomId) : RoomDirectoryEvents
|
||||||
data class SearchActiveChange(val isActive: Boolean) : RoomDirectoryEvents
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,11 @@ 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.RoomDescriptionUiModel
|
|
||||||
import io.element.android.features.roomdirectory.impl.root.model.toUiModel
|
import io.element.android.features.roomdirectory.impl.root.model.toUiModel
|
||||||
import io.element.android.libraries.architecture.Presenter
|
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.MatrixClient
|
||||||
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.ImmutableList
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -76,9 +73,6 @@ class RoomDirectoryPresenter @Inject constructor(
|
||||||
is RoomDirectoryEvents.Search -> {
|
is RoomDirectoryEvents.Search -> {
|
||||||
searchQuery = event.query
|
searchQuery = event.query
|
||||||
}
|
}
|
||||||
is RoomDirectoryEvents.SearchActiveChange -> {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,23 +84,6 @@ class RoomDirectoryPresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun searchResults(
|
|
||||||
filteredRooms: ImmutableList<RoomDescriptionUiModel>,
|
|
||||||
hasMoreToLoad: Boolean,
|
|
||||||
isSearchActive: Boolean,
|
|
||||||
): SearchBarResultState<ImmutableList<RoomDescriptionUiModel>> {
|
|
||||||
return if (!isSearchActive) {
|
|
||||||
SearchBarResultState.Initial()
|
|
||||||
} else {
|
|
||||||
if (filteredRooms.isEmpty() && !hasMoreToLoad) {
|
|
||||||
SearchBarResultState.NoResultsFound()
|
|
||||||
} else {
|
|
||||||
SearchBarResultState.Results(filteredRooms)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomDirectoryList.collectItemsAsState() = remember {
|
private fun RoomDirectoryList.collectItemsAsState() = remember {
|
||||||
items.map { list ->
|
items.map { list ->
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.features.roomdirectory.impl.root
|
package io.element.android.features.roomdirectory.impl.root
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.Row
|
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.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
|
@ -33,6 +35,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.TextFieldColors
|
import androidx.compose.material3.TextFieldColors
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
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.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
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.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
|
|
@ -77,8 +81,8 @@ fun RoomDirectoryView(
|
||||||
state.eventSink(RoomDirectoryEvents.JoinRoom(roomId))
|
state.eventSink(RoomDirectoryEvents.JoinRoom(roomId))
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.consumeWindowInsets(padding)
|
.consumeWindowInsets(padding)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -119,7 +123,10 @@ private fun RoomDirectoryContent(
|
||||||
)
|
)
|
||||||
RoomDirectoryRoomList(
|
RoomDirectoryRoomList(
|
||||||
roomDescriptions = state.roomDescriptions,
|
roomDescriptions = state.roomDescriptions,
|
||||||
|
displayLoadMoreIndicator = state.displayLoadMoreIndicator,
|
||||||
|
displayEmptyState = state.displayEmptyState,
|
||||||
onResultClicked = onResultClicked,
|
onResultClicked = onResultClicked,
|
||||||
|
onReachedLoadMore = { state.eventSink(RoomDirectoryEvents.LoadMore) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -127,18 +134,52 @@ private fun RoomDirectoryContent(
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomDirectoryRoomList(
|
private fun RoomDirectoryRoomList(
|
||||||
roomDescriptions: ImmutableList<RoomDescriptionUiModel>,
|
roomDescriptions: ImmutableList<RoomDescriptionUiModel>,
|
||||||
|
displayLoadMoreIndicator: Boolean,
|
||||||
|
displayEmptyState: Boolean,
|
||||||
onResultClicked: (RoomId) -> Unit,
|
onResultClicked: (RoomId) -> Unit,
|
||||||
|
onReachedLoadMore: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(modifier = modifier) {
|
||||||
modifier = modifier,
|
|
||||||
) {
|
|
||||||
items(roomDescriptions) { roomDescription ->
|
items(roomDescriptions) { roomDescription ->
|
||||||
RoomDirectoryRoomRow(
|
RoomDirectoryRoomRow(
|
||||||
roomDescription = roomDescription,
|
roomDescription = roomDescription,
|
||||||
onClick = onResultClicked,
|
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(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { onClick(roomDescription.roomId) }
|
.clickable { onClick(roomDescription.roomId) }
|
||||||
.padding(
|
.padding(
|
||||||
top = 12.dp,
|
top = 12.dp,
|
||||||
bottom = 12.dp,
|
bottom = 12.dp,
|
||||||
start = 16.dp,
|
start = 16.dp,
|
||||||
)
|
)
|
||||||
.height(IntrinsicSize.Min),
|
.height(IntrinsicSize.Min),
|
||||||
) {
|
) {
|
||||||
Avatar(
|
Avatar(
|
||||||
avatarData = roomDescription.avatarData,
|
avatarData = roomDescription.avatarData,
|
||||||
|
|
@ -218,8 +259,8 @@ private fun RoomDirectoryRoomRow(
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = roomDescription.name,
|
text = roomDescription.name,
|
||||||
|
|
@ -241,8 +282,8 @@ private fun RoomDirectoryRoomRow(
|
||||||
text = stringResource(id = CommonStrings.action_join),
|
text = stringResource(id = CommonStrings.action_join),
|
||||||
color = ElementTheme.colors.textSuccessPrimary,
|
color = ElementTheme.colors.textSuccessPrimary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterVertically)
|
.align(Alignment.CenterVertically)
|
||||||
.padding(start = 4.dp, end = 12.dp)
|
.padding(start = 4.dp, end = 12.dp)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Spacer(modifier = Modifier.width(24.dp))
|
Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ package io.element.android.libraries.matrix.api.roomdirectory
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface RoomDirectoryList {
|
interface RoomDirectoryList {
|
||||||
suspend fun filter(filter: String?, batchSize: Int)
|
suspend fun filter(filter: String?, batchSize: Int): Result<Unit>
|
||||||
suspend fun loadMore()
|
suspend fun loadMore(): Result<Unit>
|
||||||
suspend fun hasMoreToLoad(): Boolean
|
suspend fun hasMoreToLoad(): Boolean
|
||||||
val items: Flow<List<RoomDescription>>
|
val items: Flow<List<RoomDescription>>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,27 +42,27 @@ class RoomDirectorySearchProcessor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun MutableList<RoomDescription>.applyUpdate(update: RoomDirectorySearchEntryUpdate) {
|
private fun MutableList<RoomDescription>.applyUpdate(update: RoomDirectorySearchEntryUpdate) {
|
||||||
when (update) {
|
when (update) {
|
||||||
is RoomDirectorySearchEntryUpdate.Append -> {
|
is RoomDirectorySearchEntryUpdate.Append -> {
|
||||||
val roomSummaries = update.values.map(roomDescriptionMapper::map)
|
val roomSummaries = update.values.map(roomDescriptionMapper::map)
|
||||||
addAll(roomSummaries)
|
addAll(roomSummaries)
|
||||||
}
|
}
|
||||||
is RoomDirectorySearchEntryUpdate.PushBack -> {
|
is RoomDirectorySearchEntryUpdate.PushBack -> {
|
||||||
val roomSummary = roomDescriptionMapper.map(update.value)
|
val roomDescription = roomDescriptionMapper.map(update.value)
|
||||||
add(roomSummary)
|
add(roomDescription)
|
||||||
}
|
}
|
||||||
is RoomDirectorySearchEntryUpdate.PushFront -> {
|
is RoomDirectorySearchEntryUpdate.PushFront -> {
|
||||||
val roomSummary = roomDescriptionMapper.map(update.value)
|
val roomDescription = roomDescriptionMapper.map(update.value)
|
||||||
add(0, roomSummary)
|
add(0, roomDescription)
|
||||||
}
|
}
|
||||||
is RoomDirectorySearchEntryUpdate.Set -> {
|
is RoomDirectorySearchEntryUpdate.Set -> {
|
||||||
val roomSummary = roomDescriptionMapper.map(update.value)
|
val roomDescription = roomDescriptionMapper.map(update.value)
|
||||||
this[update.index.toInt()] = roomSummary
|
this[update.index.toInt()] = roomDescription
|
||||||
}
|
}
|
||||||
is RoomDirectorySearchEntryUpdate.Insert -> {
|
is RoomDirectorySearchEntryUpdate.Insert -> {
|
||||||
val roomSummary = roomDescriptionMapper.map(update.value)
|
val roomDescription = roomDescriptionMapper.map(update.value)
|
||||||
add(update.index.toInt(), roomSummary)
|
add(update.index.toInt(), roomDescription)
|
||||||
}
|
}
|
||||||
is RoomDirectorySearchEntryUpdate.Remove -> {
|
is RoomDirectorySearchEntryUpdate.Remove -> {
|
||||||
removeAt(update.index.toInt())
|
removeAt(update.index.toInt())
|
||||||
|
|
|
||||||
|
|
@ -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.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.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
|
@ -32,12 +33,11 @@ import org.matrix.rustcomponents.sdk.RoomDirectorySearch as InnerRoomDirectorySe
|
||||||
|
|
||||||
class RustRoomDirectoryList(
|
class RustRoomDirectoryList(
|
||||||
private val inner: InnerRoomDirectorySearch,
|
private val inner: InnerRoomDirectorySearch,
|
||||||
private val sessionCoroutineScope: CoroutineScope,
|
sessionCoroutineScope: CoroutineScope,
|
||||||
private val sessionDispatcher: CoroutineDispatcher,
|
sessionDispatcher: CoroutineDispatcher,
|
||||||
) : RoomDirectoryList {
|
) : RoomDirectoryList {
|
||||||
|
|
||||||
private val _items = MutableSharedFlow<List<RoomDescription>>()
|
private val _items = MutableSharedFlow<List<RoomDescription>>(replay = 1)
|
||||||
|
|
||||||
private val processor = RoomDirectorySearchProcessor(_items, sessionDispatcher, RoomDescriptionMapper())
|
private val processor = RoomDirectorySearchProcessor(_items, sessionDispatcher, RoomDescriptionMapper())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
@ -51,12 +51,26 @@ class RustRoomDirectoryList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun filter(filter: String?, batchSize: Int) {
|
override suspend fun filter(filter: String?, batchSize: Int): Result<Unit> {
|
||||||
inner.search(filter, batchSize.toUInt())
|
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() {
|
override suspend fun loadMore(): Result<Unit> {
|
||||||
inner.nextPage()
|
return try {
|
||||||
|
inner.nextPage()
|
||||||
|
Result.success(Unit)
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun hasMoreToLoad(): Boolean {
|
override suspend fun hasMoreToLoad(): Boolean {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue