diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt index 010aff5d4b..8f29693e9a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt @@ -115,9 +115,10 @@ class SuggestionsProcessor( .filterUpTo(MAX_BATCH_ITEMS) { member -> isJoinedMemberAndNotSelf(member) && memberMatchesQuery(member, query) } + .sortedBy { member -> matchRank(member, query) } .map(ResolvedSuggestion::Member) - if ("room".contains(query) && canSendRoomMention) { + if ("room".startsWith(query, ignoreCase = true) && canSendRoomMention) { listOf(ResolvedSuggestion.AtRoom) + matchingMembers } else { matchingMembers @@ -125,6 +126,18 @@ class SuggestionsProcessor( } } + /** + * Members whose display name starts with the query come first, then members whose user id + * localpart starts with it, then the remaining matches on any other part of the name or id. + */ + private fun matchRank(member: RoomMember, query: String): Int { + return when { + member.displayName?.startsWith(query, ignoreCase = true) == true -> 0 + member.userId.extractedDisplayName.startsWith(query, ignoreCase = true) -> 1 + else -> 2 + } + } + companion object { // We don't want to retrieve thousands of members private const val MAX_BATCH_ITEMS = 100 diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt index 6283d7236a..71a9ada1a8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt @@ -383,6 +383,48 @@ class SuggestionsProcessorTest { ) } + @Test + fun `processing Mention suggestion returns the members matching the start of the query first`() = runTest { + val matchingOnAnyPart = aRoomMember(userId = UserId("@carol:server.org"), displayName = "Not Alice") + val matchingOnUserIdStart = aRoomMember(userId = UserId("@alicia:server.org"), displayName = null) + val matchingOnDisplayNameStart = aRoomMember(userId = UserId("@zoe:server.org"), displayName = "Alice") + val result = suggestionsProcessor.process( + suggestion = aMentionSuggestion("ali"), + roomMembersState = RoomMembersState.Ready( + persistentListOf(matchingOnAnyPart, matchingOnUserIdStart, matchingOnDisplayNameStart) + ), + roomAliasSuggestions = emptyList(), + currentUserId = A_USER_ID_2, + canSendRoomMention = { false }, + isInThread = false, + ) + assertThat(result).isEqualTo( + listOf( + ResolvedSuggestion.Member(matchingOnDisplayNameStart), + ResolvedSuggestion.Member(matchingOnUserIdStart), + ResolvedSuggestion.Member(matchingOnAnyPart), + ) + ) + } + + @Test + fun `processing Mention suggestion returns room ignoring the case of the query`() = runTest { + val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = "alice") + val result = suggestionsProcessor.process( + suggestion = aMentionSuggestion("RO"), + roomMembersState = RoomMembersState.Ready(persistentListOf(aRoomMember)), + roomAliasSuggestions = emptyList(), + currentUserId = A_USER_ID_2, + canSendRoomMention = { true }, + isInThread = false, + ) + assertThat(result).isEqualTo( + listOf( + ResolvedSuggestion.AtRoom, + ) + ) + } + @Test fun `processing Mention suggestion with return matching display name but not room if not allowed`() = runTest { val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = "ro")