@@ -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