feat: 楼中楼点赞

This commit is contained in:
HuanCheng65 2023-07-17 12:02:29 +08:00
parent f14258a6a9
commit 2cf960e88a
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
4 changed files with 177 additions and 16 deletions

View File

@ -13,6 +13,7 @@ import com.huanchengfly.tieba.post.App.ScreenInfo
import com.huanchengfly.tieba.post.R import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.api.models.protos.PbContent import com.huanchengfly.tieba.post.api.models.protos.PbContent
import com.huanchengfly.tieba.post.api.models.protos.Post 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.ThreadInfo
import com.huanchengfly.tieba.post.arch.wrapImmutable import com.huanchengfly.tieba.post.arch.wrapImmutable
import com.huanchengfly.tieba.post.ui.common.PbContentRender import com.huanchengfly.tieba.post.ui.common.PbContentRender
@ -131,6 +132,34 @@ fun Post.updateAgreeStatus(
this this
} }
fun SubPostList.updateAgreeStatus(
hasAgree: Int
) = if (agree != null) {
if (hasAgree != agree.hasAgree) {
if (hasAgree == 1) {
copy(
agree = agree.copy(
agreeNum = agree.agreeNum + 1,
diffAgreeNum = agree.diffAgreeNum + 1,
hasAgree = 1
)
)
} else {
copy(
agree = agree.copy(
agreeNum = agree.agreeNum - 1,
diffAgreeNum = agree.diffAgreeNum - 1,
hasAgree = 0
)
)
}
} else {
this
}
} else {
this
}
private val PbContent.picUrl: String private val PbContent.picUrl: String
get() = get() =
ImageUtil.getUrl( ImageUtil.getUrl(

View File

@ -37,6 +37,7 @@ import com.huanchengfly.tieba.post.ui.common.PbContentRender
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme 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.LocalNavigator
import com.huanchengfly.tieba.post.ui.page.ProvideNavigator import com.huanchengfly.tieba.post.ui.page.ProvideNavigator
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.PostCard
import com.huanchengfly.tieba.post.ui.page.thread.UserNameText 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.Avatar
@ -221,7 +222,18 @@ internal fun SubPostsContent(
PostCard( PostCard(
postHolder = it, postHolder = it,
contentRenders = postContentRenders, contentRenders = postContentRenders,
showSubPosts = false showSubPosts = false,
onAgree = {
val hasAgreed = it.get { agree?.hasAgree != 0 }
viewModel.send(
SubPostsUiIntent.Agree(
forumId,
threadId,
postId,
agree = !hasAgreed
)
)
},
) )
VerticalDivider(thickness = 2.dp) VerticalDivider(thickness = 2.dp)
} }
@ -249,7 +261,19 @@ internal fun SubPostsContent(
) { index, item -> ) { index, item ->
SubPostItem( SubPostItem(
subPost = item, subPost = item,
contentRenders = subPostsContentRenders[index] contentRenders = subPostsContentRenders[index],
onAgree = {
val hasAgreed = it.agree?.hasAgree != 0
viewModel.send(
SubPostsUiIntent.Agree(
forumId,
threadId,
postId,
subPostId = it.id,
agree = !hasAgreed
)
)
},
) )
} }
} }
@ -277,10 +301,17 @@ private fun getDescText(
private fun SubPostItem( private fun SubPostItem(
subPost: ImmutableHolder<SubPostList>, subPost: ImmutableHolder<SubPostList>,
contentRenders: ImmutableList<PbContentRender>, contentRenders: ImmutableList<PbContentRender>,
threadAuthorId: Long = 0L threadAuthorId: Long = 0L,
onAgree: (SubPostList) -> Unit = {},
) { ) {
val context = LocalContext.current val context = LocalContext.current
val author = remember(subPost) { subPost.getImmutable { author } } val author = remember(subPost) { subPost.getImmutable { author } }
val hasAgreed = remember(subPost) {
subPost.get { agree?.hasAgree == 1 }
}
val agreeNum = remember(subPost) {
subPost.get { agree?.diffAgreeNum ?: 0L }
}
Card( Card(
header = { header = {
if (author.isNotNull()) { if (author.isNotNull()) {
@ -314,7 +345,13 @@ private fun SubPostItem(
onClick = { onClick = {
UserActivity.launch(context, author.get { id }.toString()) UserActivity.launch(context, author.get { id }.toString())
} }
) ) {
PostAgreeBtn(
hasAgreed = hasAgreed,
agreeNum = agreeNum,
onClick = { onAgree(subPost.get()) }
)
}
} }
}, },
content = { content = {

View File

@ -3,10 +3,12 @@ package com.huanchengfly.tieba.post.ui.page.subposts
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import com.huanchengfly.tieba.post.api.TiebaApi import com.huanchengfly.tieba.post.api.TiebaApi
import com.huanchengfly.tieba.post.api.contentRenders import com.huanchengfly.tieba.post.api.contentRenders
import com.huanchengfly.tieba.post.api.models.AgreeBean
import com.huanchengfly.tieba.post.api.models.protos.Post 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.SubPostList
import com.huanchengfly.tieba.post.api.models.protos.pbFloor.PbFloorResponse import com.huanchengfly.tieba.post.api.models.protos.pbFloor.PbFloorResponse
import com.huanchengfly.tieba.post.api.renders import com.huanchengfly.tieba.post.api.renders
import com.huanchengfly.tieba.post.api.updateAgreeStatus
import com.huanchengfly.tieba.post.arch.BaseViewModel import com.huanchengfly.tieba.post.arch.BaseViewModel
import com.huanchengfly.tieba.post.arch.ImmutableHolder import com.huanchengfly.tieba.post.arch.ImmutableHolder
import com.huanchengfly.tieba.post.arch.PartialChange import com.huanchengfly.tieba.post.arch.PartialChange
@ -50,7 +52,11 @@ class SubPostsViewModel @Inject constructor() :
override fun toPartialChangeFlow(intentFlow: Flow<SubPostsUiIntent>): Flow<SubPostsPartialChange> = override fun toPartialChangeFlow(intentFlow: Flow<SubPostsUiIntent>): Flow<SubPostsPartialChange> =
merge( merge(
intentFlow.filterIsInstance<SubPostsUiIntent.Load>() intentFlow.filterIsInstance<SubPostsUiIntent.Load>()
.flatMapConcat { it.producePartialChange() } .flatMapConcat { it.producePartialChange() },
intentFlow.filterIsInstance<SubPostsUiIntent.LoadMore>()
.flatMapConcat { it.producePartialChange() },
intentFlow.filterIsInstance<SubPostsUiIntent.Agree>()
.flatMapConcat { it.producePartialChange() },
) )
private fun SubPostsUiIntent.Load.producePartialChange(): Flow<SubPostsPartialChange.Load> = private fun SubPostsUiIntent.Load.producePartialChange(): Flow<SubPostsPartialChange.Load> =
@ -89,6 +95,20 @@ class SubPostsViewModel @Inject constructor() :
} }
.onStart { emit(SubPostsPartialChange.LoadMore.Start) } .onStart { emit(SubPostsPartialChange.LoadMore.Start) }
.catch { emit(SubPostsPartialChange.LoadMore.Failure(it)) } .catch { emit(SubPostsPartialChange.LoadMore.Failure(it)) }
private fun SubPostsUiIntent.Agree.producePartialChange(): Flow<SubPostsPartialChange.Agree> =
TiebaApi.getInstance()
.opAgreeFlow(
threadId.toString(),
(subPostId ?: postId).toString(),
if (agree) 0 else 1,
objType = if (subPostId == null) 1 else 2
)
.map<AgreeBean, SubPostsPartialChange.Agree> {
SubPostsPartialChange.Agree.Success(subPostId, agree)
}
.onStart { emit(SubPostsPartialChange.Agree.Start(subPostId, agree)) }
.catch { emit(SubPostsPartialChange.Agree.Failure(subPostId, !agree, it)) }
} }
} }
@ -109,6 +129,14 @@ sealed interface SubPostsUiIntent : UiIntent {
val page: Int = 1, val page: Int = 1,
val subPostId: Long = 0L val subPostId: Long = 0L
) : SubPostsUiIntent ) : SubPostsUiIntent
data class Agree(
val forumId: Long,
val threadId: Long,
val postId: Long,
val subPostId: Long? = null,
val agree: Boolean
) : SubPostsUiIntent
} }
sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> { sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
@ -179,6 +207,72 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
data class Failure(val throwable: Throwable) : LoadMore() data class Failure(val throwable: Throwable) : LoadMore()
} }
sealed class Agree : SubPostsPartialChange {
private fun List<ImmutableHolder<SubPostList>>.updateAgreeStatus(
subPostId: Long,
hasAgreed: Boolean
): ImmutableList<ImmutableHolder<SubPostList>> =
map {
if (it.get { id } == subPostId) {
it.getImmutable { updateAgreeStatus(if (hasAgreed) 1 else 0) }
} else {
it
}
}.toImmutableList()
override fun reduce(oldState: SubPostsUiState): SubPostsUiState =
when (this) {
is Start -> oldState.copy(
post = if (subPostId == null)
oldState.post?.getImmutable { updateAgreeStatus(if (hasAgreed) 1 else 0) }
else
oldState.post,
subPosts = if (subPostId != null)
oldState.subPosts.updateAgreeStatus(subPostId, hasAgreed)
else
oldState.subPosts,
)
is Success -> oldState.copy(
post = if (subPostId == null)
oldState.post?.getImmutable { updateAgreeStatus(if (hasAgreed) 1 else 0) }
else
oldState.post,
subPosts = if (subPostId != null)
oldState.subPosts.updateAgreeStatus(subPostId, hasAgreed)
else
oldState.subPosts,
)
is Failure -> oldState.copy(
post = if (subPostId == null)
oldState.post?.getImmutable { updateAgreeStatus(if (hasAgreed) 1 else 0) }
else
oldState.post,
subPosts = if (subPostId != null)
oldState.subPosts.updateAgreeStatus(subPostId, hasAgreed)
else
oldState.subPosts,
)
}
data class Start(
val subPostId: Long?,
val hasAgreed: Boolean
) : Agree()
data class Success(
val subPostId: Long?,
val hasAgreed: Boolean
) : Agree()
data class Failure(
val subPostId: Long?,
val hasAgreed: Boolean,
val throwable: Throwable,
) : Agree()
}
} }
data class SubPostsUiState( data class SubPostsUiState(

View File

@ -109,6 +109,7 @@ import com.huanchengfly.tieba.post.models.database.History
import com.huanchengfly.tieba.post.toJson import com.huanchengfly.tieba.post.toJson
import com.huanchengfly.tieba.post.toastShort import com.huanchengfly.tieba.post.toastShort
import com.huanchengfly.tieba.post.ui.common.PbContentRender import com.huanchengfly.tieba.post.ui.common.PbContentRender
import com.huanchengfly.tieba.post.ui.common.PbContentText
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.page.ProvideNavigator import com.huanchengfly.tieba.post.ui.page.ProvideNavigator
import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination
@ -119,7 +120,6 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.BackNavigationIcon
import com.huanchengfly.tieba.post.ui.widgets.compose.Button 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.Card
import com.huanchengfly.tieba.post.ui.widgets.compose.ConfirmDialog import com.huanchengfly.tieba.post.ui.widgets.compose.ConfirmDialog
import com.huanchengfly.tieba.post.ui.widgets.compose.EmoticonText
import com.huanchengfly.tieba.post.ui.widgets.compose.HorizontalDivider 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.LazyLoad
import com.huanchengfly.tieba.post.ui.widgets.compose.ListMenuItem import com.huanchengfly.tieba.post.ui.widgets.compose.ListMenuItem
@ -171,7 +171,7 @@ private fun getDescText(
} }
@Composable @Composable
private fun PostAgreeBtn( fun PostAgreeBtn(
hasAgreed: Boolean, hasAgreed: Boolean,
agreeNum: Long, agreeNum: Long,
onClick: () -> Unit, onClick: () -> Unit,
@ -1239,7 +1239,6 @@ fun PostCard(
onReply: () -> Unit = {}, onReply: () -> Unit = {},
onOpenSubPosts: (subPostId: Long) -> Unit = {}, onOpenSubPosts: (subPostId: Long) -> Unit = {},
) { ) {
val (post) = postHolder
if (blocked && !immersiveMode) { if (blocked && !immersiveMode) {
Column( Column(
modifier = Modifier modifier = Modifier
@ -1257,6 +1256,7 @@ fun PostCard(
} }
return return
} }
val (post) = postHolder
val hasPadding = remember(key1 = postHolder, key2 = immersiveMode) { val hasPadding = remember(key1 = postHolder, key2 = immersiveMode) {
postHolder.get { floor > 1 } && !immersiveMode postHolder.get { floor > 1 } && !immersiveMode
} }
@ -1264,16 +1264,16 @@ fun PostCard(
val context = LocalContext.current val context = LocalContext.current
val accentColor = ExtendedTheme.colors.accent val accentColor = ExtendedTheme.colors.accent
val author = postHolder.get { author!! } val author = postHolder.get { author!! }
val showTitle by remember(key1 = post.title, key2 = post.floor) { val showTitle = remember(postHolder) {
derivedStateOf { post.title.isNotBlank() && post.floor <= 1 } post.title.isNotBlank() && post.floor <= 1
} }
val hasAgreed by remember(key1 = post.agree?.hasAgree) { val hasAgreed = remember(postHolder) {
derivedStateOf { post.agree?.hasAgree == 1 } post.agree?.hasAgree == 1
} }
val agreeNum by remember(key1 = post.agree?.diffAgreeNum) { val agreeNum = remember(postHolder) {
derivedStateOf { post.agree?.diffAgreeNum ?: 0L } post.agree?.diffAgreeNum ?: 0L
} }
val subPosts = remember(key1 = post.sub_post_list) { val subPosts = remember(postHolder) {
post.sub_post_list?.sub_post_list?.toImmutableList() ?: persistentListOf() post.sub_post_list?.sub_post_list?.toImmutableList() ?: persistentListOf()
} }
val subPostContents = remember(key1 = subPosts, key2 = accentColor) { val subPostContents = remember(key1 = subPosts, key2 = accentColor) {
@ -1367,9 +1367,10 @@ fun PostCard(
.padding(vertical = 12.dp) .padding(vertical = 12.dp)
) { ) {
subPostContents.forEachIndexed { index, text -> subPostContents.forEachIndexed { index, text ->
EmoticonText( PbContentText(
text = text, text = text,
modifier = Modifier modifier = Modifier
.fillMaxWidth()
.clickable { .clickable {
onOpenSubPosts(subPosts[index].id) onOpenSubPosts(subPosts[index].id)
} }