Skip to content

Commit

Permalink
fix: mention highlight [WPB-3507] (#2166)
Browse files Browse the repository at this point in the history
Co-authored-by: Jakub Żerko <iot.zerko@gmail.com>
  • Loading branch information
github-actions[bot] and Garzas committed Sep 4, 2023
1 parent 4a8cabe commit cee9f7c
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fun MemberItemToMention(
name: String,
label: String,
membership: Membership,
searchQuery: String = "",
searchQuery: String,
clickable: Clickable,
modifier: Modifier = Modifier
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ package com.wire.android.ui.home.conversations.search
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
Expand All @@ -48,19 +49,22 @@ fun HighlightName(
searchQuery: String,
modifier: Modifier = Modifier
) {
val scope = rememberCoroutineScope()
var highlightIndexes by remember {
mutableStateOf(emptyList<MatchQueryResult>())
}

LaunchedEffect(searchQuery) {
launch {
val queryWithoutSuffix = searchQuery.removeQueryPrefix()

SideEffect {
scope.launch {
highlightIndexes = QueryMatchExtractor.extractQueryMatchIndexes(
matchText = searchQuery,
matchText = queryWithoutSuffix,
text = name
)
}
}
if (searchQuery != String.EMPTY && highlightIndexes.isNotEmpty()) {
if (queryWithoutSuffix != String.EMPTY && highlightIndexes.isNotEmpty()) {
Text(
buildAnnotatedString {
withStyle(
Expand All @@ -75,12 +79,14 @@ fun HighlightName(
}

highlightIndexes
.forEach { highLightIndexes ->
addStyle(
style = SpanStyle(background = MaterialTheme.wireColorScheme.highLight.copy(alpha = 0.5f)),
start = highLightIndexes.startIndex,
end = highLightIndexes.endIndex
)
.forEach { highLightIndex ->
if (highLightIndex.endIndex <= this.length) {
addStyle(
style = SpanStyle(background = MaterialTheme.wireColorScheme.highLight.copy(alpha = 0.5f)),
start = highLightIndex.startIndex,
end = highLightIndex.endIndex
)
}
}
},
maxLines = 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,34 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.EMPTY
import com.wire.android.util.MatchQueryResult
import com.wire.android.util.QueryMatchExtractor
import kotlinx.coroutines.launch


@Composable
fun HighlightSubtitle(
subTitle: String,
searchQuery: String = "",
suffix: String = "@"
) {
val scope = rememberCoroutineScope()

var highlightIndexes by remember {
mutableStateOf(emptyList<MatchQueryResult>())
}

val queryWithoutSuffix = searchQuery.removeQueryPrefix()

SideEffect {
scope.launch {
highlightIndexes = QueryMatchExtractor.extractQueryMatchIndexes(
matchText = searchQuery.validateSearchQuery(),
matchText = queryWithoutSuffix,
text = subTitle
)
}
}
if (highlightIndexes.isNotEmpty()) {

if (queryWithoutSuffix != String.EMPTY && highlightIndexes.isNotEmpty()) {
Text(
buildAnnotatedString {
withStyle(
Expand All @@ -76,14 +78,16 @@ fun HighlightSubtitle(
}

highlightIndexes
.forEach { highLightIndexes ->
addStyle(
style = SpanStyle(
background = MaterialTheme.wireColorScheme.highLight.copy(alpha = 0.5f),
),
start = highLightIndexes.startIndex + suffix.length,
end = highLightIndexes.endIndex + suffix.length
)
.forEach { highLightIndex ->
if (highLightIndex.endIndex <= this.length) {
addStyle(
style = SpanStyle(
background = MaterialTheme.wireColorScheme.highLight.copy(alpha = 0.5f),
),
start = highLightIndex.startIndex + suffix.length,
end = highLightIndex.endIndex + suffix.length
)
}
}
},
maxLines = 1,
Expand All @@ -99,11 +103,3 @@ fun HighlightSubtitle(
)
}
}

private fun String.validateSearchQuery(): String {
if (startsWith("@")) {
return removePrefix("@")
} else {
return this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.conversations.search

fun String.removeQueryPrefix(): String {
return if (startsWith("@")) {
removePrefix("@")
} else {
this
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import com.wire.android.ui.home.newconversation.model.Contact

@Composable
fun DropDownMentionsSuggestions(
searchQuery: String,
currentSelectedLineIndex: Int,
cursorCoordinateY: Float,
membersToMention: List<Contact>,
Expand Down Expand Up @@ -91,6 +92,7 @@ fun DropDownMentionsSuggestions(
clickable = Clickable(enabled = true) {
onMentionPicked(item)
},
searchQuery = searchQuery,
modifier = Modifier.onSizeChanged {
itemHeights[index] = it.height
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ private fun ActiveMessageComposer(
if (messageComposerViewState.value.mentionSearchResult.isNotEmpty()) {
MembersMentionList(
membersToMention = messageComposerViewState.value.mentionSearchResult,
searchQuery = messageComposition.value.messageText,
onMentionPicked = { pickedMention ->
messageCompositionHolder.addMention(pickedMention)
onClearMentionSearchResult()
Expand Down Expand Up @@ -461,6 +462,7 @@ private fun ActiveMessageComposer(
currentSelectedLineIndex = currentSelectedLineIndex,
cursorCoordinateY = cursorCoordinateY,
membersToMention = mentionSearchResult,
searchQuery = messageComposition.value.messageText,
onMentionPicked = {
messageCompositionHolder.addMention(it)
onClearMentionSearchResult()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ data class UiMention(
@Composable
fun MembersMentionList(
membersToMention: List<Contact>,
searchQuery: String,
onMentionPicked: (Contact) -> Unit
) {
Column(
Expand All @@ -68,6 +69,7 @@ fun MembersMentionList(
label = it.label,
membership = it.membership,
clickable = Clickable { onMentionPicked(it) },
searchQuery = searchQuery,
modifier = Modifier
)
Divider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ object QueryMatchExtractor {
text: String
): List<MatchQueryResult> =
withContext(Dispatchers.Default) {
if (matchText.isEmpty()) {
return@withContext listOf()
}
val index = text.indexOf(matchText, startIndex = startIndex, ignoreCase = true)

if (isIndexFound(index)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*
*/

package com.wire.android.util

import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class QueryMatchExtractorTest {

@Test
fun `given empty text, when extracting indexes, then return empty list`() = runTest {
val result = QueryMatchExtractor.extractQueryMatchIndexes(matchText = "test", text = "")
assertEquals(emptyList<MatchQueryResult>(), result)
}

@Test
fun `given empty matchText, when extracting indexes, then return empty list`() = runTest {
val result = QueryMatchExtractor.extractQueryMatchIndexes(matchText = "", text = "This is a sample text")
assertEquals(emptyList<MatchQueryResult>(), result)
}

@Test
fun `given one occurrence of matchText, when extracting indexes, then return list with one match`() = runTest {
val ten = 10
val fourteen = 14

val result = QueryMatchExtractor.extractQueryMatchIndexes(matchText = "test", text = "This is a test")
assertEquals(listOf(MatchQueryResult(ten, fourteen)), result)
}

@Test
fun `given multiple occurrences of matchText, when extracting indexes, then return list with all matches`() = runTest {
val zero = 0
val four = 4
val eight = 8
val twelve = 12

val result = QueryMatchExtractor.extractQueryMatchIndexes(matchText = "test", text = "test or test")
assertEquals(listOf(MatchQueryResult(zero, four), MatchQueryResult(eight, twelve)), result)
}

@Test
fun `given overlapping occurrences of matchText, when extracting indexes, then return list with non-overlapping matches`() =
runTest {
val zero = 0
val two = 2
val four = 4

val result = QueryMatchExtractor.extractQueryMatchIndexes(matchText = "ab", text = "abab")
assertEquals(listOf(MatchQueryResult(zero, two), MatchQueryResult(two, four)), result)
}

@Test
fun `given matchText is case insensitive, when extracting indexes, then return correct matches`() = runTest {
val ten = 10
val fourteen = 14
val fifteen = 15
val nineteen = 19

val result = QueryMatchExtractor.extractQueryMatchIndexes(matchText = "TeSt", text = "This is a test TEST")
assertEquals(listOf(MatchQueryResult(ten, fourteen), MatchQueryResult(fifteen, nineteen)), result)
}
}

0 comments on commit cee9f7c

Please sign in to comment.