From a920a776e3d3a01729248218b7b1abd50b7ff37e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= <30429749+saleniuk@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:45:40 +0200 Subject: [PATCH 1/2] fix: show proper empty screen when no contacts [WPB-250] (#2141) Co-authored-by: Oussama Hassine Co-authored-by: Yamil Medina --- .../search/SearchContactsScreen.kt | 92 ++++++++++++++++--- .../search/SearchPeopleViewModel.kt | 14 +-- .../res/drawable/ic_empty_contacts_arrow.xml | 9 ++ app/src/main/res/values/strings.xml | 1 + .../NewConversationViewModelArrangement.kt | 13 +++ .../NewConversationViewModelTest.kt | 42 +++++++++ 6 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 app/src/main/res/drawable/ic_empty_contacts_arrow.xml diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchContactsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchContactsScreen.kt index fb866ddb8a8..4b36cc87742 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchContactsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchContactsScreen.kt @@ -18,6 +18,8 @@ package com.wire.android.ui.home.conversations.search +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -33,9 +35,10 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import com.wire.android.R import com.wire.android.model.Clickable import com.wire.android.model.UserAvatarData @@ -49,10 +52,13 @@ 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 import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography import com.wire.android.util.extension.folderWithElements +import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.user.ConnectionState +import kotlinx.collections.immutable.persistentListOf @Composable fun SearchContactsScreen( @@ -105,9 +111,26 @@ fun SearchContactsScreen( SearchFailureBox(failureMessage = allKnownContactResult.failureString) } - // TODO: what to do when user has no contacts ? SearchResultState.EmptyResult -> { - /*NO OP*/ + Column( + modifier = Modifier + .fillMaxSize() + .padding(dimensions().spacing40x), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(id = R.drawable.ic_empty_contacts_arrow), + contentDescription = "" + ) + Text( + modifier = Modifier.padding(top = dimensions().spacing16x), + text = stringResource(R.string.label_empty_contacts_list), + style = MaterialTheme.wireTypography.body01, + textAlign = TextAlign.Center, + color = MaterialTheme.wireColorScheme.onSurface, + ) + } } } } @@ -173,17 +196,58 @@ private fun ContactItem( ) } -@Preview +@PreviewMultipleThemes @Composable fun PreviewContactItem() { - ContactItem( - name = "Name", - avatarData = UserAvatarData(), - membership = Membership.Admin, - belongsToGroup = true, - connectionState = ConnectionState.ACCEPTED, - addToGroup = { }, - removeFromGroup = { }, - openUserProfile = { } - ) + WireTheme { + ContactItem( + name = "Name", + avatarData = UserAvatarData(), + membership = Membership.Admin, + belongsToGroup = true, + connectionState = ConnectionState.ACCEPTED, + addToGroup = { }, + removeFromGroup = { }, + openUserProfile = { } + ) + } +} + +@PreviewMultipleThemes +@Composable +fun PreviewSearchContactsScreen() { + WireTheme { + SearchContactsScreen( + allKnownContactResult = SearchResultState.Success( + persistentListOf( + Contact( + id = "1", + domain = "domain", + name = "Name", + avatarData = UserAvatarData(), + membership = Membership.Admin, + connectionState = ConnectionState.ACCEPTED + ) + ) + ), + contactsAddedToGroup = listOf(), + onOpenUserProfile = { }, + onAddToGroup = { }, + onRemoveFromGroup = { } + ) + } +} + +@PreviewMultipleThemes +@Composable +fun PreviewSearchContactsScreenEmpty() { + WireTheme { + SearchContactsScreen( + allKnownContactResult = SearchResultState.EmptyResult, + contactsAddedToGroup = listOf(), + onOpenUserProfile = { }, + onAddToGroup = { }, + onRemoveFromGroup = { } + ) + } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchPeopleViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchPeopleViewModel.kt index 26d9893e3db..7aa49bfa8ef 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchPeopleViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/search/SearchPeopleViewModel.kt @@ -257,12 +257,14 @@ abstract class SearchPeopleViewModel : ViewModel() { } is SearchResult.Success -> { - SearchResultState.Success( - result - .contacts - .sortedBy { it.name.lowercase() } - .toImmutableList() - ) + result + .contacts + .sortedBy { it.name.lowercase() } + .toImmutableList() + .let { + if (it.isEmpty()) SearchResultState.EmptyResult + else SearchResultState.Success(it) + } } } } diff --git a/app/src/main/res/drawable/ic_empty_contacts_arrow.xml b/app/src/main/res/drawable/ic_empty_contacts_arrow.xml new file mode 100644 index 00000000000..f401f1d7503 --- /dev/null +++ b/app/src/main/res/drawable/ic_empty_contacts_arrow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3901b294463..d43e140b080 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -673,6 +673,7 @@ Something went wrong People Services + Search for people by their name or username to start a conversation 🔍 Something went wrong Please try again diff --git a/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelArrangement.kt index 3ca54b27483..485ca4f0dbc 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelArrangement.kt @@ -31,6 +31,7 @@ import com.wire.android.ui.home.newconversation.common.CreateGroupState import com.wire.android.ui.home.newconversation.model.Contact import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.conversation.MutedConversationStatus import com.wire.kalium.logic.data.id.ConversationId @@ -213,6 +214,18 @@ internal class NewConversationViewModelArrangement { coEvery { searchKnownUsers(any()) } returns flowOf(SearchUsersResult.Failure.InvalidRequest) } + fun withFailureGetAllKnownUsersResponse() = apply { + coEvery { getAllKnownUsers() } returns flowOf(GetAllContactsResult.Failure(StorageFailure.DataNotFound)) + } + + fun withEmptySuccessGetAllKnownUsersResponse() = apply { + coEvery { getAllKnownUsers() } returns flowOf(GetAllContactsResult.Success(emptyList())) + } + + fun withSuccessGetAllKnownUsersResponse() = apply { + coEvery { getAllKnownUsers() } returns flowOf(GetAllContactsResult.Success(listOf(FEDERATED_KNOWN_USER))) + } + fun withFailurePublicSearchResponse() = apply { coEvery { searchPublicUsers(any()) } returns flowOf(SearchUsersResult.Failure.InvalidRequest) } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelTest.kt index 121b98ed3a6..932c0a2a126 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModelTest.kt @@ -247,4 +247,46 @@ class NewConversationViewModelTest { ) } } + + @Test + fun `given user has no contacts and getting contacts succeeded, then initialContacts has value of EmptyResult`() { + runTest { + // Given + val (_, viewModel) = NewConversationViewModelArrangement() + .withIsSelfTeamMember(true) + .withEmptySuccessGetAllKnownUsersResponse() + .arrange() + advanceUntilIdle() + // Then + assert(viewModel.state.initialContacts is SearchResultState.EmptyResult) + } + } + + @Test + fun `given user has some contacts and getting contacts succeeded, then initialContacts has value of Success`() { + runTest { + // Given + val (_, viewModel) = NewConversationViewModelArrangement() + .withIsSelfTeamMember(true) + .withSuccessGetAllKnownUsersResponse() + .arrange() + advanceUntilIdle() + // Then + assert(viewModel.state.initialContacts is SearchResultState.Success) + } + } + + @Test + fun `given user has some contacts and getting contacts failed, then initialContacts has value of Failure`() { + runTest { + // Given + val (_, viewModel) = NewConversationViewModelArrangement() + .withIsSelfTeamMember(true) + .withFailureGetAllKnownUsersResponse() + .arrange() + advanceUntilIdle() + // Then + assert(viewModel.state.initialContacts is SearchResultState.Failure) + } + } } From 7c8b64cfa4b4d1372c3ffbd88696706b23e8c4a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Mon, 4 Sep 2023 17:34:37 +0200 Subject: [PATCH 2/2] trigger build