feat: 楼中楼点赞
This commit is contained in:
parent
f14258a6a9
commit
2cf960e88a
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,7 +345,13 @@ private fun SubPostItem(
|
|||
onClick = {
|
||||
UserActivity.launch(context, author.get { id }.toString())
|
||||
}
|
||||
)
|
||||
) {
|
||||
PostAgreeBtn(
|
||||
hasAgreed = hasAgreed,
|
||||
agreeNum = agreeNum,
|
||||
onClick = { onAgree(subPost.get()) }
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
content = {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue