Browse Source

Merge branch 'develop' of https://github.com/vector-im/element-x-android-poc into develop

feature/bma/flipper
ganfra 2 years ago
parent
commit
cf25198739
  1. 50
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt
  2. 30
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt
  3. 54
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomFilter.kt
  4. 2
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListViewState.kt

50
features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt

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

30
features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt

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

54
features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomFilter.kt

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

2
features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListViewState.kt

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