Merge branch 'develop' of https://github.com/vector-im/element-x-android-poc into develop
This commit is contained in:
commit
ee0e267648
4 changed files with 125 additions and 11 deletions
|
|
@ -2,9 +2,16 @@
|
||||||
|
|
||||||
package io.element.android.x.features.roomlist
|
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.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
|
@ -14,6 +21,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.compose.collectAsState
|
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.ElementXTheme
|
||||||
import io.element.android.x.designsystem.components.ProgressDialog
|
import io.element.android.x.designsystem.components.ProgressDialog
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
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.RoomItem
|
||||||
import io.element.android.x.features.roomlist.components.RoomListTopBar
|
import io.element.android.x.features.roomlist.components.RoomListTopBar
|
||||||
import io.element.android.x.features.roomlist.model.MatrixUser
|
import io.element.android.x.features.roomlist.model.MatrixUser
|
||||||
|
|
@ -37,6 +46,7 @@ fun RoomListScreen(
|
||||||
) {
|
) {
|
||||||
val viewModel: RoomListViewModel = mavericksViewModel()
|
val viewModel: RoomListViewModel = mavericksViewModel()
|
||||||
val logoutAction by viewModel.collectAsState(RoomListViewState::logoutAction)
|
val logoutAction by viewModel.collectAsState(RoomListViewState::logoutAction)
|
||||||
|
val filter by viewModel.collectAsState(RoomListViewState::filter)
|
||||||
if (logoutAction is Success) {
|
if (logoutAction is Success) {
|
||||||
onSuccessLogout()
|
onSuccessLogout()
|
||||||
return
|
return
|
||||||
|
|
@ -49,7 +59,9 @@ fun RoomListScreen(
|
||||||
matrixUser = matrixUser(),
|
matrixUser = matrixUser(),
|
||||||
onRoomClicked = onRoomClicked,
|
onRoomClicked = onRoomClicked,
|
||||||
onLogoutClicked = viewModel::logout,
|
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>,
|
roomSummaries: List<RoomListRoomSummary>,
|
||||||
matrixUser: MatrixUser?,
|
matrixUser: MatrixUser?,
|
||||||
onRoomClicked: (RoomId) -> Unit,
|
onRoomClicked: (RoomId) -> Unit,
|
||||||
|
filter: String,
|
||||||
|
onFilterChanged: (String) -> Unit,
|
||||||
onLogoutClicked: () -> Unit,
|
onLogoutClicked: () -> Unit,
|
||||||
isLoginOut: Boolean,
|
isLoginOut: Boolean,
|
||||||
) {
|
) {
|
||||||
val appBarState = rememberTopAppBarState()
|
val appBarState = rememberTopAppBarState()
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState)
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState)
|
||||||
LogCompositions(tag = "RoomListScreen", msg = "Content")
|
LogCompositions(tag = "RoomListScreen", msg = "Content")
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|
@ -70,10 +85,24 @@ fun RoomListContent(
|
||||||
RoomListTopBar(matrixUser, onLogoutClicked, scrollBehavior)
|
RoomListTopBar(matrixUser, onLogoutClicked, scrollBehavior)
|
||||||
},
|
},
|
||||||
content = { padding ->
|
content = { padding ->
|
||||||
LazyColumn(modifier = Modifier.padding(padding)) {
|
Column(modifier = Modifier.padding(padding)) {
|
||||||
items(roomSummaries) { room ->
|
RoomFilter(
|
||||||
RoomItem(room = room) {
|
modifier = Modifier
|
||||||
onRoomClicked(it)
|
.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
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -94,7 +126,9 @@ private fun PreviewableRoomListContent() {
|
||||||
matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")),
|
matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")),
|
||||||
onRoomClicked = {},
|
onRoomClicked = {},
|
||||||
onLogoutClicked = {},
|
onLogoutClicked = {},
|
||||||
isLoginOut = false
|
filter = "filter",
|
||||||
|
onFilterChanged = {},
|
||||||
|
isLoginOut = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +142,9 @@ private fun PreviewableDarkRoomListContent() {
|
||||||
matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")),
|
matrixUser = MatrixUser("User#1", avatarData = AvatarData("U")),
|
||||||
onRoomClicked = {},
|
onRoomClicked = {},
|
||||||
onLogoutClicked = {},
|
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 io.element.android.x.matrix.room.RoomSummary
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
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
|
||||||
|
|
@ -56,6 +58,14 @@ class RoomListViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun filterRoom(filter: String) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
filter = filter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleInit() {
|
private fun handleInit() {
|
||||||
suspend {
|
suspend {
|
||||||
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
|
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()
|
||||||
|
|
@ -75,15 +85,27 @@ class RoomListViewModel(
|
||||||
copy(user = it)
|
copy(user = it)
|
||||||
}
|
}
|
||||||
|
|
||||||
client.roomSummaryDataSource().roomSummaries()
|
// Observe the room list and the filter
|
||||||
.map(::mapRoomSummaries)
|
combine(
|
||||||
.flowOn(Dispatchers.Default)
|
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 {
|
.execute {
|
||||||
copy(
|
copy(
|
||||||
rooms = when {
|
rooms = when {
|
||||||
it is Loading ||
|
it is Loading ||
|
||||||
// Note: this second case will prevent to handle correctly the empty case
|
// 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
|
// Show fake placeholders to avoid having empty screen
|
||||||
Loading(createFakePlaceHolders())
|
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(
|
data class RoomListViewState(
|
||||||
val user: Async<MatrixUser> = Uninitialized,
|
val user: Async<MatrixUser> = Uninitialized,
|
||||||
|
// Will contain the filtered rooms, using ::filter (if filter is not empty)
|
||||||
val rooms: Async<List<RoomListRoomSummary>> = Uninitialized,
|
val rooms: Async<List<RoomListRoomSummary>> = Uninitialized,
|
||||||
|
val filter: String = "",
|
||||||
val canLoadMore: Boolean = false,
|
val canLoadMore: Boolean = false,
|
||||||
val logoutAction: Async<Unit> = Uninitialized,
|
val logoutAction: Async<Unit> = Uninitialized,
|
||||||
val roomsById: Map<RoomId, RoomListRoomSummary> = emptyMap()
|
val roomsById: Map<RoomId, RoomListRoomSummary> = emptyMap()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue