From 7087984b579c739a640209b43412bc6b492e67b2 Mon Sep 17 00:00:00 2001 From: HuanCheng65 <22636177+HuanCheng65@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:19:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(Search):=20=E9=AB=98=E4=BA=AE=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tieba/post/api/models/SearchThreadBean.kt | 2 +- .../forum/searchpost/ForumSearchPostPage.kt | 1 + .../ui/page/search/thread/SearchThreadPage.kt | 1 + .../tieba/post/ui/widgets/compose/FeedCard.kt | 4 +- .../tieba/post/ui/widgets/compose/Search.kt | 24 +++- .../tieba/post/ui/widgets/compose/Texts.kt | 130 +++++++++++++++++- 6 files changed, 153 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/api/models/SearchThreadBean.kt b/app/src/main/java/com/huanchengfly/tieba/post/api/models/SearchThreadBean.kt index 28a35e64..b7cf3f12 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/api/models/SearchThreadBean.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/api/models/SearchThreadBean.kt @@ -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, diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/searchpost/ForumSearchPostPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/searchpost/ForumSearchPostPage.kt index 072d0c52..31403da8 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/searchpost/ForumSearchPostPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/searchpost/ForumSearchPostPage.kt @@ -326,6 +326,7 @@ fun ForumSearchPostPage( ) }, hideForum = true, + searchKeyword = currentKeyword, ) { stickyHeader(key = "Sort&Filter") { Row( diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/thread/SearchThreadPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/thread/SearchThreadPage.kt index a35e648e..d9d1dbf3 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/thread/SearchThreadPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/thread/SearchThreadPage.kt @@ -153,6 +153,7 @@ fun SearchThreadPage( ) ) }, + searchKeyword = keyword, ) PullRefreshIndicator( diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/FeedCard.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/FeedCard.kt index 0ed89835..e3161351 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/FeedCard.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/FeedCard.kt @@ -271,6 +271,7 @@ fun ThreadContent( showAbstract: Boolean = true, isGood: Boolean = false, maxLines: Int = 5, + highlightKeywords: ImmutableList = 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 ) } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Search.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Search.kt index c7423c00..58fc86ba 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Search.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Search.kt @@ -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( diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Texts.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Texts.kt index 5eac5fe8..cf0f5480 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Texts.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Texts.kt @@ -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( @@ -398,4 +400,130 @@ 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 = 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 = emptyMap(), + onTextLayout: (TextLayoutResult) -> Unit = {}, + style: TextStyle = LocalTextStyle.current, + highlightKeywords: List = 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, + ) } \ No newline at end of file