Skip to content

Commit f71bcae

Browse files
committed
Move DefaultUserQueryFilter to the query.filter package and refactor it to replace the generic DefaultQueryFilter. Update all references and tests to use the new DefaultUserQueryFilter implementation.
1 parent 726b8de commit f71bcae

10 files changed

Lines changed: 123 additions & 160 deletions

File tree

stream-chat-android-docs/src/main/java/io/getstream/chat/docs/java/ui/messages/MessageComposer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
import io.getstream.chat.android.models.querysort.QuerySorter;
3030
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.CompatUserLookupHandler;
3131
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.DefaultUserLookupHandler;
32-
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.DefaultUserQueryFilter;
3332
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.UserLookupHandler;
33+
import io.getstream.chat.android.ui.common.feature.messages.composer.query.filter.DefaultUserQueryFilter;
3434
import io.getstream.chat.android.ui.common.feature.messages.composer.transliteration.DefaultStreamTransliterator;
3535
import io.getstream.chat.android.ui.common.feature.messages.composer.transliteration.StreamTransliterator;
3636
import io.getstream.chat.android.ui.common.state.messages.Edit;

stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/ui/messages/MessageComposer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import io.getstream.chat.android.models.User
1919
import io.getstream.chat.android.models.querysort.QuerySortByField.Companion.descByName
2020
import io.getstream.chat.android.models.querysort.QuerySorter
2121
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.DefaultUserLookupHandler
22-
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.DefaultUserQueryFilter
2322
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.UserLookupHandler
23+
import io.getstream.chat.android.ui.common.feature.messages.composer.query.filter.DefaultUserQueryFilter
2424
import io.getstream.chat.android.ui.common.feature.messages.composer.transliteration.DefaultStreamTransliterator
2525
import io.getstream.chat.android.ui.common.state.messages.Edit
2626
import io.getstream.chat.android.ui.common.state.messages.MessageMode

stream-chat-android-ui-common/api/stream-chat-android-ui-common.api

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -753,14 +753,6 @@ public final class io/getstream/chat/android/ui/common/feature/messages/composer
753753
public fun handleUserLookup (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
754754
}
755755

756-
public final class io/getstream/chat/android/ui/common/feature/messages/composer/mention/DefaultUserQueryFilter : io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/QueryFilter {
757-
public static final field $stable I
758-
public fun <init> ()V
759-
public fun <init> (Lio/getstream/chat/android/ui/common/feature/messages/composer/transliteration/StreamTransliterator;)V
760-
public synthetic fun <init> (Lio/getstream/chat/android/ui/common/feature/messages/composer/transliteration/StreamTransliterator;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
761-
public fun filter (Ljava/util/List;Ljava/lang/String;)Ljava/util/List;
762-
}
763-
764756
public final class io/getstream/chat/android/ui/common/feature/messages/composer/mention/LocalUserLookupHandler : io/getstream/chat/android/ui/common/feature/messages/composer/mention/UserLookupHandler {
765757
public static final field $stable I
766758
public fun <init> (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;)V
@@ -819,6 +811,14 @@ public final class io/getstream/chat/android/ui/common/feature/messages/composer
819811
public static final fun withQueryFormatter (Lio/getstream/chat/android/ui/common/feature/messages/composer/mention/UserLookupHandler;Lio/getstream/chat/android/ui/common/feature/messages/composer/query/formatter/QueryFormatter;)Lio/getstream/chat/android/ui/common/feature/messages/composer/mention/UserLookupHandler;
820812
}
821813

814+
public final class io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/DefaultUserQueryFilter : io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/QueryFilter {
815+
public static final field $stable I
816+
public fun <init> ()V
817+
public fun <init> (Lio/getstream/chat/android/ui/common/feature/messages/composer/transliteration/StreamTransliterator;)V
818+
public synthetic fun <init> (Lio/getstream/chat/android/ui/common/feature/messages/composer/transliteration/StreamTransliterator;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
819+
public fun filter (Ljava/util/List;Ljava/lang/String;)Ljava/util/List;
820+
}
821+
822822
public abstract interface class io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/QueryFilter {
823823
public abstract fun filter (Ljava/util/List;Ljava/lang/String;)Ljava/util/List;
824824
}

stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/mention/DefaultUserLookupHandler.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package io.getstream.chat.android.ui.common.feature.messages.composer.mention
1818

1919
import io.getstream.chat.android.client.ChatClient
2020
import io.getstream.chat.android.models.User
21-
import io.getstream.chat.android.ui.common.feature.messages.composer.query.filter.DefaultQueryFilter
21+
import io.getstream.chat.android.ui.common.feature.messages.composer.query.filter.DefaultUserQueryFilter
2222
import io.getstream.chat.android.ui.common.feature.messages.composer.query.filter.QueryFilter
2323
import io.getstream.log.taggedLogger
2424

@@ -44,7 +44,7 @@ public class DefaultUserLookupHandler(
4444
public constructor(
4545
chatClient: ChatClient,
4646
channelCid: String,
47-
localFilter: QueryFilter<User> = DefaultQueryFilter { it.name.ifBlank { it.id } },
47+
localFilter: QueryFilter<User> = DefaultUserQueryFilter(),
4848
) : this(
4949
localHandler = LocalUserLookupHandler(chatClient, channelCid, localFilter),
5050
remoteHandler = RemoteUserLookupHandler(chatClient, channelCid),

stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/mention/DefaultUserQueryFilter.kt

Lines changed: 0 additions & 44 deletions
This file was deleted.

stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/mention/LocalUserLookupHandler.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import io.getstream.chat.android.client.ChatClient
2020
import io.getstream.chat.android.client.api.state.state
2121
import io.getstream.chat.android.client.extensions.cidToTypeAndId
2222
import io.getstream.chat.android.models.User
23-
import io.getstream.chat.android.ui.common.feature.messages.composer.query.filter.DefaultQueryFilter
23+
import io.getstream.chat.android.ui.common.feature.messages.composer.query.filter.DefaultUserQueryFilter
2424
import io.getstream.chat.android.ui.common.feature.messages.composer.query.filter.QueryFilter
2525
import io.getstream.log.taggedLogger
2626

@@ -34,7 +34,7 @@ import io.getstream.log.taggedLogger
3434
public class LocalUserLookupHandler @JvmOverloads constructor(
3535
private val chatClient: ChatClient,
3636
private val channelCid: String,
37-
private val filter: QueryFilter<User> = DefaultQueryFilter { it.name.ifBlank { it.id } },
37+
private val filter: QueryFilter<User> = DefaultUserQueryFilter(),
3838
) : UserLookupHandler {
3939

4040
private val logger by taggedLogger("Chat:UserLookupLocal")

stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/DefaultQueryFilter.kt renamed to stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/DefaultUserQueryFilter.kt

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,54 +16,52 @@
1616

1717
package io.getstream.chat.android.ui.common.feature.messages.composer.query.filter
1818

19+
import io.getstream.chat.android.models.User
1920
import io.getstream.chat.android.ui.common.feature.messages.composer.query.formatter.Combine
2021
import io.getstream.chat.android.ui.common.feature.messages.composer.query.formatter.IgnoreDiacritics
2122
import io.getstream.chat.android.ui.common.feature.messages.composer.query.formatter.Lowercase
22-
import io.getstream.chat.android.ui.common.feature.messages.composer.query.formatter.QueryFormatter
2323
import io.getstream.chat.android.ui.common.feature.messages.composer.query.formatter.Transliterate
2424
import io.getstream.chat.android.ui.common.feature.messages.composer.transliteration.DefaultStreamTransliterator
2525
import io.getstream.chat.android.ui.common.feature.messages.composer.transliteration.StreamTransliterator
2626
import io.getstream.log.taggedLogger
2727
import kotlin.math.min
2828

2929
/**
30-
* Default implementation of [QueryFilter].
30+
* Default [QueryFilter] for [User] objects used in mention suggestions.
3131
*
32-
* Keeps only items whose normalized target contains the normalized query as a substring, then
33-
* sorts results by Levenshtein distance so the closest matches appear first. Normalization
32+
* Keeps only users whose normalized name (or id) contains the normalized query as a substring,
33+
* then sorts results by Levenshtein distance so the closest matches appear first. Normalization
3434
* applies lowercasing, diacritics removal, and optional transliteration.
3535
*
3636
* @param transliterator The transliterator to use for normalizing strings.
37-
* @param target The function to extract the searchable string from an item.
3837
*/
39-
internal class DefaultQueryFilter<T>(
40-
private val transliterator: StreamTransliterator = DefaultStreamTransliterator(),
41-
private val target: (T) -> String,
42-
) : QueryFilter<T> {
38+
public class DefaultUserQueryFilter(
39+
transliterator: StreamTransliterator = DefaultStreamTransliterator(),
40+
) : QueryFilter<User> {
4341

4442
private val logger by taggedLogger("Chat:QueryFilter")
4543

46-
private val queryFormatter: QueryFormatter = Combine(
44+
private val queryFormatter = Combine(
4745
Lowercase(),
4846
IgnoreDiacritics(),
4947
Transliterate(transliterator),
5048
)
5149

52-
override fun filter(items: List<T>, query: String): List<T> {
50+
override fun filter(items: List<User>, query: String): List<User> {
5351
logger.d { "[filter] query: \"$query\", items.size: ${items.size}" }
5452
val formattedQuery = queryFormatter.format(query)
5553
if (formattedQuery.isEmpty()) return items
5654
return items
57-
.mapNotNull { item ->
58-
val formattedTarget = queryFormatter.format(target(item))
59-
if (formattedTarget.contains(formattedQuery)) {
60-
item to levenshteinDistance(formattedQuery, formattedTarget)
55+
.mapNotNull { user ->
56+
val formattedName = queryFormatter.format(query = user.name.ifBlank(user::id))
57+
if (formattedName.contains(formattedQuery)) {
58+
user to levenshteinDistance(formattedQuery, formattedName)
6159
} else {
6260
null
6361
}
6462
}
6563
.sortedBy { (_, distance) -> distance }
66-
.map { (item, _) -> item }
64+
.map { (user, _) -> user }
6765
}
6866

6967
private fun levenshteinDistance(search: String, target: String): Int {

stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/DefaultQueryFilterTest.kt

Lines changed: 0 additions & 85 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.android.ui.common.feature.messages.composer.query.filter
18+
19+
import io.getstream.chat.android.randomUser
20+
import org.junit.jupiter.api.Assertions.assertEquals
21+
import org.junit.jupiter.api.Test
22+
23+
internal class DefaultUserQueryFilterTest {
24+
25+
private val filter = DefaultUserQueryFilter()
26+
27+
@Test
28+
fun `empty query returns all users`() {
29+
val users = listOf(user("Alice"), user("Bob"))
30+
31+
assertEquals(listOf("Alice", "Bob"), filter.filter(users, "").names())
32+
}
33+
34+
@Test
35+
fun `no match returns empty list`() {
36+
val users = listOf(user("Alice"), user("Bob"))
37+
38+
assertEquals(emptyList<String>(), filter.filter(users, "xyz").names())
39+
}
40+
41+
@Test
42+
fun `match is case insensitive`() {
43+
val users = listOf(user("Aleksandar Apostolov"), user("Jc Minarro"))
44+
45+
assertEquals(listOf("Jc Minarro"), filter.filter(users, "JC").names())
46+
}
47+
48+
@Test
49+
fun `match ignores diacritics`() {
50+
val users = listOf(user("José"), user("Bob"))
51+
52+
assertEquals(listOf("José"), filter.filter(users, "jose").names())
53+
}
54+
55+
@Test
56+
fun `short query only matches users containing that substring`() {
57+
val users = listOf(user("Aleksandar Apostolov"), user("Jc Minarro"))
58+
59+
assertEquals(listOf("Jc Minarro"), filter.filter(users, "jc").names())
60+
}
61+
62+
@Test
63+
fun `query does not fuzzy match unrelated names`() {
64+
val users = listOf(user("Aleksandar Apostolov"), user("Ara"), user("Abel"))
65+
66+
assertEquals(listOf("Aleksandar Apostolov"), filter.filter(users, "ale").names())
67+
}
68+
69+
@Test
70+
fun `query matches substring in any word`() {
71+
val users = listOf(user("Alice Smith"), user("Bob Jones"), user("Charlie Smith"))
72+
73+
assertEquals(listOf("Alice Smith", "Charlie Smith"), filter.filter(users, "smith").names())
74+
}
75+
76+
@Test
77+
fun `results are sorted by levenshtein distance`() {
78+
val users = listOf(user("Charlie Alice"), user("Alice"), user("Bob Alice Smith"))
79+
80+
assertEquals(listOf("Alice", "Charlie Alice", "Bob Alice Smith"), filter.filter(users, "alice").names())
81+
}
82+
83+
@Test
84+
fun `falls back to id when name is blank`() {
85+
val users = listOf(randomUser(name = "", id = "alice123"), user("Bob"))
86+
87+
assertEquals(listOf("alice123", "Bob"), filter.filter(users, "").map { it.name.ifBlank { it.id } })
88+
assertEquals(listOf("alice123"), filter.filter(users, "alice").map { it.id })
89+
}
90+
91+
private fun user(name: String) = randomUser(name = name)
92+
93+
private fun List<io.getstream.chat.android.models.User>.names() = map { it.name }
94+
}

stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ import io.getstream.chat.android.client.api.state.EventObserver
3333
import io.getstream.chat.android.models.Flag
3434
import io.getstream.chat.android.models.Message
3535
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.DefaultUserLookupHandler
36-
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.DefaultUserQueryFilter
3736
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.RemoteUserLookupHandler
37+
import io.getstream.chat.android.ui.common.feature.messages.composer.query.filter.DefaultUserQueryFilter
3838
import io.getstream.chat.android.ui.common.feature.messages.composer.transliteration.DefaultStreamTransliterator
3939
import io.getstream.chat.android.ui.common.state.messages.Edit
4040
import io.getstream.chat.android.ui.common.state.messages.MessageMode

0 commit comments

Comments
 (0)