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.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(
|
||||||
|
|
|
||||||
|
|
@ -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,8 +345,14 @@ 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 = {
|
||||||
Column(
|
Column(
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue