User search : show a loader when fetching results

This commit is contained in:
ganfra 2024-01-04 21:27:44 +01:00
parent c6d109c424
commit 690a165411
21 changed files with 135 additions and 81 deletions

View file

@ -35,6 +35,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.async.AsyncLoading
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.SearchBar
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
@ -49,6 +50,7 @@ import kotlinx.collections.immutable.ImmutableList
fun SearchUserBar(
query: String,
state: SearchBarResultState<ImmutableList<UserSearchResult>>,
isSearching: Boolean,
selectedUsers: ImmutableList<MatrixUser>,
active: Boolean,
isMultiSelectionEnabled: Boolean,
@ -99,6 +101,11 @@ fun SearchUserBar(
)
}
},
contentSuffix = {
if (isSearching) {
AsyncLoading()
}
},
resultState = state,
resultHandler = { users ->
LazyColumn(state = columnState) {

View file

@ -48,6 +48,7 @@ fun UserListView(
state = state.searchResults,
selectedUsers = state.selectedUsers,
active = state.isSearchActive,
isSearching = state.isFetchingSearchResults,
isMultiSelectionEnabled = state.isMultiSelectionEnabled,
showBackButton = showBackButton,
onActiveChanged = { state.eventSink(UserListEvents.OnSearchActiveChanged(it)) },

View file

@ -34,6 +34,8 @@ import io.element.android.libraries.usersearch.api.UserRepository
import io.element.android.libraries.usersearch.api.UserSearchResult
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class DefaultUserListPresenter @AssistedInject constructor(
@Assisted val args: UserListPresenterArgs,
@ -57,18 +59,21 @@ class DefaultUserListPresenter @AssistedInject constructor(
val selectedUsers by userListDataStore.selectedUsers().collectAsState(emptyList())
var searchQuery by rememberSaveable { mutableStateOf("") }
var searchResults: SearchBarResultState<ImmutableList<UserSearchResult>> by remember {
mutableStateOf(SearchBarResultState.NotSearching())
mutableStateOf(SearchBarResultState.Empty())
}
var isFetchingSearchResults by remember { mutableStateOf(false) }
LaunchedEffect(searchQuery) {
searchResults = SearchBarResultState.NotSearching()
userRepository.search(searchQuery).collect {
searchResults = SearchBarResultState.Empty()
isFetchingSearchResults = false
userRepository.search(searchQuery).onEach { state ->
isFetchingSearchResults = state.isFetchingSearchResults
searchResults = when {
it.isEmpty() -> SearchBarResultState.NoResults()
else -> SearchBarResultState.Results(it.toImmutableList())
state.results.isEmpty() && state.isFetchingSearchResults -> SearchBarResultState.Empty()
state.results.isEmpty() && !state.isFetchingSearchResults -> SearchBarResultState.NoResultsFound()
else -> SearchBarResultState.Results(state.results.toImmutableList())
}
}
}.launchIn(this)
}
return UserListState(
@ -76,6 +81,7 @@ class DefaultUserListPresenter @AssistedInject constructor(
searchResults = searchResults,
selectedUsers = selectedUsers.toImmutableList(),
isSearchActive = isSearchActive,
isFetchingSearchResults = isFetchingSearchResults,
selectionMode = args.selectionMode,
eventSink = { event ->
when (event) {

View file

@ -24,6 +24,7 @@ import kotlinx.collections.immutable.ImmutableList
data class UserListState(
val searchQuery: String,
val searchResults: SearchBarResultState<ImmutableList<UserSearchResult>>,
val isFetchingSearchResults: Boolean,
val selectedUsers: ImmutableList<MatrixUser>,
val isSearchActive: Boolean,
val selectionMode: SelectionMode,

View file

@ -51,17 +51,19 @@ open class UserListStateProvider : PreviewParameterProvider<UserListState> {
aUserListState().copy(
isSearchActive = true,
searchQuery = "something-with-no-results",
searchResults = SearchBarResultState.NoResults()
searchResults = SearchBarResultState.NoResultsFound()
),
aUserListState().copy(isSearchActive = true, searchQuery = "someone", selectionMode = SelectionMode.Single),
)
}
fun aUserListState() = UserListState(
isSearchActive = false,
searchQuery = "",
searchResults = SearchBarResultState.NotSearching(),
searchResults = SearchBarResultState.Empty(),
selectedUsers = persistentListOf(),
selectionMode = SelectionMode.Single,
isFetchingSearchResults = false,
eventSink = {}
)

View file

@ -55,7 +55,7 @@ class DefaultUserListPresenterTests {
assertThat(initialState.isMultiSelectionEnabled).isFalse()
assertThat(initialState.isSearchActive).isFalse()
assertThat(initialState.selectedUsers).isEmpty()
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java)
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Empty::class.java)
}
}
@ -76,7 +76,7 @@ class DefaultUserListPresenterTests {
assertThat(initialState.isMultiSelectionEnabled).isTrue()
assertThat(initialState.isSearchActive).isFalse()
assertThat(initialState.selectedUsers).isEmpty()
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java)
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Empty::class.java)
}
}
@ -131,7 +131,7 @@ class DefaultUserListPresenterTests {
val initialState = awaitItem()
initialState.eventSink(UserListEvents.UpdateSearchQuery("alice"))
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java)
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Empty::class.java)
assertThat(userRepository.providedQuery).isEqualTo("alice")
skipItems(2)
@ -170,13 +170,13 @@ class DefaultUserListPresenterTests {
val initialState = awaitItem()
initialState.eventSink(UserListEvents.UpdateSearchQuery("alice"))
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java)
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Empty::class.java)
assertThat(userRepository.providedQuery).isEqualTo("alice")
skipItems(2)
// When the results list is empty, the state is set to NoResults
userRepository.emitResult(emptyList())
assertThat(awaitItem().searchResults).isInstanceOf(SearchBarResultState.NoResults::class.java)
assertThat(awaitItem().searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java)
}
}