Search for users to start a new DM. (#376)
Search for users to start a new DM. Hooks up the create room UI to the matrix client to get search results. Searches are debounced for 500ms and only executed when 3 or more characters are entered. Wrap the result state so we can distinguish between "no results because we haven't searched yet" and "no results because the API returned nothing", and add a "No results found" message in the UI for the latter case. Closes #95
This commit is contained in:
parent
c411faa93d
commit
99f571b4eb
48 changed files with 432 additions and 71 deletions
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.ksp)
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -27,4 +28,5 @@ dependencies {
|
|||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
ksp(libs.showkase.processor)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ package io.element.android.features.userlist.api
|
|||
|
||||
data class UserListPresenterArgs(
|
||||
val selectionMode: SelectionMode,
|
||||
val minimumSearchLength: Int = 1,
|
||||
val searchDebouncePeriodMillis: Long = 0,
|
||||
)
|
||||
|
||||
enum class SelectionMode {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
|
||||
data class UserListState(
|
||||
val searchQuery: String,
|
||||
val searchResults: ImmutableList<MatrixUser>,
|
||||
val searchResults: UserSearchResultState,
|
||||
val selectedUsers: ImmutableList<MatrixUser>,
|
||||
val isSearchActive: Boolean,
|
||||
val selectionMode: SelectionMode,
|
||||
|
|
@ -29,3 +29,14 @@ data class UserListState(
|
|||
) {
|
||||
val isMultiSelectionEnabled = selectionMode == SelectionMode.Multiple
|
||||
}
|
||||
|
||||
sealed interface UserSearchResultState {
|
||||
/** No search results are available yet (e.g. because the user hasn't entered a (long enough) search term). */
|
||||
object NotSearching : UserSearchResultState
|
||||
|
||||
/** The search has completed, but no results were found. */
|
||||
object NoResults : UserSearchResultState
|
||||
|
||||
/** The search has completed, and some matching users were found. */
|
||||
data class Results(val results: ImmutableList<MatrixUser>) : UserSearchResultState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,22 +37,27 @@ open class UserListStateProvider : PreviewParameterProvider<UserListState> {
|
|||
isSearchActive = true,
|
||||
searchQuery = "@someone:matrix.org",
|
||||
selectedUsers = aListOfSelectedUsers(),
|
||||
searchResults = aMatrixUserList().toImmutableList(),
|
||||
searchResults = UserSearchResultState.Results(aMatrixUserList().toImmutableList()),
|
||||
),
|
||||
aUserListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "@someone:matrix.org",
|
||||
selectionMode = SelectionMode.Multiple,
|
||||
selectedUsers = aListOfSelectedUsers(),
|
||||
searchResults = aMatrixUserList().toImmutableList(),
|
||||
)
|
||||
searchResults = UserSearchResultState.Results(aMatrixUserList().toImmutableList()),
|
||||
),
|
||||
aUserListState().copy(
|
||||
isSearchActive = true,
|
||||
searchQuery = "something-with-no-results",
|
||||
searchResults = UserSearchResultState.NoResults
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aUserListState() = UserListState(
|
||||
isSearchActive = false,
|
||||
searchQuery = "",
|
||||
searchResults = persistentListOf(),
|
||||
searchResults = UserSearchResultState.NotSearching,
|
||||
selectedUsers = persistentListOf(),
|
||||
selectionMode = SelectionMode.Single,
|
||||
eventSink = {}
|
||||
|
|
|
|||
|
|
@ -17,14 +17,17 @@
|
|||
package io.element.android.features.userlist.api.components
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SearchBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -32,7 +35,9 @@ import androidx.compose.ui.draw.alpha
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.userlist.api.UserSearchResultState
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
|
|
@ -46,7 +51,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
@Composable
|
||||
fun SearchUserBar(
|
||||
query: String,
|
||||
results: ImmutableList<MatrixUser>,
|
||||
state: UserSearchResultState,
|
||||
selectedUsers: ImmutableList<MatrixUser>,
|
||||
active: Boolean,
|
||||
isMultiSelectionEnabled: Boolean,
|
||||
|
|
@ -91,6 +96,7 @@ fun SearchUserBar(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
!active -> {
|
||||
{
|
||||
Icon(
|
||||
|
|
@ -100,6 +106,7 @@ fun SearchUserBar(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
},
|
||||
colors = if (!active) SearchBarDefaults.colors() else SearchBarDefaults.colors(containerColor = Color.Transparent),
|
||||
|
|
@ -113,31 +120,43 @@ fun SearchUserBar(
|
|||
)
|
||||
}
|
||||
|
||||
LazyColumn {
|
||||
if (isMultiSelectionEnabled) {
|
||||
items(results) { matrixUser ->
|
||||
SearchMultipleUsersResultItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
matrixUser = matrixUser,
|
||||
isUserSelected = selectedUsers.find { it.id == matrixUser.id } != null,
|
||||
onCheckedChange = { checked ->
|
||||
if (checked) {
|
||||
onUserSelected(matrixUser)
|
||||
} else {
|
||||
onUserDeselected(matrixUser)
|
||||
|
||||
if (state is UserSearchResultState.Results) {
|
||||
LazyColumn {
|
||||
if (isMultiSelectionEnabled) {
|
||||
items(state.results) { matrixUser ->
|
||||
SearchMultipleUsersResultItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
matrixUser = matrixUser,
|
||||
isUserSelected = selectedUsers.find { it.id == matrixUser.id } != null,
|
||||
onCheckedChange = { checked ->
|
||||
if (checked) {
|
||||
onUserSelected(matrixUser)
|
||||
} else {
|
||||
onUserDeselected(matrixUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
items(results) { matrixUser ->
|
||||
SearchSingleUserResultItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
matrixUser = matrixUser,
|
||||
onClick = { onUserSelected(matrixUser) }
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
items(state.results) { matrixUser ->
|
||||
SearchSingleUserResultItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
matrixUser = matrixUser,
|
||||
onClick = { onUserSelected(matrixUser) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (state is UserSearchResultState.NoResults) {
|
||||
Spacer(Modifier.size(80.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.common_no_results),
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.tertiary,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ fun UserListView(
|
|||
SearchUserBar(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
query = state.searchQuery,
|
||||
results = state.searchResults,
|
||||
state = state.searchResults,
|
||||
selectedUsers = state.selectedUsers,
|
||||
active = state.isSearchActive,
|
||||
isMultiSelectionEnabled = state.isMultiSelectionEnabled,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue