From 2cf960e88a0291dfd9ce81404d79cbb9a1896987 Mon Sep 17 00:00:00 2001 From: HuanCheng65 <22636177+HuanCheng65@users.noreply.github.com> Date: Mon, 17 Jul 2023 12:02:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A5=BC=E4=B8=AD=E6=A5=BC=E7=82=B9?= =?UTF-8?q?=E8=B5=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/huanchengfly/tieba/post/api/Utils.kt | 29 ++++++ .../post/ui/page/subposts/SubPostsPage.kt | 45 ++++++++- .../ui/page/subposts/SubPostsViewModel.kt | 96 ++++++++++++++++++- .../tieba/post/ui/page/thread/ThreadPage.kt | 23 ++--- 4 files changed, 177 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/api/Utils.kt b/app/src/main/java/com/huanchengfly/tieba/post/api/Utils.kt index ad9e80b0..57a088e9 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/api/Utils.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/api/Utils.kt @@ -13,6 +13,7 @@ import com.huanchengfly.tieba.post.App.ScreenInfo import com.huanchengfly.tieba.post.R 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.SubPostList import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo import com.huanchengfly.tieba.post.arch.wrapImmutable import com.huanchengfly.tieba.post.ui.common.PbContentRender @@ -131,6 +132,34 @@ fun Post.updateAgreeStatus( 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 get() = ImageUtil.getUrl( 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 186e5659..faac5a42 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 @@ -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.page.LocalNavigator 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.UserNameText import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar @@ -221,7 +222,18 @@ internal fun SubPostsContent( PostCard( postHolder = it, 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) } @@ -249,7 +261,19 @@ internal fun SubPostsContent( ) { index, item -> SubPostItem( 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( subPost: ImmutableHolder, contentRenders: ImmutableList, - threadAuthorId: Long = 0L + threadAuthorId: Long = 0L, + onAgree: (SubPostList) -> Unit = {}, ) { val context = LocalContext.current 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( header = { if (author.isNotNull()) { @@ -314,7 +345,13 @@ private fun SubPostItem( onClick = { UserActivity.launch(context, author.get { id }.toString()) } - ) + ) { + PostAgreeBtn( + hasAgreed = hasAgreed, + agreeNum = agreeNum, + onClick = { onAgree(subPost.get()) } + ) + } } }, content = { diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsViewModel.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsViewModel.kt index 0d5f17d0..745671f3 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsViewModel.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsViewModel.kt @@ -3,10 +3,12 @@ package com.huanchengfly.tieba.post.ui.page.subposts import androidx.compose.runtime.Stable import com.huanchengfly.tieba.post.api.TiebaApi 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.SubPostList import com.huanchengfly.tieba.post.api.models.protos.pbFloor.PbFloorResponse 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.ImmutableHolder import com.huanchengfly.tieba.post.arch.PartialChange @@ -50,7 +52,11 @@ class SubPostsViewModel @Inject constructor() : override fun toPartialChangeFlow(intentFlow: Flow): Flow = merge( intentFlow.filterIsInstance() - .flatMapConcat { it.producePartialChange() } + .flatMapConcat { it.producePartialChange() }, + intentFlow.filterIsInstance() + .flatMapConcat { it.producePartialChange() }, + intentFlow.filterIsInstance() + .flatMapConcat { it.producePartialChange() }, ) private fun SubPostsUiIntent.Load.producePartialChange(): Flow = @@ -89,6 +95,20 @@ class SubPostsViewModel @Inject constructor() : } .onStart { emit(SubPostsPartialChange.LoadMore.Start) } .catch { emit(SubPostsPartialChange.LoadMore.Failure(it)) } + + private fun SubPostsUiIntent.Agree.producePartialChange(): Flow = + TiebaApi.getInstance() + .opAgreeFlow( + threadId.toString(), + (subPostId ?: postId).toString(), + if (agree) 0 else 1, + objType = if (subPostId == null) 1 else 2 + ) + .map { + 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 subPostId: Long = 0L ) : 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 { @@ -179,6 +207,72 @@ sealed interface SubPostsPartialChange : PartialChange { data class Failure(val throwable: Throwable) : LoadMore() } + + sealed class Agree : SubPostsPartialChange { + private fun List>.updateAgreeStatus( + subPostId: Long, + hasAgreed: Boolean + ): ImmutableList> = + 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( 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 33a94f91..6a9c1949 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 @@ -109,6 +109,7 @@ import com.huanchengfly.tieba.post.models.database.History import com.huanchengfly.tieba.post.toJson import com.huanchengfly.tieba.post.toastShort 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.page.ProvideNavigator 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.Card 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.LazyLoad import com.huanchengfly.tieba.post.ui.widgets.compose.ListMenuItem @@ -171,7 +171,7 @@ private fun getDescText( } @Composable -private fun PostAgreeBtn( +fun PostAgreeBtn( hasAgreed: Boolean, agreeNum: Long, onClick: () -> Unit, @@ -1239,7 +1239,6 @@ fun PostCard( onReply: () -> Unit = {}, onOpenSubPosts: (subPostId: Long) -> Unit = {}, ) { - val (post) = postHolder if (blocked && !immersiveMode) { Column( modifier = Modifier @@ -1257,6 +1256,7 @@ fun PostCard( } return } + val (post) = postHolder val hasPadding = remember(key1 = postHolder, key2 = immersiveMode) { postHolder.get { floor > 1 } && !immersiveMode } @@ -1264,16 +1264,16 @@ fun PostCard( val context = LocalContext.current val accentColor = ExtendedTheme.colors.accent val author = postHolder.get { author!! } - val showTitle by remember(key1 = post.title, key2 = post.floor) { - derivedStateOf { post.title.isNotBlank() && post.floor <= 1 } + val showTitle = remember(postHolder) { + post.title.isNotBlank() && post.floor <= 1 } - val hasAgreed by remember(key1 = post.agree?.hasAgree) { - derivedStateOf { post.agree?.hasAgree == 1 } + val hasAgreed = remember(postHolder) { + post.agree?.hasAgree == 1 } - val agreeNum by remember(key1 = post.agree?.diffAgreeNum) { - derivedStateOf { post.agree?.diffAgreeNum ?: 0L } + val agreeNum = remember(postHolder) { + 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() } val subPostContents = remember(key1 = subPosts, key2 = accentColor) { @@ -1367,9 +1367,10 @@ fun PostCard( .padding(vertical = 12.dp) ) { subPostContents.forEachIndexed { index, text -> - EmoticonText( + PbContentText( text = text, modifier = Modifier + .fillMaxWidth() .clickable { onOpenSubPosts(subPosts[index].id) }