Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: show proper empty screen when no contacts [WPB-250] #2175

Merged
merged 2 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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,
)
}
}
}
}
Expand Down Expand Up @@ -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 = { }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/ic_empty_contacts_arrow.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="110dp"
android:height="169dp"
android:viewportWidth="110"
android:viewportHeight="169">
<path
android:pathData="M107.09,167.86C107.01,168.41 107.39,168.91 107.94,168.99C108.49,169.07 108.99,168.69 109.07,168.14L107.09,167.86ZM9.43,0.42C9.11,-0.03 8.48,-0.14 8.03,0.18L0.7,5.4C0.25,5.72 0.14,6.34 0.46,6.79C0.78,7.24 1.4,7.35 1.85,7.03L8.38,2.39L13.01,8.92C13.33,9.37 13.95,9.47 14.4,9.15C14.85,8.83 14.96,8.21 14.64,7.76L9.43,0.42ZM109.07,168.14C112.36,144.63 106.62,128.71 96.35,116.51C86.15,104.38 71.5,95.97 57.21,87.37C42.86,78.72 28.84,69.87 19.45,56.85C10.1,43.88 5.28,26.67 9.6,1.17L7.63,0.83C3.23,26.78 8.12,44.55 17.82,58.02C27.49,71.42 41.86,80.46 56.18,89.08C70.57,97.74 84.88,105.98 94.82,117.8C104.7,129.54 110.31,144.88 107.09,167.86L109.07,168.14Z"
android:fillColor="#9FA1A7"/>
</vector>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@
<string name="label_general_error">Something went wrong</string>
<string name="label_add_member_people">People</string>
<string name="label_add_member_services">Services</string>
<string name="label_empty_contacts_list">Search for people by their name or username to start a conversation 🔍</string>
<!-- Snackbar messages -->
<string name="error_unknown_title">Something went wrong</string>
<string name="error_unknown_message">Please try again</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Loading