Skip to content

Commit

Permalink
feat: support remote search by handle (#2647)
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamadJaara committed Feb 9, 2024
1 parent 6b55105 commit 8dc5948
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 13 deletions.
Expand Up @@ -64,6 +64,7 @@ class KaliumConfigsModule {
wipeOnRootedDevice = BuildConfig.WIPE_ON_ROOTED_DEVICE,
isWebSocketEnabledByDefault = isWebsocketEnabledByDefault(context),
certPinningConfig = BuildConfig.CERTIFICATE_PINNING_CONFIG,
maxRemoteSearchResultCount = BuildConfig.MAX_REMOTE_SEARCH_RESULT_COUNT
)
}
}
Expand Up @@ -22,6 +22,7 @@ import com.wire.android.di.KaliumCoreLogic
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.search.FederatedSearchParser
import com.wire.kalium.logic.feature.search.SearchByHandleUseCase
import com.wire.kalium.logic.feature.search.SearchScope
import com.wire.kalium.logic.feature.search.SearchUsersUseCase
import dagger.Module
Expand All @@ -45,6 +46,10 @@ class SearchModule {
@Provides
fun provideSearchUsersUseCase(searchScope: SearchScope): SearchUsersUseCase = searchScope.searchUsers

@ViewModelScoped
@Provides
fun provideSearchByHandleUseCase(searchScope: SearchScope): SearchByHandleUseCase = searchScope.searchByHandle

@ViewModelScoped
@Provides
fun provideFederatedSearchParser(searchScope: SearchScope): FederatedSearchParser = searchScope.federatedSearchParser
Expand Down
Expand Up @@ -17,10 +17,4 @@
*/
package com.wire.android.ui.home.conversations.search

fun String.removeQueryPrefix(): String {
return if (startsWith("@")) {
removePrefix("@")
} else {
this
}
}
fun String.removeQueryPrefix(): String = removePrefix("@")
Expand Up @@ -26,7 +26,10 @@ import androidx.lifecycle.viewModelScope
import com.wire.android.mapper.ContactMapper
import com.wire.android.ui.home.newconversation.model.Contact
import com.wire.android.ui.navArgs
import com.wire.kalium.logic.feature.auth.ValidateUserHandleResult
import com.wire.kalium.logic.feature.auth.ValidateUserHandleUseCase
import com.wire.kalium.logic.feature.search.FederatedSearchParser
import com.wire.kalium.logic.feature.search.SearchByHandleUseCase
import com.wire.kalium.logic.feature.search.SearchUsersUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
Expand All @@ -39,8 +42,10 @@ import javax.inject.Inject
@HiltViewModel
class SearchUserViewModel @Inject constructor(
private val searchUserUseCase: SearchUsersUseCase,
private val searchByHandleUseCase: SearchByHandleUseCase,
private val contactMapper: ContactMapper,
private val federatedSearchParser: FederatedSearchParser,
private val validateUserHandle: ValidateUserHandleUseCase,
savedStateHandle: SavedStateHandle
) : ViewModel() {

Expand All @@ -61,7 +66,29 @@ class SearchUserViewModel @Inject constructor(
@VisibleForTesting
suspend fun safeSearch(query: String) {
val (searchTerm, domain) = federatedSearchParser(query)
val isHandleSearch = validateUserHandle(searchTerm.removeQueryPrefix()) is ValidateUserHandleResult.Valid

if (isHandleSearch) {
searchByHandle(searchTerm, domain)
} else {
searchByName(searchTerm, domain)
}
}

private suspend fun searchByHandle(searchTerm: String, domain: String?) {
searchByHandleUseCase(
searchTerm,
excludingConversation = addMembersSearchNavArgs?.conversationId,
customDomain = domain
).also { userSearchEntities ->
state = state.copy(
contactsResult = userSearchEntities.connected.map(contactMapper::fromSearchUserResult).toImmutableList(),
publicResult = userSearchEntities.notConnected.map(contactMapper::fromSearchUserResult).toImmutableList()
)
}
}

private suspend fun searchByName(searchTerm: String, domain: String?) {
searchUserUseCase(
searchTerm,
excludingMembersOfConversation = addMembersSearchNavArgs?.conversationId,
Expand Down
Expand Up @@ -31,7 +31,11 @@ import com.wire.kalium.logic.data.publicuser.model.UserSearchDetails
import com.wire.kalium.logic.data.user.ConnectionState
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.data.user.type.UserType
import com.wire.kalium.logic.feature.auth.ValidateUserHandleResult
import com.wire.kalium.logic.feature.auth.ValidateUserHandleUseCase
import com.wire.kalium.logic.feature.search.FederatedSearchParser
import com.wire.kalium.logic.feature.search.SearchByHandleUseCase
import com.wire.kalium.logic.feature.search.SearchUserResult
import com.wire.kalium.logic.feature.search.SearchUsersUseCase
import io.mockk.MockKAnnotations
import io.mockk.coEvery
Expand All @@ -54,7 +58,7 @@ class SearchUserViewModelTest {
val (arrangement, viewModel) = Arrangement()
.withAddMembersSearchNavArgsThatThrowsException()
.withSearchResult(
SearchUsersUseCase.Result(
SearchUserResult(
connected = listOf(),
notConnected = listOf()
)
Expand All @@ -65,6 +69,7 @@ class SearchUserViewModelTest {
domain = "domain"
)
)
.withIsValidHandleResult(ValidateUserHandleResult.Invalid.TooLong(""))
.arrange()

viewModel.safeSearch(query)
Expand All @@ -90,7 +95,7 @@ class SearchUserViewModelTest {
val (arrangement, viewModel) = Arrangement()
.withAddMembersSearchNavArgs(AddMembersSearchNavArgs(conversationId, true))
.withSearchResult(
SearchUsersUseCase.Result(
SearchUserResult(
connected = listOf(),
notConnected = listOf()
)
Expand All @@ -101,6 +106,7 @@ class SearchUserViewModelTest {
domain = "domain"
)
)
.withIsValidHandleResult(ValidateUserHandleResult.Invalid.TooLong(""))
.arrange()

viewModel.safeSearch(query)
Expand All @@ -122,7 +128,7 @@ class SearchUserViewModelTest {
fun `given searchUserUseCase returns a list of connected users, when calling the searchUseCase, then contactsResult is set`() =
runTest {

val result = SearchUsersUseCase.Result(
val result = SearchUserResult(
connected = listOf(
UserSearchDetails(
id = UserId("connected", "domain"),
Expand Down Expand Up @@ -157,6 +163,7 @@ class SearchUserViewModelTest {
domain = "domain"
)
)
.withIsValidHandleResult(ValidateUserHandleResult.Invalid.TooLong(""))
.arrange()

viewModel.safeSearch(query)
Expand All @@ -177,6 +184,40 @@ class SearchUserViewModelTest {
assertEquals(result.notConnected.map(arrangement::fromSearchUserResult), viewModel.state.publicResult)
}

@Test
fun `given search term is a valid handle, when searching, then search by handle`() = runTest {
val query = "query"
val (arrangement, viewModel) = Arrangement()
.withAddMembersSearchNavArgsThatThrowsException()
.withSearchByHandleResult(
SearchUserResult(
connected = listOf(),
notConnected = listOf()
)
)
.withFederatedSearchParserResult(
FederatedSearchParser.Result(
searchTerm = query,
domain = "domain"
)
)
.withIsValidHandleResult(ValidateUserHandleResult.Valid(""))
.arrange()

viewModel.safeSearch(query)
coVerify(exactly = 1) {
arrangement.searchByHandleUseCase.invoke(
query,
excludingConversation = null,
customDomain = "domain"
)
}

coVerify(exactly = 1) {
arrangement.federatedSearchParser(any())
}
}

private class Arrangement {

@MockK
Expand All @@ -191,6 +232,11 @@ class SearchUserViewModelTest {
@MockK
lateinit var federatedSearchParser: FederatedSearchParser

@MockK
lateinit var validateUserHandle: ValidateUserHandleUseCase

@MockK
lateinit var searchByHandleUseCase: SearchByHandleUseCase
init {
MockKAnnotations.init(this, relaxUnitFun = true)
every { contactMapper.fromSearchUserResult(any()) } answers {
Expand Down Expand Up @@ -232,21 +278,31 @@ class SearchUserViewModelTest {
}
}

fun withSearchResult(result: SearchUsersUseCase.Result) = apply {
fun withSearchResult(result: SearchUserResult) = apply {
coEvery { searchUsersUseCase(any(), any(), any()) } returns result
}

fun withFederatedSearchParserResult(result: FederatedSearchParser.Result) = apply {
coEvery { federatedSearchParser(any()) } returns result
}

fun withIsValidHandleResult(result: ValidateUserHandleResult) = apply {
coEvery { validateUserHandle(any()) } returns result
}

fun withSearchByHandleResult(result: SearchUserResult) = apply {
coEvery { searchByHandleUseCase(any(), any(), any()) } returns result
}

private lateinit var searchUserViewModel: SearchUserViewModel

fun arrange() = apply {
searchUserViewModel = SearchUserViewModel(
searchUsersUseCase,
searchByHandleUseCase,
contactMapper,
federatedSearchParser,
validateUserHandle,
savedStateHandle
)
}.run {
Expand Down
4 changes: 3 additions & 1 deletion buildSrc/src/main/kotlin/customization/FeatureConfigs.kt
Expand Up @@ -96,5 +96,7 @@ enum class FeatureConfigs(val value: String, val configType: ConfigType) {
CERTIFICATE_PINNING_CONFIG("cert_pinning_config", ConfigType.MapOfStringToListOfStrings),
// TODO: Add support for default proxy configs

IS_PASSWORD_PROTECTED_GUEST_LINK_ENABLED("is_password_protected_guest_link_enabled", ConfigType.BOOLEAN)
IS_PASSWORD_PROTECTED_GUEST_LINK_ENABLED("is_password_protected_guest_link_enabled", ConfigType.BOOLEAN),

MAX_REMOTE_SEARCH_RESULT_COUNT("max_remote_search_result_count", ConfigType.INT)
}
3 changes: 2 additions & 1 deletion default.json
Expand Up @@ -109,5 +109,6 @@
"is_password_protected_guest_link_enabled": false,
"url_rss_release_notes": "https://medium.com/feed/wire-news/tagged/android",
"team_app_lock": false,
"team_app_lock_timeout": 60
"team_app_lock_timeout": 60,
"max_remote_search_result_count": 30
}

0 comments on commit 8dc5948

Please sign in to comment.