feat: 删除自己的贴子/回复
This commit is contained in:
parent
81550e99e5
commit
c17a5a93b9
|
|
@ -21,6 +21,8 @@ import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.huanchengfly.tieba.post.utils.GsonUtil
|
import com.huanchengfly.tieba.post.utils.GsonUtil
|
||||||
import com.huanchengfly.tieba.post.utils.MD5Util
|
import com.huanchengfly.tieba.post.utils.MD5Util
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
@ -150,4 +152,8 @@ fun pendingIntentFlagImmutable(): Int {
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> ImmutableList<T>.removeAt(index: Int): ImmutableList<T> {
|
||||||
|
return this.toMutableList().apply { removeAt(index) }.toImmutableList()
|
||||||
}
|
}
|
||||||
|
|
@ -337,6 +337,9 @@ class MainActivityV2 : BaseComposeActivity() {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val navController = rememberAnimatedNavController()
|
val navController = rememberAnimatedNavController()
|
||||||
|
onGlobalEvent<GlobalEvent.NavigateUp> {
|
||||||
|
navController.navigateUp()
|
||||||
|
}
|
||||||
val bottomSheetNavigator =
|
val bottomSheetNavigator =
|
||||||
rememberBottomSheetNavigator(
|
rememberBottomSheetNavigator(
|
||||||
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
|
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,8 @@ abstract class BaseComposeActivity : BaseActivity() {
|
||||||
sealed interface CommonUiEvent : UiEvent {
|
sealed interface CommonUiEvent : UiEvent {
|
||||||
object ScrollToTop : CommonUiEvent
|
object ScrollToTop : CommonUiEvent
|
||||||
|
|
||||||
|
object NavigateUp : CommonUiEvent
|
||||||
|
|
||||||
data class Toast(
|
data class Toast(
|
||||||
val message: CharSequence,
|
val message: CharSequence,
|
||||||
val length: Int = android.widget.Toast.LENGTH_SHORT
|
val length: Int = android.widget.Toast.LENGTH_SHORT
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ import kotlinx.coroutines.launch
|
||||||
sealed interface GlobalEvent {
|
sealed interface GlobalEvent {
|
||||||
object AccountSwitched : GlobalEvent
|
object AccountSwitched : GlobalEvent
|
||||||
|
|
||||||
|
object NavigateUp : GlobalEvent
|
||||||
|
|
||||||
data class StartSelectImages(
|
data class StartSelectImages(
|
||||||
val id: String,
|
val id: String,
|
||||||
val maxCount: Int,
|
val maxCount: Int,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package com.huanchengfly.tieba.post.ui.page.subposts
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
|
@ -15,6 +14,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.DropdownMenuItem
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
|
|
@ -24,7 +24,9 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Close
|
import androidx.compose.material.icons.rounded.Close
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
|
@ -41,6 +43,7 @@ import com.huanchengfly.tieba.post.arch.ImmutableHolder
|
||||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||||
import com.huanchengfly.tieba.post.arch.onEvent
|
import com.huanchengfly.tieba.post.arch.onEvent
|
||||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||||
|
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||||
import com.huanchengfly.tieba.post.ui.common.PbContentRender
|
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
|
||||||
|
|
@ -51,17 +54,22 @@ 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
|
||||||
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.LazyLoad
|
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
|
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
|
||||||
|
import com.huanchengfly.tieba.post.ui.widgets.compose.LongClickMenu
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold
|
import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.TitleCentredToolbar
|
import com.huanchengfly.tieba.post.ui.widgets.compose.TitleCentredToolbar
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.UserHeader
|
import com.huanchengfly.tieba.post.ui.widgets.compose.UserHeader
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider
|
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider
|
||||||
|
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberDialogState
|
||||||
|
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberMenuState
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreen
|
import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreen
|
||||||
import com.huanchengfly.tieba.post.utils.AccountUtil.LocalAccount
|
import com.huanchengfly.tieba.post.utils.AccountUtil.LocalAccount
|
||||||
import com.huanchengfly.tieba.post.utils.DateTimeUtils
|
import com.huanchengfly.tieba.post.utils.DateTimeUtils
|
||||||
import com.huanchengfly.tieba.post.utils.StringUtil
|
import com.huanchengfly.tieba.post.utils.StringUtil
|
||||||
|
import com.huanchengfly.tieba.post.utils.TiebaUtil
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import com.ramcosta.composedestinations.spec.DestinationStyle
|
import com.ramcosta.composedestinations.spec.DestinationStyle
|
||||||
|
|
@ -149,6 +157,10 @@ internal fun SubPostsContent(
|
||||||
prop1 = SubPostsUiState::isLoading,
|
prop1 = SubPostsUiState::isLoading,
|
||||||
initial = false
|
initial = false
|
||||||
)
|
)
|
||||||
|
val anti by viewModel.uiState.collectPartialAsState(
|
||||||
|
prop1 = SubPostsUiState::anti,
|
||||||
|
initial = null
|
||||||
|
)
|
||||||
val forum by viewModel.uiState.collectPartialAsState(
|
val forum by viewModel.uiState.collectPartialAsState(
|
||||||
prop1 = SubPostsUiState::forum,
|
prop1 = SubPostsUiState::forum,
|
||||||
initial = null
|
initial = null
|
||||||
|
|
@ -189,6 +201,51 @@ internal fun SubPostsContent(
|
||||||
lazyListState.scrollToItem(2 + subPosts.indexOfFirst { it.get { id } == subPostId })
|
lazyListState.scrollToItem(2 + subPosts.indexOfFirst { it.get { id } == subPostId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val confirmDeleteDialogState = rememberDialogState()
|
||||||
|
var deleteSubPost by remember { mutableStateOf<ImmutableHolder<SubPostList>?>(null) }
|
||||||
|
ConfirmDialog(
|
||||||
|
dialogState = confirmDeleteDialogState,
|
||||||
|
onConfirm = {
|
||||||
|
if (deleteSubPost == null) {
|
||||||
|
val isSelfPost = post?.get { author_id } == account?.uid?.toLongOrNull()
|
||||||
|
viewModel.send(
|
||||||
|
SubPostsUiIntent.DeletePost(
|
||||||
|
forumId = forumId,
|
||||||
|
forumName = forum?.get { name }.orEmpty(),
|
||||||
|
threadId = threadId,
|
||||||
|
postId = postId,
|
||||||
|
deleteMyPost = isSelfPost,
|
||||||
|
tbs = anti?.get { tbs },
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val isSelfSubPost =
|
||||||
|
deleteSubPost!!.get { author_id } == account?.uid?.toLongOrNull()
|
||||||
|
viewModel.send(
|
||||||
|
SubPostsUiIntent.DeletePost(
|
||||||
|
forumId = forumId,
|
||||||
|
forumName = forum?.get { name }.orEmpty(),
|
||||||
|
threadId = threadId,
|
||||||
|
postId = postId,
|
||||||
|
subPostId = deleteSubPost!!.get { id },
|
||||||
|
deleteMyPost = isSelfSubPost,
|
||||||
|
tbs = anti?.get { tbs },
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = R.string.message_confirm_delete,
|
||||||
|
if (deleteSubPost == null && post != null) stringResource(
|
||||||
|
id = R.string.tip_post_floor,
|
||||||
|
post!!.get { floor })
|
||||||
|
else stringResource(id = R.string.this_reply)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
StateScreen(
|
StateScreen(
|
||||||
isEmpty = subPosts.isEmpty(),
|
isEmpty = subPosts.isEmpty(),
|
||||||
isError = false,
|
isError = false,
|
||||||
|
|
@ -293,6 +350,7 @@ internal fun SubPostsContent(
|
||||||
PostCard(
|
PostCard(
|
||||||
postHolder = it,
|
postHolder = it,
|
||||||
contentRenders = postContentRenders,
|
contentRenders = postContentRenders,
|
||||||
|
canDelete = { it.author_id == account?.uid?.toLongOrNull() },
|
||||||
showSubPosts = false,
|
showSubPosts = false,
|
||||||
onAgree = {
|
onAgree = {
|
||||||
val hasAgreed = it.get { agree?.hasAgree != 0 }
|
val hasAgreed = it.get { agree?.hasAgree != 0 }
|
||||||
|
|
@ -318,8 +376,11 @@ internal fun SubPostsContent(
|
||||||
replyUserPortrait = it.author?.portrait,
|
replyUserPortrait = it.author?.portrait,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
) {
|
||||||
|
deleteSubPost = null
|
||||||
|
confirmDeleteDialogState.show()
|
||||||
|
}
|
||||||
VerticalDivider(thickness = 2.dp)
|
VerticalDivider(thickness = 2.dp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -347,6 +408,7 @@ internal fun SubPostsContent(
|
||||||
SubPostItem(
|
SubPostItem(
|
||||||
subPost = item,
|
subPost = item,
|
||||||
contentRenders = subPostsContentRenders[index],
|
contentRenders = subPostsContentRenders[index],
|
||||||
|
canDelete = { it.author_id == account?.uid?.toLongOrNull() },
|
||||||
onAgree = {
|
onAgree = {
|
||||||
val hasAgreed = it.agree?.hasAgree != 0
|
val hasAgreed = it.agree?.hasAgree != 0
|
||||||
viewModel.send(
|
viewModel.send(
|
||||||
|
|
@ -359,7 +421,7 @@ internal fun SubPostsContent(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClickContent = {
|
onReplyClick = {
|
||||||
navigator.navigate(
|
navigator.navigate(
|
||||||
ReplyPageDestination(
|
ReplyPageDestination(
|
||||||
forumId = forumId,
|
forumId = forumId,
|
||||||
|
|
@ -373,7 +435,11 @@ internal fun SubPostsContent(
|
||||||
replyUserPortrait = it.author?.portrait,
|
replyUserPortrait = it.author?.portrait,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
onMenuDeleteClick = {
|
||||||
|
deleteSubPost = it.wrapImmutable()
|
||||||
|
confirmDeleteDialogState.show()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -402,8 +468,10 @@ private fun SubPostItem(
|
||||||
subPost: ImmutableHolder<SubPostList>,
|
subPost: ImmutableHolder<SubPostList>,
|
||||||
contentRenders: ImmutableList<PbContentRender>,
|
contentRenders: ImmutableList<PbContentRender>,
|
||||||
threadAuthorId: Long = 0L,
|
threadAuthorId: Long = 0L,
|
||||||
|
canDelete: (SubPostList) -> Boolean = { false },
|
||||||
onAgree: (SubPostList) -> Unit = {},
|
onAgree: (SubPostList) -> Unit = {},
|
||||||
onClickContent: (SubPostList) -> Unit = {}
|
onReplyClick: (SubPostList) -> Unit = {},
|
||||||
|
onMenuDeleteClick: ((SubPostList) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val author = remember(subPost) { subPost.getImmutable { author } }
|
val author = remember(subPost) { subPost.getImmutable { author } }
|
||||||
|
|
@ -413,64 +481,101 @@ private fun SubPostItem(
|
||||||
val agreeNum = remember(subPost) {
|
val agreeNum = remember(subPost) {
|
||||||
subPost.get { agree?.diffAgreeNum ?: 0L }
|
subPost.get { agree?.diffAgreeNum ?: 0L }
|
||||||
}
|
}
|
||||||
Card(
|
val menuState = rememberMenuState()
|
||||||
header = {
|
LongClickMenu(
|
||||||
if (author.isNotNull()) {
|
menuState = menuState,
|
||||||
author as ImmutableHolder<User>
|
indication = null,
|
||||||
UserHeader(
|
menuContent = {
|
||||||
avatar = {
|
DropdownMenuItem(
|
||||||
Avatar(
|
onClick = {
|
||||||
data = StringUtil.getAvatarUrl(author.get { portrait }),
|
onReplyClick(subPost.get())
|
||||||
size = Sizes.Small,
|
menuState.expanded = false
|
||||||
contentDescription = null
|
}
|
||||||
)
|
) {
|
||||||
},
|
Text(text = stringResource(id = R.string.btn_reply))
|
||||||
name = {
|
}
|
||||||
UserNameText(
|
DropdownMenuItem(
|
||||||
userName = StringUtil.getUsernameAnnotatedString(
|
onClick = {
|
||||||
LocalContext.current,
|
TiebaUtil.copyText(context, contentRenders.joinToString("\n") { it.toString() })
|
||||||
author.get { name },
|
menuState.expanded = false
|
||||||
author.get { nameShow }
|
}
|
||||||
),
|
) {
|
||||||
userLevel = author.get { level_id },
|
Text(text = stringResource(id = R.string.menu_copy))
|
||||||
isLz = author.get { id } == threadAuthorId,
|
}
|
||||||
bawuType = author.get { bawuType },
|
DropdownMenuItem(
|
||||||
)
|
onClick = {
|
||||||
},
|
TiebaUtil.reportPost(context, subPost.get { id }.toString())
|
||||||
desc = {
|
menuState.expanded = false
|
||||||
Text(
|
}
|
||||||
text = getDescText(
|
) {
|
||||||
subPost.get { time }.toLong(),
|
Text(text = stringResource(id = R.string.title_report))
|
||||||
author.get { ip_address })
|
}
|
||||||
)
|
if (canDelete(subPost.get()) && onMenuDeleteClick != null) {
|
||||||
},
|
DropdownMenuItem(
|
||||||
onClick = {
|
onClick = {
|
||||||
UserActivity.launch(context, author.get { id }.toString())
|
onMenuDeleteClick(subPost.get())
|
||||||
|
menuState.expanded = false
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
PostAgreeBtn(
|
Text(text = stringResource(id = R.string.title_delete))
|
||||||
hasAgreed = hasAgreed,
|
|
||||||
agreeNum = agreeNum,
|
|
||||||
onClick = { onAgree(subPost.get()) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content = {
|
onClick = { onReplyClick(subPost.get()) }
|
||||||
Column(
|
) {
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
Card(
|
||||||
modifier = Modifier
|
header = {
|
||||||
.padding(start = Sizes.Small + 8.dp)
|
if (author.isNotNull()) {
|
||||||
.fillMaxWidth()
|
author as ImmutableHolder<User>
|
||||||
.clickable(
|
UserHeader(
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
avatar = {
|
||||||
indication = null,
|
Avatar(
|
||||||
|
data = StringUtil.getAvatarUrl(author.get { portrait }),
|
||||||
|
size = Sizes.Small,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
name = {
|
||||||
|
UserNameText(
|
||||||
|
userName = StringUtil.getUsernameAnnotatedString(
|
||||||
|
LocalContext.current,
|
||||||
|
author.get { name },
|
||||||
|
author.get { nameShow }
|
||||||
|
),
|
||||||
|
userLevel = author.get { level_id },
|
||||||
|
isLz = author.get { id } == threadAuthorId,
|
||||||
|
bawuType = author.get { bawuType },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
desc = {
|
||||||
|
Text(
|
||||||
|
text = getDescText(
|
||||||
|
subPost.get { time }.toLong(),
|
||||||
|
author.get { ip_address })
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
UserActivity.launch(context, author.get { id }.toString())
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
onClickContent(subPost.get())
|
PostAgreeBtn(
|
||||||
|
hasAgreed = hasAgreed,
|
||||||
|
agreeNum = agreeNum,
|
||||||
|
onClick = { onAgree(subPost.get()) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) {
|
}
|
||||||
contentRenders.forEach { it.Render() }
|
},
|
||||||
|
content = {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = Sizes.Small + 8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
contentRenders.forEach { it.Render() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ 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.models.AgreeBean
|
import com.huanchengfly.tieba.post.api.models.AgreeBean
|
||||||
|
import com.huanchengfly.tieba.post.api.models.CommonResponse
|
||||||
|
import com.huanchengfly.tieba.post.api.models.protos.Anti
|
||||||
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.SimpleForum
|
import com.huanchengfly.tieba.post.api.models.protos.SimpleForum
|
||||||
import com.huanchengfly.tieba.post.api.models.protos.SubPostList
|
import com.huanchengfly.tieba.post.api.models.protos.SubPostList
|
||||||
|
|
@ -10,6 +12,8 @@ import com.huanchengfly.tieba.post.api.models.protos.contentRenders
|
||||||
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.models.protos.renders
|
import com.huanchengfly.tieba.post.api.models.protos.renders
|
||||||
import com.huanchengfly.tieba.post.api.models.protos.updateAgreeStatus
|
import com.huanchengfly.tieba.post.api.models.protos.updateAgreeStatus
|
||||||
|
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorCode
|
||||||
|
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
|
||||||
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
|
||||||
|
|
@ -18,6 +22,7 @@ import com.huanchengfly.tieba.post.arch.UiEvent
|
||||||
import com.huanchengfly.tieba.post.arch.UiIntent
|
import com.huanchengfly.tieba.post.arch.UiIntent
|
||||||
import com.huanchengfly.tieba.post.arch.UiState
|
import com.huanchengfly.tieba.post.arch.UiState
|
||||||
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||||
|
import com.huanchengfly.tieba.post.removeAt
|
||||||
import com.huanchengfly.tieba.post.ui.common.PbContentRender
|
import com.huanchengfly.tieba.post.ui.common.PbContentRender
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -58,6 +63,8 @@ class SubPostsViewModel @Inject constructor() :
|
||||||
.flatMapConcat { it.producePartialChange() },
|
.flatMapConcat { it.producePartialChange() },
|
||||||
intentFlow.filterIsInstance<SubPostsUiIntent.Agree>()
|
intentFlow.filterIsInstance<SubPostsUiIntent.Agree>()
|
||||||
.flatMapConcat { it.producePartialChange() },
|
.flatMapConcat { it.producePartialChange() },
|
||||||
|
intentFlow.filterIsInstance<SubPostsUiIntent.DeletePost>()
|
||||||
|
.flatMapConcat { it.producePartialChange() },
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun SubPostsUiIntent.Load.producePartialChange(): Flow<SubPostsPartialChange.Load> =
|
private fun SubPostsUiIntent.Load.producePartialChange(): Flow<SubPostsPartialChange.Load> =
|
||||||
|
|
@ -67,8 +74,10 @@ class SubPostsViewModel @Inject constructor() :
|
||||||
val post = checkNotNull(response.data_?.post)
|
val post = checkNotNull(response.data_?.post)
|
||||||
val page = checkNotNull(response.data_?.page)
|
val page = checkNotNull(response.data_?.page)
|
||||||
val forum = checkNotNull(response.data_?.forum)
|
val forum = checkNotNull(response.data_?.forum)
|
||||||
|
val anti = checkNotNull(response.data_?.anti)
|
||||||
val subPosts = response.data_?.subpost_list.orEmpty()
|
val subPosts = response.data_?.subpost_list.orEmpty()
|
||||||
SubPostsPartialChange.Load.Success(
|
SubPostsPartialChange.Load.Success(
|
||||||
|
anti.wrapImmutable(),
|
||||||
forum.wrapImmutable(),
|
forum.wrapImmutable(),
|
||||||
post.wrapImmutable(),
|
post.wrapImmutable(),
|
||||||
post.contentRenders,
|
post.contentRenders,
|
||||||
|
|
@ -115,6 +124,29 @@ class SubPostsViewModel @Inject constructor() :
|
||||||
}
|
}
|
||||||
.onStart { emit(SubPostsPartialChange.Agree.Start(subPostId, agree)) }
|
.onStart { emit(SubPostsPartialChange.Agree.Start(subPostId, agree)) }
|
||||||
.catch { emit(SubPostsPartialChange.Agree.Failure(subPostId, !agree, it)) }
|
.catch { emit(SubPostsPartialChange.Agree.Failure(subPostId, !agree, it)) }
|
||||||
|
|
||||||
|
fun SubPostsUiIntent.DeletePost.producePartialChange(): Flow<SubPostsPartialChange.DeletePost> =
|
||||||
|
TiebaApi.getInstance()
|
||||||
|
.delPostFlow(
|
||||||
|
forumId,
|
||||||
|
forumName,
|
||||||
|
threadId,
|
||||||
|
subPostId ?: postId,
|
||||||
|
tbs,
|
||||||
|
false,
|
||||||
|
deleteMyPost
|
||||||
|
)
|
||||||
|
.map<CommonResponse, SubPostsPartialChange.DeletePost> {
|
||||||
|
SubPostsPartialChange.DeletePost.Success(postId, subPostId)
|
||||||
|
}
|
||||||
|
.catch {
|
||||||
|
emit(
|
||||||
|
SubPostsPartialChange.DeletePost.Failure(
|
||||||
|
it.getErrorCode(),
|
||||||
|
it.getErrorMessage()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -143,6 +175,16 @@ sealed interface SubPostsUiIntent : UiIntent {
|
||||||
val subPostId: Long? = null,
|
val subPostId: Long? = null,
|
||||||
val agree: Boolean
|
val agree: Boolean
|
||||||
) : SubPostsUiIntent
|
) : SubPostsUiIntent
|
||||||
|
|
||||||
|
data class DeletePost(
|
||||||
|
val forumId: Long,
|
||||||
|
val forumName: String,
|
||||||
|
val threadId: Long,
|
||||||
|
val postId: Long,
|
||||||
|
val subPostId: Long? = null,
|
||||||
|
val deleteMyPost: Boolean,
|
||||||
|
val tbs: String? = null
|
||||||
|
) : SubPostsUiIntent
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
||||||
|
|
@ -173,6 +215,7 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
||||||
|
|
||||||
object Start : Load()
|
object Start : Load()
|
||||||
data class Success(
|
data class Success(
|
||||||
|
val anti: ImmutableHolder<Anti>,
|
||||||
val forum: ImmutableHolder<SimpleForum>,
|
val forum: ImmutableHolder<SimpleForum>,
|
||||||
val post: ImmutableHolder<Post>,
|
val post: ImmutableHolder<Post>,
|
||||||
val postContentRenders: ImmutableList<PbContentRender>,
|
val postContentRenders: ImmutableList<PbContentRender>,
|
||||||
|
|
@ -287,6 +330,37 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
||||||
val throwable: Throwable,
|
val throwable: Throwable,
|
||||||
) : Agree()
|
) : Agree()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class DeletePost : SubPostsPartialChange {
|
||||||
|
override fun reduce(oldState: SubPostsUiState): SubPostsUiState = when (this) {
|
||||||
|
is Success -> {
|
||||||
|
if (subPostId == null) {
|
||||||
|
oldState
|
||||||
|
} else {
|
||||||
|
val deletedSubPostIndex =
|
||||||
|
oldState.subPosts.indexOfFirst { it.get { id } == postId }
|
||||||
|
oldState.copy(
|
||||||
|
subPosts = oldState.subPosts.removeAt(deletedSubPostIndex),
|
||||||
|
subPostsContentRenders = oldState.subPostsContentRenders.removeAt(
|
||||||
|
deletedSubPostIndex
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is Failure -> oldState
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Success(
|
||||||
|
val postId: Long,
|
||||||
|
val subPostId: Long? = null,
|
||||||
|
) : DeletePost()
|
||||||
|
|
||||||
|
data class Failure(
|
||||||
|
val errorCode: Int,
|
||||||
|
val errorMessage: String
|
||||||
|
) : DeletePost()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SubPostsUiState(
|
data class SubPostsUiState(
|
||||||
|
|
@ -298,6 +372,7 @@ data class SubPostsUiState(
|
||||||
val totalPage: Int = 1,
|
val totalPage: Int = 1,
|
||||||
val totalCount: Int = 0,
|
val totalCount: Int = 0,
|
||||||
|
|
||||||
|
val anti: ImmutableHolder<Anti>? = null,
|
||||||
val forum: ImmutableHolder<SimpleForum>? = null,
|
val forum: ImmutableHolder<SimpleForum>? = null,
|
||||||
val post: ImmutableHolder<Post>? = null,
|
val post: ImmutableHolder<Post>? = null,
|
||||||
val postContentRenders: ImmutableList<PbContentRender> = persistentListOf(),
|
val postContentRenders: ImmutableList<PbContentRender> = persistentListOf(),
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import androidx.compose.material.icons.outlined.ChromeReaderMode
|
||||||
import androidx.compose.material.icons.rounded.AlignVerticalTop
|
import androidx.compose.material.icons.rounded.AlignVerticalTop
|
||||||
import androidx.compose.material.icons.rounded.ChromeReaderMode
|
import androidx.compose.material.icons.rounded.ChromeReaderMode
|
||||||
import androidx.compose.material.icons.rounded.ContentCopy
|
import androidx.compose.material.icons.rounded.ContentCopy
|
||||||
|
import androidx.compose.material.icons.rounded.Delete
|
||||||
import androidx.compose.material.icons.rounded.Face6
|
import androidx.compose.material.icons.rounded.Face6
|
||||||
import androidx.compose.material.icons.rounded.FaceRetouchingOff
|
import androidx.compose.material.icons.rounded.FaceRetouchingOff
|
||||||
import androidx.compose.material.icons.rounded.Favorite
|
import androidx.compose.material.icons.rounded.Favorite
|
||||||
|
|
@ -534,6 +535,8 @@ fun ThreadPage(
|
||||||
val curForumId = remember(forumId, forum) {
|
val curForumId = remember(forumId, forum) {
|
||||||
forumId ?: forum?.get { id }
|
forumId ?: forum?.get { id }
|
||||||
}
|
}
|
||||||
|
val curForumName = remember(forum) { forum?.get { name } }
|
||||||
|
val curTbs = remember(anti) { anti?.get { tbs } }
|
||||||
var waitLoadSuccessAndScrollToFirstReply by remember { mutableStateOf(scrollToReply) }
|
var waitLoadSuccessAndScrollToFirstReply by remember { mutableStateOf(scrollToReply) }
|
||||||
|
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
@ -639,6 +642,47 @@ fun ThreadPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val confirmDeleteDialogState = rememberDialogState()
|
||||||
|
var deletePost by remember { mutableStateOf<ImmutableHolder<Post>?>(null) }
|
||||||
|
ConfirmDialog(
|
||||||
|
dialogState = confirmDeleteDialogState,
|
||||||
|
onConfirm = {
|
||||||
|
curForumId ?: return@ConfirmDialog
|
||||||
|
if (deletePost == null) {
|
||||||
|
val isSelfThread = author?.get { id } == user.get { id }
|
||||||
|
viewModel.send(
|
||||||
|
ThreadUiIntent.DeleteThread(
|
||||||
|
forumId = curForumId,
|
||||||
|
forumName = curForumName.orEmpty(),
|
||||||
|
threadId = threadId,
|
||||||
|
deleteMyThread = isSelfThread,
|
||||||
|
tbs = curTbs
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val isSelfPost = deletePost!!.get { author_id } == user.get { id }
|
||||||
|
viewModel.send(
|
||||||
|
ThreadUiIntent.DeletePost(
|
||||||
|
forumId = curForumId,
|
||||||
|
forumName = curForumName.orEmpty(),
|
||||||
|
threadId = threadId,
|
||||||
|
postId = deletePost!!.get { id },
|
||||||
|
deleteMyPost = isSelfPost,
|
||||||
|
tbs = curTbs
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
id = R.string.message_confirm_delete,
|
||||||
|
if (deletePost == null) stringResource(id = R.string.this_thread)
|
||||||
|
else stringResource(id = R.string.tip_post_floor, deletePost!!.get { floor })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val jumpToPageDialogState = rememberDialogState()
|
val jumpToPageDialogState = rememberDialogState()
|
||||||
PromptDialog(
|
PromptDialog(
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
|
|
@ -766,6 +810,7 @@ fun ThreadPage(
|
||||||
isCollected = isCollected,
|
isCollected = isCollected,
|
||||||
isImmersiveMode = isImmersiveMode,
|
isImmersiveMode = isImmersiveMode,
|
||||||
isDesc = curSortType == ThreadSortType.SORT_TYPE_DESC,
|
isDesc = curSortType == ThreadSortType.SORT_TYPE_DESC,
|
||||||
|
canDelete = { author?.get { id } == user.get { id } },
|
||||||
onSeeLzClick = {
|
onSeeLzClick = {
|
||||||
viewModel.send(
|
viewModel.send(
|
||||||
ThreadUiIntent.LoadFirstPage(
|
ThreadUiIntent.LoadFirstPage(
|
||||||
|
|
@ -856,6 +901,10 @@ fun ThreadPage(
|
||||||
firstPostId.toString()
|
firstPostId.toString()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
onDeleteClick = {
|
||||||
|
deletePost = null
|
||||||
|
confirmDeleteDialogState.show()
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 16.dp)
|
.padding(vertical = 16.dp)
|
||||||
|
|
@ -916,6 +965,7 @@ fun ThreadPage(
|
||||||
PostCard(
|
PostCard(
|
||||||
postHolder = firstPost!!,
|
postHolder = firstPost!!,
|
||||||
contentRenders = firstPostContentRenders,
|
contentRenders = firstPostContentRenders,
|
||||||
|
canDelete = { it.author_id == user.get { id } },
|
||||||
immersiveMode = isImmersiveMode,
|
immersiveMode = isImmersiveMode,
|
||||||
isCollected = {
|
isCollected = {
|
||||||
it.id == thread?.get { collectMarkPid }
|
it.id == thread?.get { collectMarkPid }
|
||||||
|
|
@ -932,18 +982,6 @@ fun ThreadPage(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onMenuCopyClick = {
|
|
||||||
TiebaUtil.copyText(
|
|
||||||
context,
|
|
||||||
it.content.plainText
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onMenuReportClick = {
|
|
||||||
TiebaUtil.reportPost(
|
|
||||||
context,
|
|
||||||
it.id.toString()
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onMenuFavoriteClick = {
|
onMenuFavoriteClick = {
|
||||||
viewModel.send(
|
viewModel.send(
|
||||||
ThreadUiIntent.AddFavorite(
|
ThreadUiIntent.AddFavorite(
|
||||||
|
|
@ -953,7 +991,10 @@ fun ThreadPage(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
) {
|
||||||
|
deletePost = null
|
||||||
|
confirmDeleteDialogState.show()
|
||||||
|
}
|
||||||
|
|
||||||
VerticalDivider(
|
VerticalDivider(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -1122,12 +1163,6 @@ fun ThreadPage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMenuCopyClick = {
|
|
||||||
TiebaUtil.copyText(context, it.content.plainText)
|
|
||||||
},
|
|
||||||
onMenuReportClick = {
|
|
||||||
TiebaUtil.reportPost(context, it.id.toString())
|
|
||||||
},
|
|
||||||
onMenuFavoriteClick = {
|
onMenuFavoriteClick = {
|
||||||
val isPostCollected =
|
val isPostCollected =
|
||||||
it.id == thread?.get { collectMarkPid.toLongOrNull() }
|
it.id == thread?.get { collectMarkPid.toLongOrNull() }
|
||||||
|
|
@ -1152,8 +1187,11 @@ fun ThreadPage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
) {
|
||||||
|
deletePost = it.wrapImmutable()
|
||||||
|
confirmDeleteDialogState.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
item(key = "EmptyReplyTip") {
|
item(key = "EmptyReplyTip") {
|
||||||
|
|
@ -1378,9 +1416,7 @@ fun PostCard(
|
||||||
onAgree: () -> Unit = {},
|
onAgree: () -> Unit = {},
|
||||||
onReplyClick: (Post) -> Unit = {},
|
onReplyClick: (Post) -> Unit = {},
|
||||||
onOpenSubPosts: (subPostId: Long) -> Unit = {},
|
onOpenSubPosts: (subPostId: Long) -> Unit = {},
|
||||||
onMenuCopyClick: ((Post) -> Unit)? = null,
|
|
||||||
onMenuFavoriteClick: ((Post) -> Unit)? = null,
|
onMenuFavoriteClick: ((Post) -> Unit)? = null,
|
||||||
onMenuReportClick: ((Post) -> Unit)? = null,
|
|
||||||
onMenuDeleteClick: ((Post) -> Unit)? = null,
|
onMenuDeleteClick: ((Post) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -1438,15 +1474,21 @@ fun PostCard(
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(id = R.string.btn_reply))
|
Text(text = stringResource(id = R.string.btn_reply))
|
||||||
}
|
}
|
||||||
if (onMenuCopyClick != null) {
|
DropdownMenuItem(
|
||||||
DropdownMenuItem(
|
onClick = {
|
||||||
onClick = {
|
TiebaUtil.copyText(context, post.content.plainText)
|
||||||
onMenuCopyClick(post)
|
menuState.expanded = false
|
||||||
menuState.expanded = false
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(id = R.string.menu_copy))
|
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.menu_copy))
|
||||||
|
}
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
TiebaUtil.reportPost(context, post.id.toString())
|
||||||
|
menuState.expanded = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.title_report))
|
||||||
}
|
}
|
||||||
if (onMenuFavoriteClick != null) {
|
if (onMenuFavoriteClick != null) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
|
|
@ -1462,16 +1504,6 @@ fun PostCard(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (onMenuReportClick != null) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
onClick = {
|
|
||||||
onMenuReportClick(post)
|
|
||||||
menuState.expanded = false
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(id = R.string.title_report))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (canDelete(post) && onMenuDeleteClick != null) {
|
if (canDelete(post) && onMenuDeleteClick != null) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
@ -1660,6 +1692,7 @@ private fun ThreadMenu(
|
||||||
isCollected: Boolean,
|
isCollected: Boolean,
|
||||||
isImmersiveMode: Boolean,
|
isImmersiveMode: Boolean,
|
||||||
isDesc: Boolean,
|
isDesc: Boolean,
|
||||||
|
canDelete: () -> Boolean,
|
||||||
onSeeLzClick: () -> Unit,
|
onSeeLzClick: () -> Unit,
|
||||||
onCollectClick: () -> Unit,
|
onCollectClick: () -> Unit,
|
||||||
onImmersiveModeClick: () -> Unit,
|
onImmersiveModeClick: () -> Unit,
|
||||||
|
|
@ -1668,6 +1701,7 @@ private fun ThreadMenu(
|
||||||
onShareClick: () -> Unit,
|
onShareClick: () -> Unit,
|
||||||
onCopyLinkClick: () -> Unit,
|
onCopyLinkClick: () -> Unit,
|
||||||
onReportClick: () -> Unit,
|
onReportClick: () -> Unit,
|
||||||
|
onDeleteClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -1802,6 +1836,15 @@ private fun ThreadMenu(
|
||||||
onClick = onReportClick,
|
onClick = onReportClick,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
if (canDelete()) {
|
||||||
|
ListMenuItem(
|
||||||
|
icon = Icons.Rounded.Delete,
|
||||||
|
text = stringResource(id = R.string.title_delete),
|
||||||
|
iconColor = ExtendedTheme.colors.text,
|
||||||
|
onClick = onDeleteClick,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,11 @@ package com.huanchengfly.tieba.post.ui.page.thread
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import com.huanchengfly.tieba.post.App
|
||||||
|
import com.huanchengfly.tieba.post.R
|
||||||
import com.huanchengfly.tieba.post.api.TiebaApi
|
import com.huanchengfly.tieba.post.api.TiebaApi
|
||||||
import com.huanchengfly.tieba.post.api.models.AgreeBean
|
import com.huanchengfly.tieba.post.api.models.AgreeBean
|
||||||
|
import com.huanchengfly.tieba.post.api.models.CommonResponse
|
||||||
import com.huanchengfly.tieba.post.api.models.protos.Anti
|
import com.huanchengfly.tieba.post.api.models.protos.Anti
|
||||||
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.SimpleForum
|
import com.huanchengfly.tieba.post.api.models.protos.SimpleForum
|
||||||
|
|
@ -20,6 +23,7 @@ import com.huanchengfly.tieba.post.api.retrofit.exception.TiebaUnknownException
|
||||||
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorCode
|
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorCode
|
||||||
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
|
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
|
||||||
import com.huanchengfly.tieba.post.arch.BaseViewModel
|
import com.huanchengfly.tieba.post.arch.BaseViewModel
|
||||||
|
import com.huanchengfly.tieba.post.arch.CommonUiEvent
|
||||||
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
|
||||||
import com.huanchengfly.tieba.post.arch.PartialChangeProducer
|
import com.huanchengfly.tieba.post.arch.PartialChangeProducer
|
||||||
|
|
@ -27,6 +31,7 @@ import com.huanchengfly.tieba.post.arch.UiEvent
|
||||||
import com.huanchengfly.tieba.post.arch.UiIntent
|
import com.huanchengfly.tieba.post.arch.UiIntent
|
||||||
import com.huanchengfly.tieba.post.arch.UiState
|
import com.huanchengfly.tieba.post.arch.UiState
|
||||||
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||||
|
import com.huanchengfly.tieba.post.removeAt
|
||||||
import com.huanchengfly.tieba.post.repository.PbPageRepository
|
import com.huanchengfly.tieba.post.repository.PbPageRepository
|
||||||
import com.huanchengfly.tieba.post.ui.common.PbContentRender
|
import com.huanchengfly.tieba.post.ui.common.PbContentRender
|
||||||
import com.huanchengfly.tieba.post.utils.BlockManager.shouldBlock
|
import com.huanchengfly.tieba.post.utils.BlockManager.shouldBlock
|
||||||
|
|
@ -78,6 +83,19 @@ class ThreadViewModel @Inject constructor() :
|
||||||
|
|
||||||
ThreadPartialChange.RemoveFavorite.Success -> ThreadUiEvent.RemoveFavoriteSuccess
|
ThreadPartialChange.RemoveFavorite.Success -> ThreadUiEvent.RemoveFavoriteSuccess
|
||||||
is ThreadPartialChange.Load.Success -> ThreadUiEvent.LoadSuccess(partialChange.currentPage)
|
is ThreadPartialChange.Load.Success -> ThreadUiEvent.LoadSuccess(partialChange.currentPage)
|
||||||
|
is ThreadPartialChange.DeletePost.Success -> CommonUiEvent.Toast(
|
||||||
|
App.INSTANCE.getString(R.string.toast_delete_success)
|
||||||
|
)
|
||||||
|
|
||||||
|
is ThreadPartialChange.DeletePost.Failure -> CommonUiEvent.Toast(
|
||||||
|
App.INSTANCE.getString(R.string.toast_delete_failure, partialChange.errorMessage)
|
||||||
|
)
|
||||||
|
|
||||||
|
is ThreadPartialChange.DeleteThread.Success -> CommonUiEvent.NavigateUp
|
||||||
|
is ThreadPartialChange.DeleteThread.Failure -> CommonUiEvent.Toast(
|
||||||
|
App.INSTANCE.getString(R.string.toast_delete_failure, partialChange.errorMessage)
|
||||||
|
)
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -107,6 +125,10 @@ class ThreadViewModel @Inject constructor() :
|
||||||
.flatMapConcat { it.producePartialChange() },
|
.flatMapConcat { it.producePartialChange() },
|
||||||
intentFlow.filterIsInstance<ThreadUiIntent.AgreePost>()
|
intentFlow.filterIsInstance<ThreadUiIntent.AgreePost>()
|
||||||
.flatMapConcat { it.producePartialChange() },
|
.flatMapConcat { it.producePartialChange() },
|
||||||
|
intentFlow.filterIsInstance<ThreadUiIntent.DeletePost>()
|
||||||
|
.flatMapConcat { it.producePartialChange() },
|
||||||
|
intentFlow.filterIsInstance<ThreadUiIntent.DeleteThread>()
|
||||||
|
.flatMapConcat { it.producePartialChange() },
|
||||||
)
|
)
|
||||||
|
|
||||||
fun ThreadUiIntent.Init.producePartialChange(): Flow<ThreadPartialChange.Init> =
|
fun ThreadUiIntent.Init.producePartialChange(): Flow<ThreadPartialChange.Init> =
|
||||||
|
|
@ -379,6 +401,36 @@ class ThreadViewModel @Inject constructor() :
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ThreadUiIntent.DeletePost.producePartialChange(): Flow<ThreadPartialChange.DeletePost> =
|
||||||
|
TiebaApi.getInstance()
|
||||||
|
.delPostFlow(forumId, forumName, threadId, postId, tbs, false, deleteMyPost)
|
||||||
|
.map<CommonResponse, ThreadPartialChange.DeletePost> {
|
||||||
|
ThreadPartialChange.DeletePost.Success(postId)
|
||||||
|
}
|
||||||
|
.catch {
|
||||||
|
emit(
|
||||||
|
ThreadPartialChange.DeletePost.Failure(
|
||||||
|
it.getErrorCode(),
|
||||||
|
it.getErrorMessage()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ThreadUiIntent.DeleteThread.producePartialChange(): Flow<ThreadPartialChange.DeleteThread> =
|
||||||
|
TiebaApi.getInstance()
|
||||||
|
.delThreadFlow(forumId, forumName, threadId, tbs, deleteMyThread, false)
|
||||||
|
.map<CommonResponse, ThreadPartialChange.DeleteThread> {
|
||||||
|
ThreadPartialChange.DeleteThread.Success
|
||||||
|
}
|
||||||
|
.catch {
|
||||||
|
emit(
|
||||||
|
ThreadPartialChange.DeleteThread.Failure(
|
||||||
|
it.getErrorCode(),
|
||||||
|
it.getErrorMessage()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -456,6 +508,23 @@ sealed interface ThreadUiIntent : UiIntent {
|
||||||
val postId: Long,
|
val postId: Long,
|
||||||
val agree: Boolean
|
val agree: Boolean
|
||||||
) : ThreadUiIntent
|
) : ThreadUiIntent
|
||||||
|
|
||||||
|
data class DeletePost(
|
||||||
|
val forumId: Long,
|
||||||
|
val forumName: String,
|
||||||
|
val threadId: Long,
|
||||||
|
val postId: Long,
|
||||||
|
val deleteMyPost: Boolean,
|
||||||
|
val tbs: String? = null
|
||||||
|
) : ThreadUiIntent
|
||||||
|
|
||||||
|
data class DeleteThread(
|
||||||
|
val forumId: Long,
|
||||||
|
val forumName: String,
|
||||||
|
val threadId: Long,
|
||||||
|
val deleteMyThread: Boolean,
|
||||||
|
val tbs: String? = null
|
||||||
|
) : ThreadUiIntent
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
|
sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
|
||||||
|
|
@ -858,6 +927,41 @@ sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
|
||||||
val errorMessage: String
|
val errorMessage: String
|
||||||
) : AgreePost()
|
) : AgreePost()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class DeletePost : ThreadPartialChange {
|
||||||
|
override fun reduce(oldState: ThreadUiState): ThreadUiState = when (this) {
|
||||||
|
is Success -> {
|
||||||
|
val deletedPostIndex = oldState.data.indexOfFirst { it.post.get { id } == postId }
|
||||||
|
oldState.copy(
|
||||||
|
data = oldState.data.removeAt(deletedPostIndex),
|
||||||
|
contentRenders = oldState.contentRenders.removeAt(deletedPostIndex),
|
||||||
|
subPostContents = oldState.subPostContents.removeAt(deletedPostIndex)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Failure -> oldState
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Success(
|
||||||
|
val postId: Long
|
||||||
|
) : DeletePost()
|
||||||
|
|
||||||
|
data class Failure(
|
||||||
|
val errorCode: Int,
|
||||||
|
val errorMessage: String
|
||||||
|
) : DeletePost()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class DeleteThread : ThreadPartialChange {
|
||||||
|
override fun reduce(oldState: ThreadUiState): ThreadUiState = oldState
|
||||||
|
|
||||||
|
object Success : DeleteThread()
|
||||||
|
|
||||||
|
data class Failure(
|
||||||
|
val errorCode: Int,
|
||||||
|
val errorMessage: String
|
||||||
|
) : DeleteThread()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ThreadUiState(
|
data class ThreadUiState(
|
||||||
|
|
|
||||||
|
|
@ -699,4 +699,8 @@
|
||||||
<string name="btn_reply">回复</string>
|
<string name="btn_reply">回复</string>
|
||||||
<string name="title_collect_floor">收藏到此楼</string>
|
<string name="title_collect_floor">收藏到此楼</string>
|
||||||
<string name="title_collected_floor">已收藏到本楼</string>
|
<string name="title_collected_floor">已收藏到本楼</string>
|
||||||
|
<string name="toast_delete_failure">删除失败 %s</string>
|
||||||
|
<string name="message_confirm_delete">你确定要删除%s吗?</string>
|
||||||
|
<string name="this_thread">本贴</string>
|
||||||
|
<string name="this_reply">这条回复</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue