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

@@ -12,6 +12,5 @@ import io.element.android.libraries.matrix.api.room.RoomMember
sealed interface RoomMemberListEvents {
data class ChangeSelectedSection(val section: SelectedSection) : RoomMemberListEvents
data class UpdateSearchQuery(val query: String) : RoomMemberListEvents
data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvents
}

View File

@@ -8,6 +8,7 @@
package io.element.android.features.roomdetails.impl.members
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
@@ -16,7 +17,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Inject
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents.ShowActionsForUser
@@ -56,7 +56,7 @@ class RoomMemberListPresenter(
@Composable
override fun present(): RoomMemberListState {
var searchQuery by rememberSaveable { mutableStateOf("") }
val searchQuery = rememberTextFieldState()
val membersState by room.membersStateFlow.collectAsState()
val canInvite by room.permissionsAsState(false) { perms -> perms.canOwnUserInvite() }
val roomModerationState = roomMembersModerationPresenter.present()
@@ -117,17 +117,16 @@ class RoomMemberListPresenter(
}
}
LaunchedEffect(searchQuery, roomMembers) {
LaunchedEffect(searchQuery.text, roomMembers) {
filteredRoomMembers = roomMembers.map { members ->
withContext(coroutineDispatchers.io) {
members.filter(searchQuery)
members.filter(searchQuery.text.toString())
}
}
}
fun handleEvent(event: RoomMemberListEvents) {
when (event) {
is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query
is RoomMemberListEvents.RoomMemberSelected ->
roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser()))
is RoomMemberListEvents.ChangeSelectedSection -> selectedSection = event.section

View File

@@ -8,6 +8,7 @@
package io.element.android.features.roomdetails.impl.members
import androidx.compose.foundation.text.input.TextFieldState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.bool.orFalse
@@ -20,7 +21,7 @@ data class RoomMemberListState(
// Only used to know if we can show the banned section
private val roomMembers: AsyncData<RoomMembers>,
val filteredRoomMembers: AsyncData<RoomMembers>,
val searchQuery: String,
val searchQuery: TextFieldState,
val canInvite: Boolean,
val selectedSection: SelectedSection,
val moderationState: RoomMemberModerationState,

View File

@@ -8,6 +8,7 @@
package io.element.android.features.roomdetails.impl.members
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
@@ -88,7 +89,7 @@ internal fun aRoomMemberListState(
) = RoomMemberListState(
roomMembers = roomMembers,
filteredRoomMembers = roomMembers.map { it.filter(searchQuery) },
searchQuery = searchQuery,
searchQuery = TextFieldState(searchQuery),
canInvite = canInvite,
moderationState = moderationState,
selectedSection = selectedSection,

View File

@@ -42,7 +42,6 @@ import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubti
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
@@ -89,13 +88,8 @@ fun RoomMemberListView(
.consumeWindowInsets(padding),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
var searchQuery by textFieldState(state.searchQuery)
SearchField(
value = searchQuery,
onValueChange = { newQuery ->
searchQuery = newQuery
state.eventSink(RoomMemberListEvents.UpdateSearchQuery(newQuery))
},
state = state.searchQuery,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
@@ -105,7 +99,7 @@ fun RoomMemberListView(
roomMembersData = state.filteredRoomMembers,
selectedSection = state.selectedSection,
showBannedSection = state.showBannedSection,
searchQuery = state.searchQuery,
searchQuery = state.searchQuery.text.toString(),
onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) },
onSelectUser = ::onSelectUser,
)

View File

@@ -8,6 +8,7 @@
package io.element.android.features.roomdetails.impl.members
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
import com.google.common.truth.Truth.assertThat
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.Presenter
@@ -42,7 +43,7 @@ class RoomMemberListPresenterTest {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.filteredRoomMembers.isLoading()).isTrue()
assertThat(initialState.searchQuery).isEmpty()
assertThat(initialState.searchQuery.text.toString()).isEmpty()
assertThat(initialState.selectedSection).isEqualTo(SelectedSection.MEMBERS)
}
}
@@ -87,7 +88,7 @@ class RoomMemberListPresenterTest {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.filteredRoomMembers.isLoading()).isTrue()
assertThat(initialState.searchQuery).isEmpty()
assertThat(initialState.searchQuery.text.toString()).isEmpty()
room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
// Skip items while the new members state is processed
skipItems(2)
@@ -116,9 +117,9 @@ class RoomMemberListPresenterTest {
assertThat(loadedRoomMembers.invited).isNotEmpty()
assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse()
assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse()
loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something"))
loadedState.searchQuery.setTextAndPlaceCursorAtEnd("something")
val searchQueryUpdatedState = awaitItem()
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("something")
assertThat(searchQueryUpdatedState.searchQuery.text).isEqualTo("something")
val searchSearchResultDelivered = awaitItem()
val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!!
assertThat(emptyRoomMembers.joined).isEmpty()
@@ -144,9 +145,9 @@ class RoomMemberListPresenterTest {
assertThat(loadedRoomMembers.invited).isNotEmpty()
assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse()
assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse()
loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("alice"))
loadedState.searchQuery.setTextAndPlaceCursorAtEnd("alice")
val searchQueryUpdatedState = awaitItem()
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("alice")
assertThat(searchQueryUpdatedState.searchQuery.text).isEqualTo("alice")
val searchSearchResultDelivered = awaitItem()
val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!!
assertThat(emptyRoomMembers.joined).isNotEmpty()