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.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(

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.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<SubPostList>,
contentRenders: ImmutableList<PbContentRender>,
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,8 +345,14 @@ private fun SubPostItem(
onClick = {
UserActivity.launch(context, author.get { id }.toString())
}
) {
PostAgreeBtn(
hasAgreed = hasAgreed,
agreeNum = agreeNum,
onClick = { onAgree(subPost.get()) }
)
}
}
},
content = {
Column(

View File

@ -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<SubPostsUiIntent>): Flow<SubPostsPartialChange> =
merge(
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> =
@ -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<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 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<SubPostsUiState> {
@ -179,6 +207,72 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
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(

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.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)
}