From fcb871e1fd5ab893ced41d80d33d5bf1502e4c43 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:33:05 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20show=20proper=20empty=20user=20search=20?= =?UTF-8?q?screens=20[WPB-6257]=20=F0=9F=8D=92=20=F0=9F=8D=92=20(#3618)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Mohamad Jaara Co-authored-by: MichaƂ Saleniuk --- .../search/SearchAllPeopleScreen.kt | 150 +++++------------- .../search/SearchAllServicesScreen.kt | 72 +++++++-- .../search/SearchServicesViewModel.kt | 14 +- .../search/SearchUserViewModel.kt | 8 +- .../search/SearchUsersAndServicesScreen.kt | 4 +- .../search/widget/SearchFailureWidget.kt | 17 +- 6 files changed, 113 insertions(+), 152 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllPeopleScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllPeopleScreen.kt index a1f22068e1..7f4d467678 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllPeopleScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllPeopleScreen.kt @@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn @@ -37,16 +36,15 @@ import androidx.compose.runtime.SideEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import com.wire.android.R import com.wire.android.model.Clickable import com.wire.android.model.ItemActionType import com.wire.android.ui.common.button.WireSecondaryButton import com.wire.android.ui.common.dimensions -import com.wire.android.ui.common.progress.WireCircularProgressIndicator +import com.wire.android.ui.common.progress.CenteredCircularProgressBarIndicator +import com.wire.android.ui.home.conversations.search.widget.SearchFailureBox import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.android.ui.home.newconversation.model.Contact import com.wire.android.ui.theme.WireTheme @@ -82,10 +80,15 @@ fun SearchAllPeopleScreen( onPublicResultsExpansionChanged: (Boolean) -> Unit = {}, lazyListState: LazyListState = rememberLazyListState() ) { - if (contactsSearchResult.isEmpty() && publicSearchResult.isEmpty()) { - EmptySearchQueryScreen() - } else { - SearchResult( + val emptyResults = contactsSearchResult.isEmpty() && publicSearchResult.isEmpty() + when { + isLoading -> CenteredCircularProgressBarIndicator() + + searchQuery.isBlank() && emptyResults -> EmptySearchQueryScreen() + + searchQuery.isNotBlank() && emptyResults -> SearchFailureBox(R.string.label_no_results_found) + + else -> SearchResult( searchQuery = searchQuery, publicSearchResult = publicSearchResult, contactsSearchResult = contactsSearchResult, @@ -94,7 +97,6 @@ fun SearchAllPeopleScreen( onOpenUserProfile = onOpenUserProfile, lazyListState = lazyListState, isSearchActive = isSearchActive, - isLoading = isLoading, actionType = actionType, selectedContactResultsExpanded = selectedContactResultsExpanded, onSelectedContactResultsExpansionChanged = onSelectedContactResultsExpansionChanged, @@ -112,7 +114,6 @@ private fun SearchResult( contactsSearchResult: ImmutableList, publicSearchResult: ImmutableList, contactsSelectedSearchResult: ImmutableList, - isLoading: Boolean, isSearchActive: Boolean, actionType: ItemActionType, onChecked: (Boolean, Contact) -> Unit, @@ -140,8 +141,7 @@ private fun SearchResult( searchTitle = context.getString(R.string.label_selected) + " (${contactsSelectedSearchResult.size})", searchQuery = searchQuery, onChecked = onChecked, - isLoading = isLoading, - contactSearchResult = contactsSelectedSearchResult.map { it to true }.toImmutableList(), + searchResult = contactsSelectedSearchResult.map { it to true }.toImmutableList(), allItemsVisible = true, // for selected contacts we always show all items showMoreOrLessButtonVisible = false, onShowAllButtonClicked = searchPeopleScreenState::toggleShowAllContactsResult, @@ -157,8 +157,7 @@ private fun SearchResult( searchTitle = context.getString(R.string.label_contacts), searchQuery = searchQuery, onChecked = onChecked, - isLoading = isLoading, - contactSearchResult = contactsSearchResult.map { it to false }.toImmutableList(), + searchResult = contactsSearchResult.map { it to false }.toImmutableList(), allItemsVisible = !isSearchActive || searchPeopleScreenState.contactsAllResultsCollapsed, showMoreOrLessButtonVisible = isSearchActive, onShowAllButtonClicked = searchPeopleScreenState::toggleShowAllContactsResult, @@ -173,8 +172,7 @@ private fun SearchResult( externalSearchResults( searchTitle = context.getString(R.string.label_public_wire), searchQuery = searchQuery, - contactSearchResult = publicSearchResult, - isLoading = isLoading, + searchResult = publicSearchResult, allItemsVisible = searchPeopleScreenState.publicResultsCollapsed, showMoreOrLessButtonVisible = isSearchActive, onShowAllButtonClicked = searchPeopleScreenState::toggleShowAllPublicResult, @@ -193,78 +191,6 @@ private fun SearchResult( @Suppress("LongParameterList") private fun LazyListScope.internalSearchResults( - searchTitle: String, - searchQuery: String, - onChecked: (Boolean, Contact) -> Unit, - actionType: ItemActionType, - isLoading: Boolean, - contactSearchResult: ImmutableList>, - allItemsVisible: Boolean, - showMoreOrLessButtonVisible: Boolean, - onShowAllButtonClicked: () -> Unit, - onOpenUserProfile: (Contact) -> Unit, - expanded: Boolean, - onExpansionChanged: (Boolean) -> Unit, -) { - when { - isLoading -> { - inProgressItem() - } - - else -> { - internalSuccessItem( - searchTitle = searchTitle, - allItemsVisible = allItemsVisible, - showMoreOrLessButtonVisible = showMoreOrLessButtonVisible, - onChecked = onChecked, - searchResult = contactSearchResult, - searchQuery = searchQuery, - onShowAllButtonClicked = onShowAllButtonClicked, - onOpenUserProfile = onOpenUserProfile, - actionType = actionType, - expanded = expanded, - onExpansionChanged = onExpansionChanged, - ) - } - } -} - -@Suppress("LongParameterList") -private fun LazyListScope.externalSearchResults( - searchTitle: String, - searchQuery: String, - contactSearchResult: ImmutableList, - isLoading: Boolean, - allItemsVisible: Boolean, - showMoreOrLessButtonVisible: Boolean, - onShowAllButtonClicked: () -> Unit, - onOpenUserProfile: (Contact) -> Unit, - expanded: Boolean, - onExpansionChanged: (Boolean) -> Unit, -) { - when { - isLoading -> { - inProgressItem() - } - - else -> { - externalSuccessItem( - searchTitle = searchTitle, - allItemsVisible = allItemsVisible, - showMoreOrLessButtonVisible = showMoreOrLessButtonVisible, - searchResult = contactSearchResult, - searchQuery = searchQuery, - onShowAllButtonClicked = onShowAllButtonClicked, - onOpenUserProfile = onOpenUserProfile, - expanded = expanded, - onExpansionChanged = onExpansionChanged, - ) - } - } -} - -@Suppress("LongParameterList") -private fun LazyListScope.internalSuccessItem( searchTitle: String, allItemsVisible: Boolean, showMoreOrLessButtonVisible: Boolean, @@ -330,7 +256,7 @@ private fun LazyListScope.internalSuccessItem( } @Suppress("LongParameterList") -private fun LazyListScope.externalSuccessItem( +private fun LazyListScope.externalSearchResults( searchTitle: String, allItemsVisible: Boolean, showMoreOrLessButtonVisible: Boolean, @@ -383,23 +309,6 @@ private fun LazyListScope.externalSuccessItem( } } -fun LazyListScope.inProgressItem() { - item { - Box( - Modifier - .fillMaxWidth() - .height(224.dp) - ) { - WireCircularProgressIndicator( - progressColor = Color.Black, - modifier = Modifier.align( - Alignment.Center - ) - ) - } - } -} - @Composable private fun ShowButton( isShownAll: Boolean, @@ -445,10 +354,11 @@ fun PreviewSearchAllPeopleScreen_Loading() = WireTheme { @PreviewMultipleThemes @Composable -fun PreviewSearchAllPeopleScreen_EmptyList() = WireTheme { +fun PreviewSearchAllPeopleScreen_InitialResults() = WireTheme { + val contacts = previewContactsList(count = 10, startIndex = 0, isContact = true) SearchAllPeopleScreen( - searchQuery = "Search query", - contactsSearchResult = persistentListOf(), + searchQuery = "", + contactsSearchResult = contacts, publicSearchResult = persistentListOf(), contactsSelectedSearchResult = persistentListOf(), isLoading = false, @@ -461,11 +371,10 @@ fun PreviewSearchAllPeopleScreen_EmptyList() = WireTheme { @PreviewMultipleThemes @Composable -fun PreviewSearchAllPeopleScreen_NoSearch() = WireTheme { - val contacts = previewContactsList(count = 10, startIndex = 0, isContact = true) +fun PreviewSearchAllPeopleScreen_EmptyInitialResults() = WireTheme { SearchAllPeopleScreen( searchQuery = "", - contactsSearchResult = contacts, + contactsSearchResult = persistentListOf(), publicSearchResult = persistentListOf(), contactsSelectedSearchResult = persistentListOf(), isLoading = false, @@ -514,6 +423,23 @@ fun PreviewSearchAllPeopleScreen_SearchResults_TypeCheck() = WireTheme { ) } +@PreviewMultipleThemes +@Composable +fun PreviewSearchAllPeopleScreen_EmptySearchResults() = WireTheme { + SearchAllPeopleScreen( + searchQuery = "Con", + contactsSearchResult = persistentListOf(), + publicSearchResult = persistentListOf(), + contactsSelectedSearchResult = persistentListOf(), + isLoading = false, + isSearchActive = true, + actionType = ItemActionType.CLICK, + onChecked = { _, _ -> }, + onOpenUserProfile = {}, + selectedContactResultsExpanded = true, + ) +} + private fun previewContact(index: Int, isContact: Boolean) = Contact( id = index.toString(), domain = "wire.com", diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllServicesScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllServicesScreen.kt index 79edbf100a..010c095faa 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllServicesScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchAllServicesScreen.kt @@ -39,9 +39,15 @@ import com.wire.android.ui.common.avatar.UserProfileAvatar import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.progress.CenteredCircularProgressBarIndicator import com.wire.android.ui.home.conversations.search.widget.SearchFailureBox +import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.android.ui.home.newconversation.model.Contact import com.wire.android.util.extension.folderWithElements +import com.wire.android.ui.theme.WireTheme +import com.wire.android.util.ui.PreviewMultipleThemes +import com.wire.kalium.logic.data.user.ConnectionState import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList @Composable fun SearchAllServicesScreen( @@ -59,7 +65,6 @@ fun SearchAllServicesScreen( onServiceClicked = onServiceClicked, result = searchServicesViewModel.state.result, lazyListState = lazyListState, - error = searchServicesViewModel.state.error, isLoading = searchServicesViewModel.state.isLoading ) } @@ -69,27 +74,23 @@ private fun SearchAllServicesContent( searchQuery: String, result: ImmutableList, isLoading: Boolean, - error: Boolean, onServiceClicked: (Contact) -> Unit, lazyListState: LazyListState = rememberLazyListState() ) { when { isLoading -> CenteredCircularProgressBarIndicator() - error -> SearchFailureBox(failureMessage = R.string.label_general_error) // TODO(user experience): what to do when user team has no services? - result.isEmpty() -> { - EmptySearchQueryScreen() - } + searchQuery.isBlank() && result.isEmpty() -> EmptySearchQueryScreen() - else -> { - SuccessServicesList( - searchQuery = searchQuery, - onServiceClicked = onServiceClicked, - services = result, - lazyListState = lazyListState - ) - } + searchQuery.isNotBlank() && result.isEmpty() -> SearchFailureBox(R.string.label_no_results_found) + + else -> SuccessServicesList( + searchQuery = searchQuery, + onServiceClicked = onServiceClicked, + services = result, + lazyListState = lazyListState + ) } } @@ -141,3 +142,46 @@ private fun SuccessServicesList( } } } + +@PreviewMultipleThemes +@Composable +fun PreviewSearchAllServicesScreen_Loading() = WireTheme { + SearchAllServicesContent("", persistentListOf(), true, {}) +} + +@PreviewMultipleThemes +@Composable +fun PreviewSearchAllServicesScreen_InitialResults() = WireTheme { + SearchAllServicesContent("", previewServiceList(count = 10).toPersistentList(), false, {}) +} + +@PreviewMultipleThemes +@Composable +fun PreviewSearchAllServicesScreen_EmptyInitialResults() = WireTheme { + SearchAllServicesContent("", persistentListOf(), false, {}) +} + +@PreviewMultipleThemes +@Composable +fun PreviewSearchAllServicesScreen_SearchResults() = WireTheme { + SearchAllServicesContent("Serv", previewServiceList(count = 10).toPersistentList(), false, {}) +} + +@PreviewMultipleThemes +@Composable +fun PreviewSearchAllServicesScreen_EmptySearchResults() = WireTheme { + SearchAllServicesContent("Serv", persistentListOf(), false, {}) +} + +private fun previewService(index: Int) = Contact( + id = index.toString(), + domain = "wire.com", + name = "Service nr $index", + handle = "service_$index", + connectionState = ConnectionState.NOT_CONNECTED, + membership = Membership.Service, +) + +private fun previewServiceList(count: Int): List = buildList { + repeat(count) { index -> add(previewService(index)) } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchServicesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchServicesViewModel.kt index 31aef8992f..16b345f61a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchServicesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchServicesViewModel.kt @@ -46,7 +46,7 @@ class SearchServicesViewModel @Inject constructor( private val searchServicesByName: SearchServicesByNameUseCase, ) : ViewModel() { private val searchQueryTextFlow = MutableStateFlow(String.EMPTY) - var state: SearchServicesState by mutableStateOf(SearchServicesState()) + var state: SearchServicesState by mutableStateOf(SearchServicesState(isLoading = true)) private set init { @@ -68,15 +68,12 @@ class SearchServicesViewModel @Inject constructor( private fun search(query: String) { viewModelScope.launch { - if (query.isEmpty()) { - getAllServices().first().also { services -> - state = state.copy(result = services.map(contactMapper::fromService).toImmutableList(), searchQuery = query) - } + val result = if (query.isEmpty()) { + getAllServices().first() } else { - searchServicesByName(query).first().also { services -> - state = state.copy(result = services.map(contactMapper::fromService).toImmutableList(), searchQuery = query) - } + searchServicesByName(query).first() } + state = state.copy(isLoading = false, searchQuery = query, result = result.map(contactMapper::fromService).toImmutableList()) } } } @@ -85,5 +82,4 @@ data class SearchServicesState( val result: ImmutableList = persistentListOf(), val searchQuery: String = String.EMPTY, val isLoading: Boolean = false, - val error: Boolean = false ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUserViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUserViewModel.kt index f0d221fd2a..d40a68298d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUserViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUserViewModel.kt @@ -73,7 +73,7 @@ class SearchUserViewModel @Inject constructor( private val searchQueryTextFlow = MutableStateFlow(String.EMPTY) private val selectedContactsFlow = MutableStateFlow>(persistentSetOf()) - var state: SearchUserState by mutableStateOf(SearchUserState()) + var state: SearchUserState by mutableStateOf(SearchUserState(isLoading = true)) private set init { @@ -114,7 +114,8 @@ class SearchUserViewModel @Inject constructor( contactsResult = newState.contactsResult, publicResult = newState.publicResult, selectedResult = newState.selectedResult, - searchQuery = newState.searchQuery + searchQuery = newState.searchQuery, + isLoading = false, ) } } @@ -180,5 +181,6 @@ data class SearchUserState( val publicResult: ImmutableList = persistentListOf(), val selectedResult: ImmutableList = persistentListOf(), val searchQuery: String = String.EMPTY, - val isOtherDomainAllowed: Boolean = false + val isOtherDomainAllowed: Boolean = false, + val isLoading: Boolean = false, ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUsersAndServicesScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUsersAndServicesScreen.kt index 4223f71d9b..ff3801d6bf 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUsersAndServicesScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchUsersAndServicesScreen.kt @@ -166,7 +166,6 @@ fun SearchUsersAndServicesScreen( onOpenUserProfile = onOpenUserProfile, onContactChecked = onContactChecked, isSearchActive = searchBarState.isSearchActive, - isLoading = false, // TODO: update correctly actionType = actionType, lazyListState = lazyListStates[pageIndex], ) @@ -237,7 +236,6 @@ enum class SearchPeopleScreenType { private fun SearchAllPeopleOrContactsScreen( searchQuery: String, contactsSelected: ImmutableSet, - isLoading: Boolean, isSearchActive: Boolean, actionType: ItemActionType, onOpenUserProfile: (Contact) -> Unit, @@ -270,7 +268,7 @@ private fun SearchAllPeopleOrContactsScreen( onOpenUserProfile = onOpenUserProfile, lazyListState = lazyListState, isSearchActive = isSearchActive, - isLoading = isLoading, + isLoading = searchUserViewModel.state.isLoading, actionType = actionType, selectedContactResultsExpanded = selectedContactResultsExpanded, onSelectedContactResultsExpansionChanged = remember { { selectedContactResultsExpanded = it } }, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/widget/SearchFailureWidget.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/widget/SearchFailureWidget.kt index a23c7b753f..1509b860b1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/widget/SearchFailureWidget.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/widget/SearchFailureWidget.kt @@ -20,26 +20,21 @@ package com.wire.android.ui.home.conversations.search.widget import androidx.annotation.StringRes import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp +import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.ui.PreviewMultipleThemes @Composable fun SearchFailureBox(@StringRes failureMessage: Int, modifier: Modifier = Modifier) { - Box( - modifier - .fillMaxWidth() - .height(224.dp) - ) { + Box(modifier.fillMaxSize()) { Text( stringResource(id = failureMessage), modifier = Modifier.align(Alignment.Center), @@ -48,8 +43,8 @@ fun SearchFailureBox(@StringRes failureMessage: Int, modifier: Modifier = Modifi } } -@Preview +@PreviewMultipleThemes @Composable -fun SearchFailureBoxPreview() { +fun SearchFailureBoxPreview() = WireTheme { SearchFailureBox(failureMessage = com.wire.android.R.string.label_no_results_found) }