Filter room list
This commit is contained in:
committed by
Benoit Marty
parent
00dc980afc
commit
4040699a4b
@@ -2,9 +2,16 @@
|
||||
|
||||
package io.element.android.x.features.roomlist
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
@@ -14,6 +21,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.compose.collectAsState
|
||||
@@ -22,6 +30,7 @@ import io.element.android.x.core.compose.LogCompositions
|
||||
import io.element.android.x.designsystem.ElementXTheme
|
||||
import io.element.android.x.designsystem.components.ProgressDialog
|
||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.x.features.roomlist.components.RoomFilter
|
||||
import io.element.android.x.features.roomlist.components.RoomItem
|
||||
import io.element.android.x.features.roomlist.components.RoomListTopBar
|
||||
import io.element.android.x.features.roomlist.model.MatrixUser
|
||||
@@ -37,6 +46,7 @@ fun RoomListScreen(
|
||||
) {
|
||||
val viewModel: RoomListViewModel = mavericksViewModel()
|
||||
val logoutAction by viewModel.collectAsState(RoomListViewState::logoutAction)
|
||||
val filter by viewModel.collectAsState(RoomListViewState::filter)
|
||||
if (logoutAction is Success) {
|
||||
onSuccessLogout()
|
||||
return
|
||||
@@ -49,7 +59,9 @@ fun RoomListScreen(
|
||||
matrixUser = matrixUser(),
|
||||
onRoomClicked = onRoomClicked,
|
||||
onLogoutClicked = viewModel::logout,
|
||||
isLoginOut = logoutAction is Loading
|
||||
isLoginOut = logoutAction is Loading,
|
||||
filter = filter,
|
||||
onFilterChanged = viewModel::filterRoom,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,10 +70,13 @@ fun RoomListContent(
|
||||
roomSummaries: List<RoomListRoomSummary>,
|
||||
matrixUser: MatrixUser?,
|
||||
onRoomClicked: (RoomId) -> Unit,
|
||||
filter: String,
|
||||
onFilterChanged: (String) -> Unit,
|
||||
onLogoutClicked: () -> Unit,
|
||||
isLoginOut: Boolean,
|
||||
) {
|
||||
val appBarState = rememberTopAppBarState()
|
||||
val lazyListState = rememberLazyListState()
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState)
|
||||
LogCompositions(tag = "RoomListScreen", msg = "Content")
|
||||
Scaffold(
|
||||
@@ -70,10 +85,24 @@ fun RoomListContent(
|
||||
RoomListTopBar(matrixUser, onLogoutClicked, scrollBehavior)
|
||||
},
|
||||
content = { padding ->
|
||||
LazyColumn(modifier = Modifier.padding(padding)) {
|
||||
items(roomSummaries) { room ->
|
||||
RoomItem(room = room) {
|
||||
onRoomClicked(it)
|
||||
Column(modifier = Modifier.padding(padding)) {
|
||||
RoomFilter(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.animateContentSize(animationSpec = tween(durationMillis = 300))
|
||||
.height(if (lazyListState.isScrolled()) 0.dp else 56.dp)
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
filter = filter,
|
||||
onFilterChanged = onFilterChanged
|
||||
)
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f),
|
||||
state = lazyListState,
|
||||
) {
|
||||
items(roomSummaries) { room ->
|
||||
RoomItem(room = room) {
|
||||
onRoomClicked(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +113,9 @@ fun RoomListContent(
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListState.isScrolled(): Boolean {
|
||||
return firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
@@ -94,7 +126,9 @@ private fun PreviewableRoomListContent() {
|
||||
matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")),
|
||||
onRoomClicked = {},
|
||||
onLogoutClicked = {},
|
||||
isLoginOut = false
|
||||
filter = "filter",
|
||||
onFilterChanged = {},
|
||||
isLoginOut = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -108,7 +142,9 @@ private fun PreviewableDarkRoomListContent() {
|
||||
matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")),
|
||||
onRoomClicked = {},
|
||||
onLogoutClicked = {},
|
||||
isLoginOut = true
|
||||
filter = "filter",
|
||||
onFilterChanged = {},
|
||||
isLoginOut = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import io.element.android.x.matrix.media.MediaResolver
|
||||
import io.element.android.x.matrix.room.RoomSummary
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -56,6 +58,14 @@ class RoomListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun filterRoom(filter: String) {
|
||||
setState {
|
||||
copy(
|
||||
filter = filter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInit() {
|
||||
suspend {
|
||||
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
|
||||
@@ -75,15 +85,27 @@ class RoomListViewModel(
|
||||
copy(user = it)
|
||||
}
|
||||
|
||||
client.roomSummaryDataSource().roomSummaries()
|
||||
.map(::mapRoomSummaries)
|
||||
.flowOn(Dispatchers.Default)
|
||||
// Observe the room list and the filter
|
||||
combine(
|
||||
client.roomSummaryDataSource().roomSummaries()
|
||||
.map(::mapRoomSummaries)
|
||||
.flowOn(Dispatchers.Default),
|
||||
stateFlow
|
||||
.map { it.filter }
|
||||
.distinctUntilChanged(),
|
||||
) { list, filter ->
|
||||
if (filter.isEmpty()) {
|
||||
list
|
||||
} else {
|
||||
list.filter { it.name.contains(filter, ignoreCase = true) }
|
||||
}
|
||||
}
|
||||
.execute {
|
||||
copy(
|
||||
rooms = when {
|
||||
it is Loading ||
|
||||
// Note: this second case will prevent to handle correctly the empty case
|
||||
(it is Success && it().isEmpty()) -> {
|
||||
(it is Success && it().isEmpty() && filter.isEmpty()) -> {
|
||||
// Show fake placeholders to avoid having empty screen
|
||||
Loading(createFakePlaceHolders())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.element.android.x.features.roomlist.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RoomFilter(
|
||||
modifier: Modifier = Modifier,
|
||||
filter: String,
|
||||
onFilterChanged: (String) -> Unit
|
||||
) {
|
||||
TextField(
|
||||
modifier = modifier,
|
||||
value = filter,
|
||||
onValueChange = onFilterChanged,
|
||||
//label = {
|
||||
// Text(text = "Search")
|
||||
//},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Search,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
trailingIcon = if (filter.isNotEmpty()) {
|
||||
{
|
||||
IconButton(onClick = { onFilterChanged("") }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Clear,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
} else null
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
private fun RoomFilterPreview() {
|
||||
RoomFilter(
|
||||
filter = "",
|
||||
onFilterChanged = {}
|
||||
)
|
||||
}
|
||||
@@ -7,7 +7,9 @@ import io.element.android.x.matrix.core.RoomId
|
||||
|
||||
data class RoomListViewState(
|
||||
val user: Async<MatrixUser> = Uninitialized,
|
||||
// Will contain the filtered rooms, using ::filter (if filter is not empty)
|
||||
val rooms: Async<List<RoomListRoomSummary>> = Uninitialized,
|
||||
val filter: String = "",
|
||||
val canLoadMore: Boolean = false,
|
||||
val logoutAction: Async<Unit> = Uninitialized,
|
||||
val roomsById: Map<RoomId, RoomListRoomSummary> = emptyMap()
|
||||
|
||||
Reference in New Issue
Block a user