From fad05912ae44b7a3ed0073686b1039674212151f Mon Sep 17 00:00:00 2001 From: HuanCheng65 <22636177+HuanCheng65@users.noreply.github.com> Date: Fri, 21 Jul 2023 18:57:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A5=BC=E5=B1=82=E9=95=BF=E6=8C=89?= =?UTF-8?q?=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/api/models/protos/Extensions.kt | 13 +- .../tieba/post/ui/common/PbContentRender.kt | 16 ++ .../ui/page/history/list/HistoryListPage.kt | 2 +- .../tieba/post/ui/page/main/home/HomePage.kt | 2 +- .../post/ui/page/subposts/SubPostsPage.kt | 2 +- .../tieba/post/ui/page/thread/ThreadPage.kt | 267 +++++++++++------- .../tieba/post/ui/widgets/compose/Menu.kt | 12 +- .../tieba/post/ui/widgets/compose/Toolbar.kt | 2 +- app/src/main/res/values/strings.xml | 1 + 9 files changed, 202 insertions(+), 115 deletions(-) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/api/models/protos/Extensions.kt b/app/src/main/java/com/huanchengfly/tieba/post/api/models/protos/Extensions.kt index c9b0ffc9..04052ed5 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/api/models/protos/Extensions.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/api/models/protos/Extensions.kt @@ -159,16 +159,9 @@ private val PbContent.picUrl: String cdnSrcActive, src ) + val List.plainText: String - get() = joinToString(separator = "") { - when (it.type) { - 0, 1, 4, 9, 27 -> it.text - 2 -> "#(${it.c})" - 3, 20 -> "[图片]" - 5 -> "[视频]" - else -> "" - } - } + get() = renders.joinToString("\n") { toString() } @OptIn(ExperimentalTextApi::class) val List.renders: ImmutableList @@ -307,6 +300,7 @@ val List.renders: ImmutableList return renders.toImmutableList() } + val Post.contentRenders: ImmutableList get() { val renders = content.renders @@ -325,6 +319,7 @@ val Post.contentRenders: ImmutableList } else it }.toImmutableList() } + val User.bawuType: String? get() = if (is_bawu == 1) { if (bawu_type == "manager") "吧主" else "小吧主" diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/common/PbContentRender.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/common/PbContentRender.kt index 0e81adfd..18f082dc 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/common/PbContentRender.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/common/PbContentRender.kt @@ -64,6 +64,10 @@ data class TextContentRender( ) : PbContentRender { constructor(text: String) : this(AnnotatedString(text)) + override fun toString(): String { + return text.toString() + } + @Composable override fun Render() { PbContentText(text = text, fontSize = 15.sp, style = MaterialTheme.typography.body1) @@ -134,6 +138,10 @@ data class PicContentRender( contentScale = ContentScale.Crop ) } + + override fun toString(): String { + return "[图片]" + } } @Stable @@ -148,6 +156,10 @@ data class VoiceContentRender( } VoicePlayer(url = voiceUrl, duration = duration) } + + override fun toString(): String { + return "[视频]" + } } @Stable @@ -189,6 +201,10 @@ data class VideoContentRender( } } } + + override fun toString(): String { + return "[语音]" + } } @Composable diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt index eed96774..79b115f9 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt @@ -217,7 +217,6 @@ private fun HistoryItem( ) { val menuState = rememberMenuState() LongClickMenu( - menuState = menuState, menuContent = { DropdownMenuItem(onClick = { onDelete(info) @@ -226,6 +225,7 @@ private fun HistoryItem( Text(text = stringResource(id = R.string.title_delete)) } }, + menuState = menuState, onClick = { onClick(info) } ) { Column( diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/home/HomePage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/home/HomePage.kt index 8d658b0d..8b1e2cf3 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/home/HomePage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/home/HomePage.kt @@ -268,7 +268,6 @@ private fun ForumItem( } } LongClickMenu( - menuState = menuState, menuContent = { if (isTopForum) { DropdownMenuItem( @@ -306,6 +305,7 @@ private fun ForumItem( Text(text = stringResource(id = R.string.button_unfollow)) } }, + menuState = menuState, onClick = { navigator.navigate(ForumPageDestination(item.forumName)) } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsPage.kt index 25a16160..c93a058e 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsPage.kt @@ -298,7 +298,7 @@ internal fun SubPostsContent( ) ) }, - onClickContent = { + onReplyClick = { navigator.navigate( ReplyPageDestination( forumId = forumId, diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt index 25062956..5af46082 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt @@ -28,8 +28,8 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.appendInlineContent -import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.ButtonDefaults +import androidx.compose.material.DropdownMenuItem import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme @@ -96,6 +96,7 @@ import com.huanchengfly.tieba.post.api.models.protos.SimpleForum import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo import com.huanchengfly.tieba.post.api.models.protos.User import com.huanchengfly.tieba.post.api.models.protos.bawuType +import com.huanchengfly.tieba.post.api.models.protos.plainText import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage import com.huanchengfly.tieba.post.arch.ImmutableHolder import com.huanchengfly.tieba.post.arch.collectPartialAsState @@ -127,6 +128,7 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.HorizontalDivider import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad import com.huanchengfly.tieba.post.ui.widgets.compose.ListMenuItem import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout +import com.huanchengfly.tieba.post.ui.widgets.compose.LongClickMenu import com.huanchengfly.tieba.post.ui.widgets.compose.MyBackHandler import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold import com.huanchengfly.tieba.post.ui.widgets.compose.PromptDialog @@ -1041,7 +1043,9 @@ fun ThreadPage( subPostContents = subPostContents[index], threadAuthorId = author?.get { id } ?: 0L, blocked = blocked, + canDelete = { it.author_id == user.get { id } }, immersiveMode = isImmersiveMode, + isCollected = { it.id == thread?.get { collectMarkPid.toLongOrNull() } }, onAgree = { val postHasAgreed = item.get { agree?.hasAgree == 1 } @@ -1053,7 +1057,7 @@ fun ThreadPage( ) ) }, - onClickContent = { + onReplyClick = { navigator.navigate( ReplyPageDestination( forumId = curForumId ?: 0, @@ -1080,6 +1084,37 @@ fun ThreadPage( ) } }, + onMenuCopyClick = { + TiebaUtil.copyText(context, it.content.plainText) + }, + onMenuReportClick = { + TiebaUtil.reportPost(context, it.id.toString()) + }, + onMenuFavoriteClick = { + val isPostCollected = + it.id == thread?.get { collectMarkPid.toLongOrNull() } + val fid = forum?.get { id } ?: forumId + val tbs = anti?.get { tbs } + if (fid != null) { + if (isPostCollected) { + viewModel.send( + ThreadUiIntent.RemoveFavorite( + threadId = threadId, + forumId = fid, + tbs = tbs + ) + ) + } else { + viewModel.send( + ThreadUiIntent.AddFavorite( + threadId = threadId, + postId = it.id, + floor = it.floor + ) + ) + } + } + } ) } if (data.isEmpty()) { @@ -1298,11 +1333,17 @@ fun PostCard( subPostContents: ImmutableList = persistentListOf(), threadAuthorId: Long = 0L, blocked: Boolean = false, + canDelete: (Post) -> Boolean = { false }, immersiveMode: Boolean = false, + isCollected: (Post) -> Boolean = { false }, showSubPosts: Boolean = true, onAgree: () -> Unit = {}, - onClickContent: (Post) -> Unit = {}, + onReplyClick: (Post) -> Unit = {}, onOpenSubPosts: (subPostId: Long) -> Unit = {}, + onMenuCopyClick: ((Post) -> Unit)? = null, + onMenuFavoriteClick: ((Post) -> Unit)? = null, + onMenuReportClick: ((Post) -> Unit)? = null, + onMenuDeleteClick: ((Post) -> Unit)? = null, ) { val context = LocalContext.current if (blocked && !immersiveMode) { @@ -1325,7 +1366,7 @@ fun PostCard( } return } - val (post) = postHolder + val post = remember(postHolder) { postHolder.get() } val hasPadding = remember(key1 = postHolder, key2 = immersiveMode) { postHolder.get { floor > 1 } && !immersiveMode } @@ -1343,58 +1384,92 @@ fun PostCard( val subPosts = remember(postHolder) { post.sub_post_list?.sub_post_list?.toImmutableList() ?: persistentListOf() } - Card( - header = { - if (!immersiveMode) { - UserHeader( - avatar = { - Avatar( - data = StringUtil.getAvatarUrl(author.portrait), - size = Sizes.Small, - contentDescription = null - ) - }, - name = { - UserNameText( - userName = StringUtil.getUsernameAnnotatedString( - LocalContext.current, - author.name, - author.nameShow - ), - userLevel = author.level_id, - isLz = author.id == threadAuthorId, - bawuType = author.bawuType, - ) - }, - desc = { - Text(text = getDescText(post.time.toLong(), post.floor, author.ip_address)) - }, - onClick = { - UserActivity.launch(context, author.id.toString()) - } - ) { - if (post.floor > 1) { - PostAgreeBtn( - hasAgreed = hasAgreed, - agreeNum = agreeNum, - onClick = onAgree - ) + LongClickMenu( + indication = null, + onClick = { + onReplyClick(post) + }, + menuContent = { + DropdownMenuItem(onClick = { onReplyClick(post) }) { + Text(text = stringResource(id = R.string.btn_reply)) + } + if (onMenuCopyClick != null) { + DropdownMenuItem(onClick = { onMenuCopyClick(post) }) { + Text(text = stringResource(id = R.string.menu_copy)) + } + } + if (onMenuFavoriteClick != null) { + DropdownMenuItem(onClick = { onMenuFavoriteClick(post) }) { + if (isCollected(post)) { + Text(text = stringResource(id = R.string.title_collect)) + } else { + Text(text = stringResource(id = R.string.title_collect_on)) } } } - }, - content = { - SelectionContainer { + if (onMenuReportClick != null) { + DropdownMenuItem(onClick = { onMenuReportClick(post) }) { + Text(text = stringResource(id = R.string.title_report)) + } + } + if (canDelete(post) && onMenuDeleteClick != null) { + DropdownMenuItem(onClick = { onMenuDeleteClick(post) }) { + Text(text = stringResource(id = R.string.title_delete)) + } + } + } + ) { + Card( + header = { + if (!immersiveMode) { + UserHeader( + avatar = { + Avatar( + data = StringUtil.getAvatarUrl(author.portrait), + size = Sizes.Small, + contentDescription = null + ) + }, + name = { + UserNameText( + userName = StringUtil.getUsernameAnnotatedString( + LocalContext.current, + author.name, + author.nameShow + ), + userLevel = author.level_id, + isLz = author.id == threadAuthorId, + bawuType = author.bawuType, + ) + }, + desc = { + Text( + text = getDescText( + post.time.toLong(), + post.floor, + author.ip_address + ) + ) + }, + onClick = { + UserActivity.launch(context, author.id.toString()) + } + ) { + if (post.floor > 1) { + PostAgreeBtn( + hasAgreed = hasAgreed, + agreeNum = agreeNum, + onClick = onAgree + ) + } + } + } + }, + content = { Column( verticalArrangement = Arrangement.spacedBy(8.dp), modifier = paddingModifier .fillMaxWidth() - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null - ) { - onClickContent(post) - } ) { if (showTitle) { Text( @@ -1406,58 +1481,58 @@ fun PostCard( contentRenders.forEach { it.Render() } } - } - if (showSubPosts && post.sub_post_number > 0 && subPostContents.isNotEmpty() && !immersiveMode) { - Column( - modifier = Modifier - .fillMaxWidth() - .then(paddingModifier) - .clip(RoundedCornerShape(6.dp)) - .background(ExtendedTheme.colors.floorCard) - .padding(vertical = 12.dp), - verticalArrangement = Arrangement.spacedBy(2.dp) - ) { - subPostContents.forEachIndexed { index, text -> - PbContentText( - text = text, - modifier = Modifier - .fillMaxWidth() - .clickable { - onOpenSubPosts(subPosts[index].id) - } - .padding(horizontal = 12.dp), - color = ExtendedTheme.colors.text, - fontSize = 13.sp, - style = MaterialTheme.typography.body2, - emoticonSize = 0.9f, - overflow = TextOverflow.Ellipsis, - maxLines = 4, - ) - } + if (showSubPosts && post.sub_post_number > 0 && subPostContents.isNotEmpty() && !immersiveMode) { + Column( + modifier = Modifier + .fillMaxWidth() + .then(paddingModifier) + .clip(RoundedCornerShape(6.dp)) + .background(ExtendedTheme.colors.floorCard) + .padding(vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + subPostContents.forEachIndexed { index, text -> + PbContentText( + text = text, + modifier = Modifier + .fillMaxWidth() + .clickable { + onOpenSubPosts(subPosts[index].id) + } + .padding(horizontal = 12.dp), + color = ExtendedTheme.colors.text, + fontSize = 13.sp, + style = MaterialTheme.typography.body2, + emoticonSize = 0.9f, + overflow = TextOverflow.Ellipsis, + maxLines = 4, + ) + } - if (post.sub_post_number > subPostContents.size) { - Text( - text = stringResource( - id = R.string.open_all_sub_posts, - post.sub_post_number - ), - style = MaterialTheme.typography.caption, - fontSize = 13.sp, - color = ExtendedTheme.colors.accent, - modifier = Modifier - .fillMaxWidth() - .padding(top = 2.dp) - .clickable { - onOpenSubPosts(0) - } - .padding(horizontal = 12.dp) - ) + if (post.sub_post_number > subPostContents.size) { + Text( + text = stringResource( + id = R.string.open_all_sub_posts, + post.sub_post_number + ), + style = MaterialTheme.typography.caption, + fontSize = 13.sp, + color = ExtendedTheme.colors.accent, + modifier = Modifier + .fillMaxWidth() + .padding(top = 2.dp) + .clickable { + onOpenSubPosts(0) + } + .padding(horizontal = 12.dp) + ) + } } } } - } - ) + ) + } } @Composable diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Menu.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Menu.kt index d59cec3f..20e223d7 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Menu.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Menu.kt @@ -2,6 +2,7 @@ package com.huanchengfly.tieba.post.ui.widgets.compose import android.util.Log import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Indication import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable @@ -24,7 +25,6 @@ import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.listSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset @@ -106,19 +106,19 @@ fun ClickMenu( } } -@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class) +@OptIn(ExperimentalFoundationApi::class) @Composable fun LongClickMenu( + menuContent: @Composable() (ColumnScope.() -> Unit), + modifier: Modifier = Modifier, menuState: MenuState = rememberMenuState(), - menuContent: @Composable ColumnScope.() -> Unit, onClick: (() -> Unit)? = null, shape: Shape = RoundedCornerShape(14.dp), - modifier: Modifier = Modifier, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + indication: Indication? = LocalIndication.current, content: @Composable () -> Unit ) { val coroutineScope = rememberCoroutineScope() - val interactionSource = remember { MutableInteractionSource() } - val indication = LocalIndication.current LaunchedEffect(key1 = null) { coroutineScope.launch { interactionSource.interactions diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Toolbar.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Toolbar.kt index 26bc8b4b..0e26ae24 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Toolbar.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Toolbar.kt @@ -78,7 +78,6 @@ fun AccountNavIcon( val context = LocalContext.current val menuState = rememberMenuState() LongClickMenu( - menuState = menuState, menuContent = { val allAccounts = AccountUtil.allAccounts allAccounts.forEach { @@ -129,6 +128,7 @@ fun AccountNavIcon( Text(text = stringResource(id = R.string.title_new_account)) } }, + menuState = menuState, onClick = onClick, shape = CircleShape ) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index acc442de..ea06dc5f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -696,4 +696,5 @@ 显示用户名 显示吧名 修改用户名 + 回复