feat: 楼中楼屏蔽
This commit is contained in:
parent
5272f0f328
commit
478f275ceb
|
|
@ -18,6 +18,7 @@ import com.huanchengfly.tieba.post.ui.common.TextContentRender.Companion.appendT
|
|||
import com.huanchengfly.tieba.post.ui.common.VideoContentRender
|
||||
import com.huanchengfly.tieba.post.ui.common.VoiceContentRender
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.utils.ThemeUtils
|
||||
import com.huanchengfly.tieba.post.ui.page.thread.SubPostItemData
|
||||
import com.huanchengfly.tieba.post.ui.utils.getPhotoViewData
|
||||
import com.huanchengfly.tieba.post.utils.EmoticonManager
|
||||
import com.huanchengfly.tieba.post.utils.EmoticonUtil.emoticonString
|
||||
|
|
@ -330,6 +331,16 @@ val Post.subPostContents: ImmutableList<AnnotatedString>
|
|||
?.toImmutableList()
|
||||
?: persistentListOf()
|
||||
|
||||
val Post.subPosts: ImmutableList<SubPostItemData>
|
||||
get() = sub_post_list?.sub_post_list?.map {
|
||||
SubPostItemData(
|
||||
it.wrapImmutable(),
|
||||
it.getContentText(origin_thread_info?.author?.id)
|
||||
)
|
||||
}
|
||||
?.toImmutableList()
|
||||
?: persistentListOf()
|
||||
|
||||
@OptIn(ExperimentalTextApi::class)
|
||||
fun SubPostList.getContentText(threadAuthorId: Long? = null): AnnotatedString {
|
||||
val context = App.INSTANCE
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.forum.threadlist
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -56,12 +55,13 @@ import com.huanchengfly.tieba.post.ui.page.LocalNavigator
|
|||
import com.huanchengfly.tieba.post.ui.page.destinations.ThreadPageDestination
|
||||
import com.huanchengfly.tieba.post.ui.page.forum.getSortType
|
||||
import com.huanchengfly.tieba.post.ui.widgets.Chip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BlockTip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BlockableContent
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.FeedCard
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LocalSnackbarHostState
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider
|
||||
import com.huanchengfly.tieba.post.utils.appPreferences
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -185,70 +185,59 @@ private fun ThreadList(
|
|||
}
|
||||
}
|
||||
) { index, (holder, blocked) ->
|
||||
if (blocked) {
|
||||
if (!LocalContext.current.appPreferences.hideBlockedContent) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(ExtendedTheme.colors.card)
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.tip_blocked_thread),
|
||||
style = MaterialTheme.typography.caption,
|
||||
color = ExtendedTheme.colors.textSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
return@itemsIndexed
|
||||
}
|
||||
val (item) = holder
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(itemFraction)
|
||||
BlockableContent(
|
||||
blocked = blocked,
|
||||
blockedTip = { BlockTip(text = { Text(text = stringResource(id = R.string.tip_blocked_thread)) }) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
) {
|
||||
if (item.isTop == 1) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
onItemClicked(item)
|
||||
val (item) = holder
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(itemFraction)
|
||||
) {
|
||||
if (item.isTop == 1) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
onItemClicked(item)
|
||||
}
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Chip(
|
||||
text = stringResource(id = R.string.content_top),
|
||||
shape = RoundedCornerShape(3.dp)
|
||||
)
|
||||
var title = item.title
|
||||
if (title.isBlank()) {
|
||||
title = item.abstractText
|
||||
}
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Chip(
|
||||
text = stringResource(id = R.string.content_top),
|
||||
shape = RoundedCornerShape(3.dp)
|
||||
)
|
||||
var title = item.title
|
||||
if (title.isBlank()) {
|
||||
title = item.abstractText
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.subtitle2,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f),
|
||||
fontSize = 15.sp
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.subtitle2,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f),
|
||||
fontSize = 15.sp
|
||||
} else {
|
||||
if (index > 0) {
|
||||
if (items[index - 1].thread.get { isTop } == 1) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
VerticalDivider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||
}
|
||||
FeedCard(
|
||||
item = holder,
|
||||
onClick = onItemClicked,
|
||||
onReplyClick = onItemReplyClicked,
|
||||
onAgree = onAgree,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (index > 0) {
|
||||
if (items[index - 1].thread.get { isTop } == 1) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
VerticalDivider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||
}
|
||||
FeedCard(
|
||||
item = holder,
|
||||
onClick = onItemClicked,
|
||||
onReplyClick = onItemReplyClicked,
|
||||
onAgree = onAgree,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ import com.huanchengfly.tieba.post.ui.models.ThreadItemData
|
|||
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
|
||||
import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination
|
||||
import com.huanchengfly.tieba.post.ui.page.destinations.ThreadPageDestination
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BlockTip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BlockableContent
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.FeedCard
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
|
||||
|
|
@ -307,7 +309,13 @@ private fun FeedList(
|
|||
enter = EnterTransition.None,
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
) {
|
||||
if (!blocked) {
|
||||
BlockableContent(
|
||||
blocked = blocked,
|
||||
blockedTip = { BlockTip(text = { Text(text = stringResource(id = R.string.tip_blocked_thread)) }) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
) {
|
||||
FeedCard(
|
||||
item = item,
|
||||
onClick = onItemClick,
|
||||
|
|
@ -328,21 +336,6 @@ private fun FeedList(
|
|||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(ExtendedTheme.colors.card)
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.tip_blocked_thread),
|
||||
style = MaterialTheme.typography.caption,
|
||||
color = ExtendedTheme.colors.textSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showDivider) {
|
||||
|
|
|
|||
|
|
@ -40,14 +40,12 @@ import com.huanchengfly.tieba.post.App
|
|||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.activities.UserActivity
|
||||
import com.huanchengfly.tieba.post.api.models.protos.SubPostList
|
||||
import com.huanchengfly.tieba.post.api.models.protos.User
|
||||
import com.huanchengfly.tieba.post.api.models.protos.bawuType
|
||||
import com.huanchengfly.tieba.post.arch.ImmutableHolder
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.onEvent
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||
import com.huanchengfly.tieba.post.ui.common.PbContentRender
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
|
||||
import com.huanchengfly.tieba.post.ui.page.ProvideNavigator
|
||||
|
|
@ -57,6 +55,8 @@ import com.huanchengfly.tieba.post.ui.page.thread.PostAgreeBtn
|
|||
import com.huanchengfly.tieba.post.ui.page.thread.PostCard
|
||||
import com.huanchengfly.tieba.post.ui.page.thread.UserNameText
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BlockTip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BlockableContent
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Card
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ConfirmDialog
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||
|
|
@ -77,7 +77,6 @@ import com.huanchengfly.tieba.post.utils.TiebaUtil
|
|||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.spec.DestinationStyleBottomSheet
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
|
|
@ -187,10 +186,6 @@ internal fun SubPostsContent(
|
|||
prop1 = SubPostsUiState::subPosts,
|
||||
initial = persistentListOf()
|
||||
)
|
||||
val subPostsContentRenders by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SubPostsUiState::subPostsContentRenders,
|
||||
initial = persistentListOf()
|
||||
)
|
||||
val currentPage by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SubPostsUiState::currentPage,
|
||||
initial = 1
|
||||
|
|
@ -208,7 +203,7 @@ internal fun SubPostsContent(
|
|||
|
||||
viewModel.onEvent<SubPostsUiEvent.ScrollToSubPosts> {
|
||||
delay(20)
|
||||
lazyListState.scrollToItem(2 + subPosts.indexOfFirst { it.get { id } == subPostId })
|
||||
lazyListState.scrollToItem(2 + subPosts.indexOfFirst { it.id == subPostId })
|
||||
}
|
||||
|
||||
val confirmDeleteDialogState = rememberDialogState()
|
||||
|
|
@ -432,11 +427,10 @@ internal fun SubPostsContent(
|
|||
}
|
||||
itemsIndexed(
|
||||
items = subPosts,
|
||||
key = { _, subPost -> subPost.get { id } }
|
||||
key = { _, subPost -> subPost.id }
|
||||
) { index, item ->
|
||||
SubPostItem(
|
||||
subPost = item,
|
||||
contentRenders = subPostsContentRenders[index],
|
||||
item = item,
|
||||
canDelete = { it.author_id == account?.uid?.toLongOrNull() },
|
||||
threadAuthorId = thread?.get { author?.id },
|
||||
onAgree = {
|
||||
|
|
@ -495,16 +489,16 @@ private fun getDescText(
|
|||
|
||||
@Composable
|
||||
private fun SubPostItem(
|
||||
subPost: ImmutableHolder<SubPostList>,
|
||||
contentRenders: ImmutableList<PbContentRender>,
|
||||
item: SubPostItemData,
|
||||
threadAuthorId: Long? = null,
|
||||
canDelete: (SubPostList) -> Boolean = { false },
|
||||
onAgree: (SubPostList) -> Unit = {},
|
||||
onReplyClick: (SubPostList) -> Unit = {},
|
||||
onMenuDeleteClick: ((SubPostList) -> Unit)? = null,
|
||||
) {
|
||||
val (subPost, contentRenders, blocked) = item
|
||||
val context = LocalContext.current
|
||||
val author = remember(subPost) { subPost.getImmutable { author } }
|
||||
val author = remember(subPost) { subPost.get { author }?.wrapImmutable() }
|
||||
val hasAgreed = remember(subPost) {
|
||||
subPost.get { agree?.hasAgree == 1 }
|
||||
}
|
||||
|
|
@ -512,100 +506,109 @@ private fun SubPostItem(
|
|||
subPost.get { agree?.diffAgreeNum ?: 0L }
|
||||
}
|
||||
val menuState = rememberMenuState()
|
||||
LongClickMenu(
|
||||
menuState = menuState,
|
||||
indication = null,
|
||||
menuContent = {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onReplyClick(subPost.get())
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.btn_reply))
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
TiebaUtil.copyText(context, contentRenders.joinToString("\n") { it.toString() })
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.menu_copy))
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
TiebaUtil.reportPost(context, subPost.get { id }.toString())
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.title_report))
|
||||
}
|
||||
if (canDelete(subPost.get()) && onMenuDeleteClick != null) {
|
||||
BlockableContent(
|
||||
blocked = blocked,
|
||||
blockedTip = { BlockTip(text = { Text(text = stringResource(id = R.string.tip_blocked_sub_post)) }) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
) {
|
||||
LongClickMenu(
|
||||
menuState = menuState,
|
||||
indication = null,
|
||||
menuContent = {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onMenuDeleteClick(subPost.get())
|
||||
onReplyClick(subPost.get())
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.title_delete))
|
||||
Text(text = stringResource(id = R.string.btn_reply))
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = { onReplyClick(subPost.get()) }
|
||||
) {
|
||||
Card(
|
||||
header = {
|
||||
if (author.isNotNull()) {
|
||||
author as ImmutableHolder<User>
|
||||
UserHeader(
|
||||
avatar = {
|
||||
Avatar(
|
||||
data = StringUtil.getAvatarUrl(author.get { portrait }),
|
||||
size = Sizes.Small,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
name = {
|
||||
UserNameText(
|
||||
userName = StringUtil.getUsernameAnnotatedString(
|
||||
LocalContext.current,
|
||||
author.get { name },
|
||||
author.get { nameShow }
|
||||
),
|
||||
userLevel = author.get { level_id },
|
||||
isLz = author.get { id } == threadAuthorId,
|
||||
bawuType = author.get { bawuType },
|
||||
)
|
||||
},
|
||||
desc = {
|
||||
Text(
|
||||
text = getDescText(
|
||||
subPost.get { time }.toLong(),
|
||||
author.get { ip_address })
|
||||
)
|
||||
},
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
TiebaUtil.copyText(
|
||||
context,
|
||||
contentRenders.joinToString("\n") { it.toString() })
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.menu_copy))
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
TiebaUtil.reportPost(context, subPost.get { id }.toString())
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.title_report))
|
||||
}
|
||||
if (canDelete(subPost.get()) && onMenuDeleteClick != null) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
UserActivity.launch(context, author.get { id }.toString())
|
||||
onMenuDeleteClick(subPost.get())
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
PostAgreeBtn(
|
||||
hasAgreed = hasAgreed,
|
||||
agreeNum = agreeNum,
|
||||
onClick = { onAgree(subPost.get()) }
|
||||
)
|
||||
Text(text = stringResource(id = R.string.title_delete))
|
||||
}
|
||||
}
|
||||
},
|
||||
content = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.padding(start = Sizes.Small + 8.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
contentRenders.fastForEach { it.Render() }
|
||||
onClick = { onReplyClick(subPost.get()) }
|
||||
) {
|
||||
Card(
|
||||
header = {
|
||||
if (author != null) {
|
||||
UserHeader(
|
||||
avatar = {
|
||||
Avatar(
|
||||
data = StringUtil.getAvatarUrl(author.get { portrait }),
|
||||
size = Sizes.Small,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
name = {
|
||||
UserNameText(
|
||||
userName = StringUtil.getUsernameAnnotatedString(
|
||||
LocalContext.current,
|
||||
author.get { name },
|
||||
author.get { nameShow }
|
||||
),
|
||||
userLevel = author.get { level_id },
|
||||
isLz = author.get { id } == threadAuthorId,
|
||||
bawuType = author.get { bawuType },
|
||||
)
|
||||
},
|
||||
desc = {
|
||||
Text(
|
||||
text = getDescText(
|
||||
subPost.get { time }.toLong(),
|
||||
author.get { ip_address })
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
UserActivity.launch(context, author.get { id }.toString())
|
||||
}
|
||||
) {
|
||||
PostAgreeBtn(
|
||||
hasAgreed = hasAgreed,
|
||||
agreeNum = agreeNum,
|
||||
onClick = { onAgree(subPost.get()) }
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
content = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.padding(start = Sizes.Small + 8.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
contentRenders.fastForEach { it.Render() }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.subposts
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.huanchengfly.tieba.post.api.TiebaApi
|
||||
import com.huanchengfly.tieba.post.api.models.AgreeBean
|
||||
|
|
@ -9,6 +10,7 @@ import com.huanchengfly.tieba.post.api.models.protos.Post
|
|||
import com.huanchengfly.tieba.post.api.models.protos.SimpleForum
|
||||
import com.huanchengfly.tieba.post.api.models.protos.SubPostList
|
||||
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.contentRenders
|
||||
import com.huanchengfly.tieba.post.api.models.protos.pbFloor.PbFloorResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.renders
|
||||
|
|
@ -23,8 +25,8 @@ import com.huanchengfly.tieba.post.arch.UiEvent
|
|||
import com.huanchengfly.tieba.post.arch.UiIntent
|
||||
import com.huanchengfly.tieba.post.arch.UiState
|
||||
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||
import com.huanchengfly.tieba.post.removeAt
|
||||
import com.huanchengfly.tieba.post.ui.common.PbContentRender
|
||||
import com.huanchengfly.tieba.post.utils.BlockManager.shouldBlock
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -77,15 +79,19 @@ class SubPostsViewModel @Inject constructor() :
|
|||
val forum = checkNotNull(response.data_?.forum)
|
||||
val thread = checkNotNull(response.data_?.thread)
|
||||
val anti = checkNotNull(response.data_?.anti)
|
||||
val subPosts = response.data_?.subpost_list.orEmpty()
|
||||
val subPosts = response.data_?.subpost_list.orEmpty().map {
|
||||
SubPostItemData(
|
||||
it.wrapImmutable(),
|
||||
it.content.renders.toImmutableList(),
|
||||
)
|
||||
}.toImmutableList()
|
||||
SubPostsPartialChange.Load.Success(
|
||||
anti.wrapImmutable(),
|
||||
forum.wrapImmutable(),
|
||||
thread.wrapImmutable(),
|
||||
post.wrapImmutable(),
|
||||
post.contentRenders,
|
||||
subPosts.wrapImmutable(),
|
||||
subPosts.map { it.content.renders }.toImmutableList(),
|
||||
subPosts,
|
||||
page.current_page < page.total_page,
|
||||
page.current_page,
|
||||
page.total_page,
|
||||
|
|
@ -100,10 +106,14 @@ class SubPostsViewModel @Inject constructor() :
|
|||
.pbFloorFlow(threadId, postId, forumId, page, subPostId)
|
||||
.map<PbFloorResponse, SubPostsPartialChange.LoadMore> { response ->
|
||||
val page = checkNotNull(response.data_?.page)
|
||||
val subPosts = response.data_?.subpost_list.orEmpty()
|
||||
val subPosts = response.data_?.subpost_list.orEmpty().map {
|
||||
SubPostItemData(
|
||||
it.wrapImmutable(),
|
||||
it.content.renders.toImmutableList(),
|
||||
)
|
||||
}.toImmutableList()
|
||||
SubPostsPartialChange.LoadMore.Success(
|
||||
subPosts.wrapImmutable(),
|
||||
subPosts.map { it.content.renders }.toImmutableList(),
|
||||
subPosts,
|
||||
page.current_page < page.total_page,
|
||||
page.current_page,
|
||||
page.total_page,
|
||||
|
|
@ -208,7 +218,6 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
|||
post = post,
|
||||
postContentRenders = postContentRenders,
|
||||
subPosts = subPosts,
|
||||
subPostsContentRenders = subPostsContentRenders,
|
||||
)
|
||||
|
||||
is Failure -> oldState.copy(
|
||||
|
|
@ -216,15 +225,15 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
|||
)
|
||||
}
|
||||
|
||||
object Start : Load()
|
||||
data object Start : Load()
|
||||
|
||||
data class Success(
|
||||
val anti: ImmutableHolder<Anti>,
|
||||
val forum: ImmutableHolder<SimpleForum>,
|
||||
val thread: ImmutableHolder<ThreadInfo>,
|
||||
val post: ImmutableHolder<Post>,
|
||||
val postContentRenders: ImmutableList<PbContentRender>,
|
||||
val subPosts: ImmutableList<ImmutableHolder<SubPostList>>,
|
||||
val subPostsContentRenders: ImmutableList<ImmutableList<PbContentRender>>,
|
||||
val subPosts: ImmutableList<SubPostItemData>,
|
||||
val hasMore: Boolean,
|
||||
val currentPage: Int,
|
||||
val totalPage: Int,
|
||||
|
|
@ -248,7 +257,6 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
|||
totalPage = totalPage,
|
||||
totalCount = totalCount,
|
||||
subPosts = (oldState.subPosts + subPosts).toImmutableList(),
|
||||
subPostsContentRenders = (oldState.subPostsContentRenders + subPostsContentRenders).toImmutableList(),
|
||||
)
|
||||
|
||||
is Failure -> oldState.copy(
|
||||
|
|
@ -256,10 +264,10 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
|||
)
|
||||
}
|
||||
|
||||
object Start : LoadMore()
|
||||
data object Start : LoadMore()
|
||||
|
||||
data class Success(
|
||||
val subPosts: ImmutableList<ImmutableHolder<SubPostList>>,
|
||||
val subPostsContentRenders: ImmutableList<ImmutableList<PbContentRender>>,
|
||||
val subPosts: ImmutableList<SubPostItemData>,
|
||||
val hasMore: Boolean,
|
||||
val currentPage: Int,
|
||||
val totalPage: Int,
|
||||
|
|
@ -270,13 +278,13 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
|||
}
|
||||
|
||||
sealed class Agree : SubPostsPartialChange {
|
||||
private fun List<ImmutableHolder<SubPostList>>.updateAgreeStatus(
|
||||
private fun List<SubPostItemData>.updateAgreeStatus(
|
||||
subPostId: Long,
|
||||
hasAgreed: Boolean
|
||||
): ImmutableList<ImmutableHolder<SubPostList>> =
|
||||
hasAgreed: Boolean,
|
||||
): ImmutableList<SubPostItemData> =
|
||||
map {
|
||||
if (it.get { id } == subPostId) {
|
||||
it.getImmutable { updateAgreeStatus(if (hasAgreed) 1 else 0) }
|
||||
if (it.id == subPostId) {
|
||||
it.updateAgreeStatus(if (hasAgreed) 1 else 0)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
|
|
@ -341,13 +349,9 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
|||
if (subPostId == null) {
|
||||
oldState
|
||||
} else {
|
||||
val deletedSubPostIndex =
|
||||
oldState.subPosts.indexOfFirst { it.get { id } == postId }
|
||||
oldState.copy(
|
||||
subPosts = oldState.subPosts.removeAt(deletedSubPostIndex),
|
||||
subPostsContentRenders = oldState.subPostsContentRenders.removeAt(
|
||||
deletedSubPostIndex
|
||||
),
|
||||
subPosts = oldState.subPosts.filter { it.id != subPostId }
|
||||
.toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -362,11 +366,35 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
|||
|
||||
data class Failure(
|
||||
val errorCode: Int,
|
||||
val errorMessage: String
|
||||
val errorMessage: String,
|
||||
) : DeletePost()
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class SubPostItemData(
|
||||
val subPost: ImmutableHolder<SubPostList>,
|
||||
val subPostContentRenders: ImmutableList<PbContentRender>,
|
||||
val blocked: Boolean = subPost.get { shouldBlock() },
|
||||
) {
|
||||
constructor(
|
||||
subPost: SubPostList,
|
||||
) : this(
|
||||
subPost.wrapImmutable(),
|
||||
subPost.content.renders.toImmutableList(),
|
||||
subPost.shouldBlock()
|
||||
)
|
||||
|
||||
val id: Long
|
||||
get() = subPost.get { id }
|
||||
|
||||
val author: ImmutableHolder<User>?
|
||||
get() = subPost.get { author }?.wrapImmutable()
|
||||
}
|
||||
|
||||
private fun SubPostItemData.updateAgreeStatus(hasAgreed: Int): SubPostItemData =
|
||||
copy(subPost = subPost.getImmutable { updateAgreeStatus(hasAgreed) })
|
||||
|
||||
data class SubPostsUiState(
|
||||
val isLoading: Boolean = false,
|
||||
val isRefreshing: Boolean = false,
|
||||
|
|
@ -381,10 +409,9 @@ data class SubPostsUiState(
|
|||
val thread: ImmutableHolder<ThreadInfo>? = null,
|
||||
val post: ImmutableHolder<Post>? = null,
|
||||
val postContentRenders: ImmutableList<PbContentRender> = persistentListOf(),
|
||||
val subPosts: ImmutableList<ImmutableHolder<SubPostList>> = persistentListOf(),
|
||||
val subPostsContentRenders: ImmutableList<ImmutableList<PbContentRender>> = persistentListOf(),
|
||||
val subPosts: ImmutableList<SubPostItemData> = persistentListOf(),
|
||||
) : UiState
|
||||
|
||||
sealed interface SubPostsUiEvent : UiEvent {
|
||||
object ScrollToSubPosts : SubPostsUiEvent
|
||||
data object ScrollToSubPosts : SubPostsUiEvent
|
||||
}
|
||||
|
|
@ -85,7 +85,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.huanchengfly.tieba.post.App
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.activities.UserActivity
|
||||
|
|
@ -125,6 +124,8 @@ import com.huanchengfly.tieba.post.ui.page.destinations.ThreadPageDestination
|
|||
import com.huanchengfly.tieba.post.ui.widgets.Chip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BackNavigationIcon
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BlockTip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BlockableContent
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Button
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Card
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ConfirmDialog
|
||||
|
|
@ -799,13 +800,13 @@ fun ThreadPage(
|
|||
fun PostCard(
|
||||
item: ImmutableHolder<Post>,
|
||||
contentRenders: ImmutableList<PbContentRender>,
|
||||
subPostContents: ImmutableList<AnnotatedString>,
|
||||
blocked: Boolean
|
||||
subPosts: ImmutableList<SubPostItemData>,
|
||||
blocked: Boolean,
|
||||
) {
|
||||
PostCard(
|
||||
postHolder = item,
|
||||
contentRenders = contentRenders,
|
||||
subPostContents = subPostContents,
|
||||
subPosts = subPosts,
|
||||
threadAuthorId = author?.get { id } ?: 0L,
|
||||
blocked = blocked,
|
||||
canDelete = { it.author_id == user.get { id } },
|
||||
|
|
@ -920,11 +921,11 @@ fun ThreadPage(
|
|||
items(
|
||||
items = latestPosts,
|
||||
key = { (item) -> "LatestPost_${item.get { id }}" }
|
||||
) { (item, blocked, renders, subPostContents) ->
|
||||
) { (item, blocked, renders, subPosts) ->
|
||||
PostCard(
|
||||
item,
|
||||
renders,
|
||||
subPostContents,
|
||||
subPosts,
|
||||
blocked
|
||||
)
|
||||
}
|
||||
|
|
@ -1295,11 +1296,11 @@ fun ThreadPage(
|
|||
items(
|
||||
items = data,
|
||||
key = { (item) -> "Post_${item.get { id }}" }
|
||||
) { (item, blocked, renders, subPostContents) ->
|
||||
) { (item, blocked, renders, subPosts) ->
|
||||
PostCard(
|
||||
item,
|
||||
renders,
|
||||
subPostContents,
|
||||
subPosts,
|
||||
blocked
|
||||
)
|
||||
}
|
||||
|
|
@ -1484,7 +1485,7 @@ private fun BottomBar(
|
|||
fun PostCard(
|
||||
postHolder: ImmutableHolder<Post>,
|
||||
contentRenders: ImmutableList<PbContentRender>,
|
||||
subPostContents: ImmutableList<AnnotatedString> = persistentListOf(),
|
||||
subPosts: ImmutableList<SubPostItemData> = persistentListOf(),
|
||||
threadAuthorId: Long = 0L,
|
||||
blocked: Boolean = false,
|
||||
canDelete: (Post) -> Boolean = { false },
|
||||
|
|
@ -1499,26 +1500,6 @@ fun PostCard(
|
|||
onMenuDeleteClick: ((Post) -> Unit)? = null,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
if (blocked && !immersiveMode) {
|
||||
val hideBlockedContent = context.appPreferences.hideBlockedContent
|
||||
if (!hideBlockedContent) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(ExtendedTheme.colors.floorCard)
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.tip_blocked_post, postHolder.get { floor }),
|
||||
style = MaterialTheme.typography.caption,
|
||||
color = ExtendedTheme.colors.textSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
val post = remember(postHolder) { postHolder.get() }
|
||||
val hasPadding = remember(key1 = postHolder, key2 = immersiveMode) {
|
||||
postHolder.get { floor > 1 } && !immersiveMode
|
||||
|
|
@ -1534,188 +1515,214 @@ fun PostCard(
|
|||
val agreeNum = remember(postHolder) {
|
||||
post.agree?.diffAgreeNum ?: 0L
|
||||
}
|
||||
val subPosts = remember(postHolder) {
|
||||
postHolder.get { sub_post_list?.sub_post_list }?.wrapImmutable() ?: persistentListOf()
|
||||
}
|
||||
val menuState = rememberMenuState()
|
||||
LongClickMenu(
|
||||
menuState = menuState,
|
||||
indication = null,
|
||||
onClick = {
|
||||
onReplyClick(post)
|
||||
BlockableContent(
|
||||
blocked = blocked,
|
||||
blockedTip = {
|
||||
BlockTip {
|
||||
Text(
|
||||
text = stringResource(id = R.string.tip_blocked_post, postHolder.get { floor }),
|
||||
)
|
||||
}
|
||||
},
|
||||
menuContent = {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onReplyClick(post)
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.btn_reply))
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
TiebaUtil.copyText(context, post.content.plainText)
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.menu_copy))
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
TiebaUtil.reportPost(context, post.id.toString())
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.title_report))
|
||||
}
|
||||
if (onMenuFavoriteClick != null) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onMenuFavoriteClick(post)
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
if (isCollected(post)) {
|
||||
Text(text = stringResource(id = R.string.title_collect_on))
|
||||
} else {
|
||||
Text(text = stringResource(id = R.string.title_collect_floor))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canDelete(post) && onMenuDeleteClick != null) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onMenuDeleteClick(post)
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.title_delete))
|
||||
}
|
||||
}
|
||||
}
|
||||
hideBlockedContent = context.appPreferences.hideBlockedContent || immersiveMode,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
) {
|
||||
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
|
||||
)
|
||||
)
|
||||
},
|
||||
LongClickMenu(
|
||||
menuState = menuState,
|
||||
indication = null,
|
||||
onClick = {
|
||||
onReplyClick(post)
|
||||
},
|
||||
menuContent = {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onReplyClick(post)
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.btn_reply))
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
TiebaUtil.copyText(context, post.content.plainText)
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.menu_copy))
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
TiebaUtil.reportPost(context, post.id.toString())
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.title_report))
|
||||
}
|
||||
if (onMenuFavoriteClick != null) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
UserActivity.launch(context, author.id.toString())
|
||||
onMenuFavoriteClick(post)
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
if (post.floor > 1) {
|
||||
PostAgreeBtn(
|
||||
hasAgreed = hasAgreed,
|
||||
agreeNum = agreeNum,
|
||||
onClick = onAgree
|
||||
)
|
||||
if (isCollected(post)) {
|
||||
Text(text = stringResource(id = R.string.title_collect_on))
|
||||
} else {
|
||||
Text(text = stringResource(id = R.string.title_collect_floor))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
content = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = paddingModifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
if (showTitle) {
|
||||
Text(
|
||||
text = post.title,
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
fontSize = 15.sp
|
||||
)
|
||||
if (canDelete(post) && onMenuDeleteClick != null) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onMenuDeleteClick(post)
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.title_delete))
|
||||
}
|
||||
|
||||
if (isCollected(post)) {
|
||||
Chip(
|
||||
text = stringResource(id = R.string.title_collected_floor),
|
||||
invertColor = true,
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Star,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
}
|
||||
}
|
||||
) {
|
||||
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
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
contentRenders.fastForEach { it.Render() }
|
||||
}
|
||||
|
||||
if (showSubPosts && post.sub_post_number > 0 && subPostContents.isNotEmpty() && !immersiveMode) {
|
||||
},
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = paddingModifier
|
||||
.fillMaxWidth()
|
||||
.then(paddingModifier)
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(ExtendedTheme.colors.floorCard)
|
||||
.padding(vertical = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
subPostContents.fastForEachIndexed { index, text ->
|
||||
SubPostItem(
|
||||
subPostList = subPosts[index],
|
||||
subPostContent = text,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp),
|
||||
onReplyClick = { onSubPostReplyClick?.invoke(post, it) },
|
||||
onOpenSubPosts = onOpenSubPosts,
|
||||
if (showTitle) {
|
||||
Text(
|
||||
text = post.title,
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
fontSize = 15.sp
|
||||
)
|
||||
}
|
||||
|
||||
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 (isCollected(post)) {
|
||||
Chip(
|
||||
text = stringResource(id = R.string.title_collected_floor),
|
||||
invertColor = true,
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Star,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
contentRenders.fastForEach { it.Render() }
|
||||
}
|
||||
|
||||
if (showSubPosts && post.sub_post_number > 0 && subPosts.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)
|
||||
) {
|
||||
subPosts.fastForEach { item ->
|
||||
BlockableContent(
|
||||
blocked = item.blocked,
|
||||
blockedTip = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.tip_blocked_sub_post),
|
||||
style = MaterialTheme.typography.body2.copy(
|
||||
color = ExtendedTheme.colors.textDisabled,
|
||||
fontSize = 13.sp
|
||||
),
|
||||
modifier = Modifier.padding(horizontal = 12.dp)
|
||||
)
|
||||
},
|
||||
) {
|
||||
SubPostItem(
|
||||
subPostList = item.subPost,
|
||||
subPostContent = item.subPostContent,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp),
|
||||
onReplyClick = { onSubPostReplyClick?.invoke(post, it) },
|
||||
onOpenSubPosts = onOpenSubPosts,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (post.sub_post_number > subPosts.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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ import com.huanchengfly.tieba.post.api.models.CommonResponse
|
|||
import com.huanchengfly.tieba.post.api.models.protos.Anti
|
||||
import com.huanchengfly.tieba.post.api.models.protos.Post
|
||||
import com.huanchengfly.tieba.post.api.models.protos.SimpleForum
|
||||
import com.huanchengfly.tieba.post.api.models.protos.SubPostList
|
||||
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.contentRenders
|
||||
import com.huanchengfly.tieba.post.api.models.protos.pbPage.PbPageResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.renders
|
||||
import com.huanchengfly.tieba.post.api.models.protos.subPostContents
|
||||
import com.huanchengfly.tieba.post.api.models.protos.subPosts
|
||||
import com.huanchengfly.tieba.post.api.models.protos.updateAgreeStatus
|
||||
import com.huanchengfly.tieba.post.api.models.protos.updateCollectStatus
|
||||
import com.huanchengfly.tieba.post.api.retrofit.exception.TiebaUnknownException
|
||||
|
|
@ -1137,5 +1139,18 @@ data class PostItemData(
|
|||
val post: ImmutableHolder<Post>,
|
||||
val blocked: Boolean = post.get { shouldBlock() },
|
||||
val contentRenders: ImmutableList<PbContentRender> = post.get { this.contentRenders },
|
||||
val subPostContents: ImmutableList<AnnotatedString> = post.get { this.subPostContents },
|
||||
)
|
||||
val subPosts: ImmutableList<SubPostItemData> = post.get { this.subPosts },
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class SubPostItemData(
|
||||
val subPost: ImmutableHolder<SubPostList>,
|
||||
val subPostContent: AnnotatedString,
|
||||
val blocked: Boolean = subPost.get { shouldBlock() },
|
||||
) {
|
||||
val id: Long
|
||||
get() = subPost.get { id }
|
||||
|
||||
val author: ImmutableHolder<User>?
|
||||
get() = subPost.get { author }?.wrapImmutable()
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.huanchengfly.tieba.post.ui.widgets.compose
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.ProvideTextStyle
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.utils.appPreferences
|
||||
|
||||
@Composable
|
||||
fun BlockTip(
|
||||
text: @Composable () -> Unit = { Text(text = stringResource(id = R.string.tip_blocked_content)) },
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(ExtendedTheme.colors.textSecondary.copy(alpha = 0.1f))
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
) {
|
||||
ProvideTextStyle(value = MaterialTheme.typography.caption) {
|
||||
ProvideContentColor(color = ExtendedTheme.colors.textSecondary) {
|
||||
text()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BlockableContent(
|
||||
blocked: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
blockedTip: @Composable () -> Unit = { BlockTip() },
|
||||
hideBlockedContent: Boolean = LocalContext.current.appPreferences.hideBlockedContent,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
if (!blocked) {
|
||||
content()
|
||||
} else if (!hideBlockedContent) {
|
||||
Column(modifier = modifier) {
|
||||
blockedTip()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.huanchengfly.tieba.post.utils
|
||||
|
||||
import com.huanchengfly.tieba.post.api.models.protos.Post
|
||||
import com.huanchengfly.tieba.post.api.models.protos.SubPostList
|
||||
import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo
|
||||
import com.huanchengfly.tieba.post.api.models.protos.abstractText
|
||||
import com.huanchengfly.tieba.post.api.models.protos.plainText
|
||||
|
|
@ -60,4 +61,7 @@ object BlockManager {
|
|||
|
||||
fun Post.shouldBlock(): Boolean =
|
||||
shouldBlock(content.plainText) || shouldBlock(author_id, author?.name)
|
||||
|
||||
fun SubPostList.shouldBlock(): Boolean =
|
||||
shouldBlock(content.plainText) || shouldBlock(author_id, author?.name)
|
||||
}
|
||||
|
|
@ -675,6 +675,7 @@
|
|||
<string name="open_all_sub_posts">查看全部 %d 条回复</string>
|
||||
<string name="message_store_thread_update">贴子更新到了第 %d 楼</string>
|
||||
<string name="tip_blocked_post">由于你的屏蔽设置,第 %d 楼已被屏蔽</string>
|
||||
<string name="tip_blocked_sub_post">由于你的屏蔽设置,该回复已被屏蔽</string>
|
||||
<string name="settings_block_video">不看视频贴</string>
|
||||
<string name="btn_full_screen">全屏</string>
|
||||
<string name="btn_play">播放</string>
|
||||
|
|
@ -707,6 +708,7 @@
|
|||
<string name="below_is_latest_post">以下为最新回复</string>
|
||||
<string name="btn_open_origin_thread">查看原贴</string>
|
||||
<string name="tip_blocked_thread">由于你的屏蔽设置,该贴已被屏蔽</string>
|
||||
<string name="tip_blocked_content">由于你的屏蔽设置,该内容已被屏蔽</string>
|
||||
<string name="desc_picture">图片</string>
|
||||
<string name="tip_thread_store_deleted">该贴已被删除</string>
|
||||
<string name="button_expand">展开</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue