Skip to content

Commit f0c9a14

Browse files
authored
Merge pull request #6256 from nextcloud/issue-6170-furtherImproveSearchResultsOrder
Issue 6170 further improve search results order
2 parents 73ecbd2 + 83e7c86 commit f0c9a14

1 file changed

Lines changed: 60 additions & 10 deletions

File tree

app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -345,14 +345,27 @@ class ConversationsListViewModel @Inject constructor(
345345
getMessagesFlow(filter)
346346
) { localConvs, openConvs, contacts, (messages, hasMore) ->
347347
val entries = mutableListOf<ConversationListEntry>()
348+
val wordPattern = """\b${Regex.escape(filter)}\b""".toRegex(RegexOption.IGNORE_CASE)
348349

349350
if (localConvs.isNotEmpty()) {
350351
entries.add(ConversationListEntry.Header(conversationsTitle))
351-
localConvs.forEach { entries.add(ConversationListEntry.ConversationEntry(it)) }
352+
sortedByMatchQuality(
353+
localConvs,
354+
{ it.name },
355+
filter,
356+
wordPattern,
357+
isSingleUser = { it.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL }
358+
).forEach { entries.add(ConversationListEntry.ConversationEntry(it)) }
352359
}
353360
if (openConvs.isNotEmpty()) {
354361
entries.add(ConversationListEntry.Header(openConversationsTitle))
355-
openConvs.forEach { conv ->
362+
sortedByMatchQuality(
363+
openConvs,
364+
{ it.name },
365+
filter,
366+
wordPattern,
367+
isSingleUser = { it.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL }
368+
).forEach { conv ->
356369
entries.add(
357370
ConversationListEntry.ConversationEntry(
358371
ConversationModel.mapToConversationModel(conv, currentUser)
@@ -362,17 +375,21 @@ class ConversationsListViewModel @Inject constructor(
362375
}
363376
if (contacts.isNotEmpty()) {
364377
entries.add(ConversationListEntry.Header(usersTitle))
365-
contacts.forEach { autocompleteUser ->
366-
val participant = Participant()
367-
participant.actorId = autocompleteUser.id
368-
participant.actorType = actorTypeConverter.getFromString(autocompleteUser.source)
369-
participant.displayName = autocompleteUser.label
370-
entries.add(ConversationListEntry.ContactEntry(participant))
371-
}
378+
sortedByMatchQuality(contacts, { it.label }, filter, wordPattern)
379+
.forEach { autocompleteUser ->
380+
val participant = Participant()
381+
participant.actorId = autocompleteUser.id
382+
participant.actorType = actorTypeConverter.getFromString(autocompleteUser.source)
383+
participant.displayName = autocompleteUser.label
384+
entries.add(ConversationListEntry.ContactEntry(participant))
385+
}
372386
}
373387
if (messages.isNotEmpty()) {
374388
entries.add(ConversationListEntry.Header(messagesTitle))
375-
messages.forEach { msg -> entries.add(ConversationListEntry.MessageResultEntry(msg)) }
389+
sortedByMatchQuality(messages, {
390+
it.messageExcerpt
391+
}, filter, wordPattern, exactMatchEnabled = false)
392+
.forEach { msg -> entries.add(ConversationListEntry.MessageResultEntry(msg)) }
376393
}
377394
if (hasMore) entries.add(ConversationListEntry.LoadMore)
378395

@@ -384,6 +401,39 @@ class ConversationsListViewModel @Inject constructor(
384401
}
385402
}
386403

404+
// Sort keys in order:
405+
// 1. tier: 0 = exact title, 1 = starts with filter, 2 = whole-word match, 3 = substring
406+
// 2. isSingleUser: 0 = 1-on-1 conversation, 1 = group/other (optional)
407+
// 3. position of the match in the text (earlier = better, tiebreaker within tier)
408+
// exactMatchEnabled = false for messages, where an exact excerpt match is not a meaningful tier.
409+
private fun <T> sortedByMatchQuality(
410+
list: List<T>,
411+
getText: (T) -> String?,
412+
filter: String,
413+
wordPattern: Regex,
414+
exactMatchEnabled: Boolean = true,
415+
isSingleUser: ((T) -> Boolean)? = null
416+
): List<T> =
417+
list.sortedWith(
418+
compareBy(
419+
{ item ->
420+
val text = getText(item)
421+
when {
422+
text == null -> 3
423+
exactMatchEnabled && text.trim().equals(filter, ignoreCase = true) -> 0
424+
text.startsWith(filter, ignoreCase = true) -> 1
425+
text.contains(wordPattern) -> 2
426+
else -> 3
427+
}
428+
},
429+
{ item -> if (isSingleUser?.invoke(item) == true) 0 else 1 },
430+
{ item ->
431+
val pos = getText(item)?.indexOf(filter, ignoreCase = true) ?: -1
432+
if (pos >= 0) pos else Int.MAX_VALUE
433+
}
434+
)
435+
)
436+
387437
private fun getMessagesFlow(search: String): Flow<MessageSearchResults> =
388438
flow {
389439
emit(searchHelper.startMessageSearch(search))

0 commit comments

Comments
 (0)