feat: 楼层长按菜单
This commit is contained in:
parent
d5f0b1c61a
commit
fad05912ae
|
|
@ -159,16 +159,9 @@ private val PbContent.picUrl: String
|
|||
cdnSrcActive,
|
||||
src
|
||||
)
|
||||
|
||||
val List<PbContent>.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<PbContent>.renders: ImmutableList<PbContentRender>
|
||||
|
|
@ -307,6 +300,7 @@ val List<PbContent>.renders: ImmutableList<PbContentRender>
|
|||
|
||||
return renders.toImmutableList()
|
||||
}
|
||||
|
||||
val Post.contentRenders: ImmutableList<PbContentRender>
|
||||
get() {
|
||||
val renders = content.renders
|
||||
|
|
@ -325,6 +319,7 @@ val Post.contentRenders: ImmutableList<PbContentRender>
|
|||
} else it
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
val User.bawuType: String?
|
||||
get() = if (is_bawu == 1) {
|
||||
if (bawu_type == "manager") "吧主" else "小吧主"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -298,7 +298,7 @@ internal fun SubPostsContent(
|
|||
)
|
||||
)
|
||||
},
|
||||
onClickContent = {
|
||||
onReplyClick = {
|
||||
navigator.navigate(
|
||||
ReplyPageDestination(
|
||||
forumId = forumId,
|
||||
|
|
|
|||
|
|
@ -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<AnnotatedString> = 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -696,4 +696,5 @@
|
|||
<string name="title_image_watermark_user_name">显示用户名</string>
|
||||
<string name="title_image_watermark_forum_name">显示吧名</string>
|
||||
<string name="title_modify_username">修改用户名</string>
|
||||
<string name="btn_reply">回复</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue