Let SearchBar/SearchField use TextFieldState

This commit is contained in:
ganfra
2026-01-22 16:34:22 +01:00
parent 87619e50e8
commit fa1b32f0ba
48 changed files with 197 additions and 298 deletions

View File

@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.surfaceColorAtElevation
@@ -40,14 +41,13 @@ import kotlinx.collections.immutable.ImmutableList
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchUserBar(
query: String,
state: SearchBarResultState<ImmutableList<UserSearchResult>>,
queryState: TextFieldState,
resultState: SearchBarResultState<ImmutableList<UserSearchResult>>,
showLoader: Boolean,
selectedUsers: ImmutableList<MatrixUser>,
active: Boolean,
isMultiSelectionEnable: Boolean,
onActiveChange: (Boolean) -> Unit,
onTextChange: (String) -> Unit,
onUserSelect: (MatrixUser) -> Unit,
onUserDeselect: (MatrixUser) -> Unit,
modifier: Modifier = Modifier,
@@ -57,8 +57,7 @@ fun SearchUserBar(
val columnState = rememberLazyListState()
SearchBar(
query = query,
onQueryChange = onTextChange,
queryState = queryState,
active = active,
onActiveChange = onActiveChange,
modifier = modifier,
@@ -98,7 +97,7 @@ fun SearchUserBar(
AsyncLoading()
}
},
resultState = state,
resultState = resultState,
resultHandler = { users ->
LazyColumn(state = columnState) {
if (isMultiSelectionEnable) {

View File

@@ -46,15 +46,14 @@ fun UserListView(
) {
SearchUserBar(
modifier = Modifier.fillMaxWidth(),
query = state.searchQuery,
state = state.searchResults,
queryState = state.searchQuery,
resultState = state.searchResults,
selectedUsers = state.selectedUsers,
active = state.isSearchActive,
showLoader = state.showSearchLoader,
isMultiSelectionEnable = state.isMultiSelectionEnabled,
showBackButton = showBackButton,
onActiveChange = { state.eventSink(UserListEvents.OnSearchActiveChanged(it)) },
onTextChange = { state.eventSink(UserListEvents.UpdateSearchQuery(it)) },
onUserSelect = {
state.eventSink(UserListEvents.AddToSelection(it))
onSelectUser(it)

View File

@@ -27,10 +27,10 @@ open class StartChatStateProvider : PreviewParameterProvider<StartChatState> {
aCreateRoomRootState(
startDmAction = AsyncAction.Loading,
userListState = aMatrixUser().let {
aUserListState().copy(
aUserListState(
searchQuery = it.userId.value,
searchResults = SearchBarResultState.Results(persistentListOf(UserSearchResult(it, false))),
selectedUsers = persistentListOf(it),
selectedUsers = listOf(it),
isSearchActive = true,
)
}
@@ -38,10 +38,10 @@ open class StartChatStateProvider : PreviewParameterProvider<StartChatState> {
aCreateRoomRootState(
startDmAction = AsyncAction.Failure(RuntimeException("error")),
userListState = aMatrixUser().let {
aUserListState().copy(
aUserListState(
searchQuery = it.userId.value,
searchResults = SearchBarResultState.Results(persistentListOf(UserSearchResult(it, false))),
selectedUsers = persistentListOf(it),
selectedUsers = listOf(it),
isSearchActive = true,
)
}

View File

@@ -8,6 +8,7 @@
package io.element.android.features.startchat.impl.userlist
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -64,12 +65,13 @@ class DefaultUserListPresenter(
}
var isSearchActive by rememberSaveable { mutableStateOf(false) }
val selectedUsers by userListDataStore.selectedUsers.collectAsState(emptyList())
var searchQuery by rememberSaveable { mutableStateOf("") }
val queryState = rememberTextFieldState()
var searchResults: SearchBarResultState<ImmutableList<UserSearchResult>> by remember {
mutableStateOf(SearchBarResultState.Initial())
}
var showSearchLoader by remember { mutableStateOf(false) }
val searchQuery = queryState.text.toString()
LaunchedEffect(searchQuery) {
searchResults = SearchBarResultState.Initial()
showSearchLoader = false
@@ -86,14 +88,13 @@ class DefaultUserListPresenter(
fun handleEvent(event: UserListEvents) {
when (event) {
is UserListEvents.OnSearchActiveChanged -> isSearchActive = event.active
is UserListEvents.UpdateSearchQuery -> searchQuery = event.query
is UserListEvents.AddToSelection -> userListDataStore.selectUser(event.matrixUser)
is UserListEvents.RemoveFromSelection -> userListDataStore.removeUserFromSelection(event.matrixUser)
}
}
return UserListState(
searchQuery = searchQuery,
searchQuery = queryState,
searchResults = searchResults,
selectedUsers = selectedUsers.toImmutableList(),
isSearchActive = isSearchActive,

View File

@@ -11,7 +11,6 @@ package io.element.android.features.startchat.impl.userlist
import io.element.android.libraries.matrix.api.user.MatrixUser
sealed interface UserListEvents {
data class UpdateSearchQuery(val query: String) : UserListEvents
data class AddToSelection(val matrixUser: MatrixUser) : UserListEvents
data class RemoveFromSelection(val matrixUser: MatrixUser) : UserListEvents
data class OnSearchActiveChanged(val active: Boolean) : UserListEvents

View File

@@ -8,6 +8,7 @@
package io.element.android.features.startchat.impl.userlist
import androidx.compose.foundation.text.input.TextFieldState
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.room.recent.RecentDirectRoom
import io.element.android.libraries.matrix.api.user.MatrixUser
@@ -15,7 +16,7 @@ import io.element.android.libraries.usersearch.api.UserSearchResult
import kotlinx.collections.immutable.ImmutableList
data class UserListState(
val searchQuery: String,
val searchQuery: TextFieldState,
val searchResults: SearchBarResultState<ImmutableList<UserSearchResult>>,
val showSearchLoader: Boolean,
val selectedUsers: ImmutableList<MatrixUser>,

View File

@@ -8,6 +8,7 @@
package io.element.android.features.startchat.impl.userlist
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.core.RoomId
@@ -69,7 +70,7 @@ fun aUserListState(
recentDirectRooms: List<RecentDirectRoom> = emptyList(),
eventSink: (UserListEvents) -> Unit = {},
) = UserListState(
searchQuery = searchQuery,
searchQuery = TextFieldState(initialText = searchQuery),
isSearchActive = isSearchActive,
searchResults = searchResults,
selectedUsers = selectedUsers.toImmutableList(),

View File

@@ -8,6 +8,7 @@
package io.element.android.features.startchat.impl.userlist
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.test.FakeMatrixClient
@@ -41,7 +42,7 @@ class DefaultUserListPresenterTest {
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.searchQuery).isEmpty()
assertThat(initialState.searchQuery.text.toString()).isEmpty()
assertThat(initialState.isMultiSelectionEnabled).isFalse()
assertThat(initialState.isSearchActive).isFalse()
assertThat(initialState.selectedUsers).isEmpty()
@@ -61,7 +62,7 @@ class DefaultUserListPresenterTest {
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.searchQuery).isEmpty()
assertThat(initialState.searchQuery.text.toString()).isEmpty()
assertThat(initialState.isMultiSelectionEnabled).isTrue()
assertThat(initialState.isSearchActive).isFalse()
assertThat(initialState.selectedUsers).isEmpty()
@@ -86,14 +87,14 @@ class DefaultUserListPresenterTest {
assertThat(awaitItem().isSearchActive).isTrue()
val matrixIdQuery = "@name:matrix.org"
initialState.eventSink(UserListEvents.UpdateSearchQuery(matrixIdQuery))
assertThat(awaitItem().searchQuery).isEqualTo(matrixIdQuery)
initialState.searchQuery.setTextAndPlaceCursorAtEnd(matrixIdQuery)
assertThat(awaitItem().searchQuery.text.toString()).isEqualTo(matrixIdQuery)
assertThat(userRepository.providedQuery).isEqualTo(matrixIdQuery)
skipItems(1)
val notMatrixIdQuery = "name"
initialState.eventSink(UserListEvents.UpdateSearchQuery(notMatrixIdQuery))
assertThat(awaitItem().searchQuery).isEqualTo(notMatrixIdQuery)
initialState.searchQuery.setTextAndPlaceCursorAtEnd(notMatrixIdQuery)
assertThat(awaitItem().searchQuery.text.toString()).isEqualTo(notMatrixIdQuery)
assertThat(userRepository.providedQuery).isEqualTo(notMatrixIdQuery)
skipItems(1)
@@ -117,7 +118,7 @@ class DefaultUserListPresenterTest {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(UserListEvents.UpdateSearchQuery("alice"))
initialState.searchQuery.setTextAndPlaceCursorAtEnd("alice")
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
assertThat(userRepository.providedQuery).isEqualTo("alice")
skipItems(2)
@@ -168,7 +169,7 @@ class DefaultUserListPresenterTest {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(UserListEvents.UpdateSearchQuery("alice"))
initialState.searchQuery.setTextAndPlaceCursorAtEnd("alice")
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
assertThat(userRepository.providedQuery).isEqualTo("alice")
skipItems(2)