feat(Search): 高亮搜索词

This commit is contained in:
HuanCheng65 2024-01-29 16:19:57 +08:00
parent 2cf07d8e62
commit 7087984b57
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
6 changed files with 153 additions and 9 deletions

View File

@ -35,7 +35,7 @@ data class SearchThreadBean(
data class ThreadInfoBean(
val tid: String,
val pid: String,
val cid: String,
val cid: String = "0",
val title: String,
val content: String,
val time: String,

View File

@ -326,6 +326,7 @@ fun ForumSearchPostPage(
)
},
hideForum = true,
searchKeyword = currentKeyword,
) {
stickyHeader(key = "Sort&Filter") {
Row(

View File

@ -153,6 +153,7 @@ fun SearchThreadPage(
)
)
},
searchKeyword = keyword,
)
PullRefreshIndicator(

View File

@ -271,6 +271,7 @@ fun ThreadContent(
showAbstract: Boolean = true,
isGood: Boolean = false,
maxLines: Int = 5,
highlightKeywords: ImmutableList<String> = persistentListOf(),
) {
val content = buildAnnotatedString {
if (showTitle) {
@ -298,7 +299,7 @@ fun ThreadContent(
}
}
EmoticonText(
HighlightText(
text = content,
modifier = Modifier
.fillMaxWidth()
@ -308,6 +309,7 @@ fun ThreadContent(
overflow = TextOverflow.Ellipsis,
maxLines = maxLines,
style = MaterialTheme.typography.body1,
highlightKeywords = highlightKeywords
)
}

View File

@ -71,6 +71,7 @@ fun QuotePostCard(
quotePostInfo: SearchThreadBean.PostInfo,
mainPost: SearchThreadBean.MainPost,
modifier: Modifier = Modifier,
keyword: String? = null,
) {
val quoteContentString = remember(quotePostInfo) {
buildAnnotatedStringWithUser(
@ -84,13 +85,15 @@ fun QuotePostCard(
modifier = modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
PbContentText(
HighlightText(
text = quoteContentString,
style = MaterialTheme.typography.body2,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
highlightKeywords = (keyword?.split(" ") ?: emptyList()).toImmutableList()
)
MainPostCard(
keyword = keyword,
mainPost = mainPost,
modifier = Modifier
.fillMaxWidth()
@ -104,6 +107,7 @@ fun QuotePostCard(
fun MainPostCard(
mainPost: SearchThreadBean.MainPost,
modifier: Modifier = Modifier,
keyword: String? = null,
) {
val titleString = remember(mainPost) {
buildAnnotatedStringWithUser(
@ -117,12 +121,13 @@ fun MainPostCard(
modifier = modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
PbContentText(
HighlightText(
text = titleString,
style = MaterialTheme.typography.subtitle2,
fontWeight = FontWeight.Bold,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
highlightKeywords = (keyword?.split(" ") ?: emptyList()).toImmutableList()
)
if (mainPost.content.isNotBlank()) {
PbContentText(
@ -146,6 +151,7 @@ fun SearchThreadList(
onQuotePostClick: (SearchThreadBean.PostInfo) -> Unit = {},
onMainPostClick: (SearchThreadBean.MainPost) -> Unit = {},
hideForum: Boolean = false,
searchKeyword: String? = null,
header: LazyListScope.() -> Unit = {},
) {
MyLazyColumn(
@ -162,9 +168,10 @@ fun SearchThreadList(
onClick = onItemClick,
onUserClick = onItemUserClick,
onForumClick = onItemForumClick,
hideForum = hideForum,
onQuotePostClick = onQuotePostClick,
onMainPostClick = onMainPostClick,
hideForum = hideForum,
searchKeyword = searchKeyword
)
}
}
@ -215,6 +222,7 @@ fun SearchThreadItem(
onQuotePostClick: (SearchThreadBean.PostInfo) -> Unit = {},
onMainPostClick: (SearchThreadBean.MainPost) -> Unit = {},
hideForum: Boolean = false,
searchKeyword: String? = null,
) {
Card(
modifier = modifier,
@ -232,8 +240,8 @@ fun SearchThreadItem(
showTitle = item.mainPost == null && item.title.isNotBlank(),
showAbstract = item.content.isNotBlank(),
maxLines = 2,
highlightKeywords = (searchKeyword?.split(" ") ?: emptyList()).toImmutableList(),
)
SearchMedia(medias = item.media.toImmutableList())
if (item.mainPost != null) {
if (item.postInfo != null) {
QuotePostCard(
@ -245,7 +253,8 @@ fun SearchThreadItem(
.background(ExtendedTheme.colors.floorCard)
.clickable {
onQuotePostClick(item.postInfo)
}
},
keyword = searchKeyword
)
} else {
MainPostCard(
@ -256,9 +265,12 @@ fun SearchThreadItem(
.background(ExtendedTheme.colors.floorCard)
.clickable {
onMainPostClick(item.mainPost)
}
},
keyword = searchKeyword
)
}
} else {
SearchMedia(medias = item.media.toImmutableList())
}
if (!hideForum && item.forumName.isNotEmpty()) {
ForumInfoChip(

View File

@ -32,11 +32,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
@ -54,10 +54,12 @@ import com.huanchengfly.tieba.post.pxToDp
import com.huanchengfly.tieba.post.pxToSp
import com.huanchengfly.tieba.post.pxToSpFloat
import com.huanchengfly.tieba.post.spToPxFloat
import com.huanchengfly.tieba.post.ui.common.PbContentText
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.utils.EmoticonManager
import com.huanchengfly.tieba.post.utils.EmoticonUtil.emoticonString
import com.huanchengfly.tieba.post.utils.calcLineHeightPx
import java.util.regex.Pattern
@Composable
fun EmoticonText(
@ -399,3 +401,129 @@ fun buildChipInlineContent(
}
)
}
@Composable
fun HighlightText(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
lineSpacing: TextUnit = 0.sp,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
emoticonSize: Float = 0.9f,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
highlightKeywords: List<String> = emptyList(),
highlightColor: Color = ExtendedTheme.colors.primary,
highlightStyle: TextStyle = style,
) {
HighlightText(
text = AnnotatedString(text),
modifier = modifier,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
lineSpacing = lineSpacing,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
emoticonSize = emoticonSize,
onTextLayout = onTextLayout,
style = style,
highlightKeywords = highlightKeywords,
highlightColor = highlightColor,
highlightStyle = highlightStyle,
)
}
@Composable
fun HighlightText(
text: AnnotatedString,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
lineSpacing: TextUnit = 0.sp,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
emoticonSize: Float = 0.9f,
inlineContent: Map<String, InlineTextContent> = emptyMap(),
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
highlightKeywords: List<String> = emptyList(),
highlightColor: Color = ExtendedTheme.colors.primary,
highlightStyle: TextStyle = style,
) {
val mergedHighlightStyle = remember(highlightStyle, highlightColor) {
highlightStyle.copy(color = highlightColor)
}
val highlightText = remember(text, highlightKeywords) {
if (highlightKeywords.isEmpty()) {
text
} else {
buildAnnotatedString {
append(text)
highlightKeywords.forEach { keyword ->
val regexPattern = keyword.toPattern(Pattern.CASE_INSENSITIVE)
val matcher = regexPattern.matcher(text.text)
while (matcher.find()) {
val start = matcher.start()
val end = matcher.end()
addStyle(
mergedHighlightStyle.toSpanStyle(),
start,
end
)
}
}
}
}
}
PbContentText(
text = highlightText,
modifier = modifier,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
lineSpacing = lineSpacing,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
emoticonSize = emoticonSize,
inlineContent = inlineContent,
onTextLayout = onTextLayout,
style = style,
)
}