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.huanchengfly.tieba.post.utils.GsonUtil
|
||||
import com.huanchengfly.tieba.post.utils.MD5Util
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.io.File
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
|
@ -151,3 +153,7 @@ fun pendingIntentFlagImmutable(): Int {
|
|||
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()
|
||||
onGlobalEvent<GlobalEvent.NavigateUp> {
|
||||
navController.navigateUp()
|
||||
}
|
||||
val bottomSheetNavigator =
|
||||
rememberBottomSheetNavigator(
|
||||
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ abstract class BaseComposeActivity : BaseActivity() {
|
|||
sealed interface CommonUiEvent : UiEvent {
|
||||
object ScrollToTop : CommonUiEvent
|
||||
|
||||
object NavigateUp : CommonUiEvent
|
||||
|
||||
data class Toast(
|
||||
val message: CharSequence,
|
||||
val length: Int = android.widget.Toast.LENGTH_SHORT
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import kotlinx.coroutines.launch
|
|||
sealed interface GlobalEvent {
|
||||
object AccountSwitched : GlobalEvent
|
||||
|
||||
object NavigateUp : GlobalEvent
|
||||
|
||||
data class StartSelectImages(
|
||||
val id: String,
|
||||
val maxCount: Int,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.huanchengfly.tieba.post.ui.page.subposts
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.onEvent
|
||||
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.theme.compose.ExtendedTheme
|
||||
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.widgets.compose.Avatar
|
||||
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.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.Sizes
|
||||
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.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.utils.AccountUtil.LocalAccount
|
||||
import com.huanchengfly.tieba.post.utils.DateTimeUtils
|
||||
import com.huanchengfly.tieba.post.utils.StringUtil
|
||||
import com.huanchengfly.tieba.post.utils.TiebaUtil
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.ramcosta.composedestinations.spec.DestinationStyle
|
||||
|
|
@ -149,6 +157,10 @@ internal fun SubPostsContent(
|
|||
prop1 = SubPostsUiState::isLoading,
|
||||
initial = false
|
||||
)
|
||||
val anti by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SubPostsUiState::anti,
|
||||
initial = null
|
||||
)
|
||||
val forum by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SubPostsUiState::forum,
|
||||
initial = null
|
||||
|
|
@ -189,6 +201,51 @@ internal fun SubPostsContent(
|
|||
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(
|
||||
isEmpty = subPosts.isEmpty(),
|
||||
isError = false,
|
||||
|
|
@ -293,6 +350,7 @@ internal fun SubPostsContent(
|
|||
PostCard(
|
||||
postHolder = it,
|
||||
contentRenders = postContentRenders,
|
||||
canDelete = { it.author_id == account?.uid?.toLongOrNull() },
|
||||
showSubPosts = false,
|
||||
onAgree = {
|
||||
val hasAgreed = it.get { agree?.hasAgree != 0 }
|
||||
|
|
@ -318,8 +376,11 @@ internal fun SubPostsContent(
|
|||
replyUserPortrait = it.author?.portrait,
|
||||
)
|
||||
)
|
||||
},
|
||||
) {
|
||||
deleteSubPost = null
|
||||
confirmDeleteDialogState.show()
|
||||
}
|
||||
)
|
||||
VerticalDivider(thickness = 2.dp)
|
||||
}
|
||||
}
|
||||
|
|
@ -347,6 +408,7 @@ internal fun SubPostsContent(
|
|||
SubPostItem(
|
||||
subPost = item,
|
||||
contentRenders = subPostsContentRenders[index],
|
||||
canDelete = { it.author_id == account?.uid?.toLongOrNull() },
|
||||
onAgree = {
|
||||
val hasAgreed = it.agree?.hasAgree != 0
|
||||
viewModel.send(
|
||||
|
|
@ -359,7 +421,7 @@ internal fun SubPostsContent(
|
|||
)
|
||||
)
|
||||
},
|
||||
onClickContent = {
|
||||
onReplyClick = {
|
||||
navigator.navigate(
|
||||
ReplyPageDestination(
|
||||
forumId = forumId,
|
||||
|
|
@ -373,7 +435,11 @@ internal fun SubPostsContent(
|
|||
replyUserPortrait = it.author?.portrait,
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
onMenuDeleteClick = {
|
||||
deleteSubPost = it.wrapImmutable()
|
||||
confirmDeleteDialogState.show()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -402,8 +468,10 @@ private fun SubPostItem(
|
|||
subPost: ImmutableHolder<SubPostList>,
|
||||
contentRenders: ImmutableList<PbContentRender>,
|
||||
threadAuthorId: Long = 0L,
|
||||
canDelete: (SubPostList) -> Boolean = { false },
|
||||
onAgree: (SubPostList) -> Unit = {},
|
||||
onClickContent: (SubPostList) -> Unit = {}
|
||||
onReplyClick: (SubPostList) -> Unit = {},
|
||||
onMenuDeleteClick: ((SubPostList) -> Unit)? = null,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val author = remember(subPost) { subPost.getImmutable { author } }
|
||||
|
|
@ -413,6 +481,48 @@ private fun SubPostItem(
|
|||
val agreeNum = remember(subPost) {
|
||||
subPost.get { agree?.diffAgreeNum ?: 0L }
|
||||
}
|
||||
val menuState = rememberMenuState()
|
||||
LongClickMenu(
|
||||
menuState = menuState,
|
||||
indication = null,
|
||||
menuContent = {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onReplyClick(subPost.get())
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.btn_reply))
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
TiebaUtil.copyText(context, contentRenders.joinToString("\n") { it.toString() })
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.menu_copy))
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
TiebaUtil.reportPost(context, subPost.get { id }.toString())
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.title_report))
|
||||
}
|
||||
if (canDelete(subPost.get()) && onMenuDeleteClick != null) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onMenuDeleteClick(subPost.get())
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.title_delete))
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = { onReplyClick(subPost.get()) }
|
||||
) {
|
||||
Card(
|
||||
header = {
|
||||
if (author.isNotNull()) {
|
||||
|
|
@ -462,15 +572,10 @@ private fun SubPostItem(
|
|||
modifier = Modifier
|
||||
.padding(start = Sizes.Small + 8.dp)
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
) {
|
||||
onClickContent(subPost.get())
|
||||
}
|
||||
) {
|
||||
contentRenders.forEach { it.Render() }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ 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.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.SimpleForum
|
||||
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.renders
|
||||
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.ImmutableHolder
|
||||
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.UiState
|
||||
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||
import com.huanchengfly.tieba.post.removeAt
|
||||
import com.huanchengfly.tieba.post.ui.common.PbContentRender
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -58,6 +63,8 @@ class SubPostsViewModel @Inject constructor() :
|
|||
.flatMapConcat { it.producePartialChange() },
|
||||
intentFlow.filterIsInstance<SubPostsUiIntent.Agree>()
|
||||
.flatMapConcat { it.producePartialChange() },
|
||||
intentFlow.filterIsInstance<SubPostsUiIntent.DeletePost>()
|
||||
.flatMapConcat { it.producePartialChange() },
|
||||
)
|
||||
|
||||
private fun SubPostsUiIntent.Load.producePartialChange(): Flow<SubPostsPartialChange.Load> =
|
||||
|
|
@ -67,8 +74,10 @@ class SubPostsViewModel @Inject constructor() :
|
|||
val post = checkNotNull(response.data_?.post)
|
||||
val page = checkNotNull(response.data_?.page)
|
||||
val forum = checkNotNull(response.data_?.forum)
|
||||
val anti = checkNotNull(response.data_?.anti)
|
||||
val subPosts = response.data_?.subpost_list.orEmpty()
|
||||
SubPostsPartialChange.Load.Success(
|
||||
anti.wrapImmutable(),
|
||||
forum.wrapImmutable(),
|
||||
post.wrapImmutable(),
|
||||
post.contentRenders,
|
||||
|
|
@ -115,6 +124,29 @@ class SubPostsViewModel @Inject constructor() :
|
|||
}
|
||||
.onStart { emit(SubPostsPartialChange.Agree.Start(subPostId, agree)) }
|
||||
.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 agree: Boolean
|
||||
) : 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> {
|
||||
|
|
@ -173,6 +215,7 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
|||
|
||||
object Start : Load()
|
||||
data class Success(
|
||||
val anti: ImmutableHolder<Anti>,
|
||||
val forum: ImmutableHolder<SimpleForum>,
|
||||
val post: ImmutableHolder<Post>,
|
||||
val postContentRenders: ImmutableList<PbContentRender>,
|
||||
|
|
@ -287,6 +330,37 @@ sealed interface SubPostsPartialChange : PartialChange<SubPostsUiState> {
|
|||
val throwable: Throwable,
|
||||
) : 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(
|
||||
|
|
@ -298,6 +372,7 @@ data class SubPostsUiState(
|
|||
val totalPage: Int = 1,
|
||||
val totalCount: Int = 0,
|
||||
|
||||
val anti: ImmutableHolder<Anti>? = null,
|
||||
val forum: ImmutableHolder<SimpleForum>? = null,
|
||||
val post: ImmutableHolder<Post>? = null,
|
||||
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.ChromeReaderMode
|
||||
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.FaceRetouchingOff
|
||||
import androidx.compose.material.icons.rounded.Favorite
|
||||
|
|
@ -534,6 +535,8 @@ fun ThreadPage(
|
|||
val curForumId = remember(forumId, forum) {
|
||||
forumId ?: forum?.get { id }
|
||||
}
|
||||
val curForumName = remember(forum) { forum?.get { name } }
|
||||
val curTbs = remember(anti) { anti?.get { tbs } }
|
||||
var waitLoadSuccessAndScrollToFirstReply by remember { mutableStateOf(scrollToReply) }
|
||||
|
||||
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()
|
||||
PromptDialog(
|
||||
onConfirm = {
|
||||
|
|
@ -766,6 +810,7 @@ fun ThreadPage(
|
|||
isCollected = isCollected,
|
||||
isImmersiveMode = isImmersiveMode,
|
||||
isDesc = curSortType == ThreadSortType.SORT_TYPE_DESC,
|
||||
canDelete = { author?.get { id } == user.get { id } },
|
||||
onSeeLzClick = {
|
||||
viewModel.send(
|
||||
ThreadUiIntent.LoadFirstPage(
|
||||
|
|
@ -856,6 +901,10 @@ fun ThreadPage(
|
|||
firstPostId.toString()
|
||||
)
|
||||
},
|
||||
onDeleteClick = {
|
||||
deletePost = null
|
||||
confirmDeleteDialogState.show()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp)
|
||||
|
|
@ -916,6 +965,7 @@ fun ThreadPage(
|
|||
PostCard(
|
||||
postHolder = firstPost!!,
|
||||
contentRenders = firstPostContentRenders,
|
||||
canDelete = { it.author_id == user.get { id } },
|
||||
immersiveMode = isImmersiveMode,
|
||||
isCollected = {
|
||||
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 = {
|
||||
viewModel.send(
|
||||
ThreadUiIntent.AddFavorite(
|
||||
|
|
@ -953,7 +991,10 @@ fun ThreadPage(
|
|||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
) {
|
||||
deletePost = null
|
||||
confirmDeleteDialogState.show()
|
||||
}
|
||||
|
||||
VerticalDivider(
|
||||
modifier = Modifier
|
||||
|
|
@ -1122,12 +1163,6 @@ fun ThreadPage(
|
|||
)
|
||||
}
|
||||
},
|
||||
onMenuCopyClick = {
|
||||
TiebaUtil.copyText(context, it.content.plainText)
|
||||
},
|
||||
onMenuReportClick = {
|
||||
TiebaUtil.reportPost(context, it.id.toString())
|
||||
},
|
||||
onMenuFavoriteClick = {
|
||||
val isPostCollected =
|
||||
it.id == thread?.get { collectMarkPid.toLongOrNull() }
|
||||
|
|
@ -1152,8 +1187,11 @@ fun ThreadPage(
|
|||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
deletePost = it.wrapImmutable()
|
||||
confirmDeleteDialogState.show()
|
||||
}
|
||||
)
|
||||
}
|
||||
if (data.isEmpty()) {
|
||||
item(key = "EmptyReplyTip") {
|
||||
|
|
@ -1378,9 +1416,7 @@ fun PostCard(
|
|||
onAgree: () -> Unit = {},
|
||||
onReplyClick: (Post) -> Unit = {},
|
||||
onOpenSubPosts: (subPostId: Long) -> Unit = {},
|
||||
onMenuCopyClick: ((Post) -> Unit)? = null,
|
||||
onMenuFavoriteClick: ((Post) -> Unit)? = null,
|
||||
onMenuReportClick: ((Post) -> Unit)? = null,
|
||||
onMenuDeleteClick: ((Post) -> Unit)? = null,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
|
@ -1438,15 +1474,21 @@ fun PostCard(
|
|||
) {
|
||||
Text(text = stringResource(id = R.string.btn_reply))
|
||||
}
|
||||
if (onMenuCopyClick != null) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onMenuCopyClick(post)
|
||||
TiebaUtil.copyText(context, post.content.plainText)
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
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) {
|
||||
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) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
|
|
@ -1660,6 +1692,7 @@ private fun ThreadMenu(
|
|||
isCollected: Boolean,
|
||||
isImmersiveMode: Boolean,
|
||||
isDesc: Boolean,
|
||||
canDelete: () -> Boolean,
|
||||
onSeeLzClick: () -> Unit,
|
||||
onCollectClick: () -> Unit,
|
||||
onImmersiveModeClick: () -> Unit,
|
||||
|
|
@ -1668,6 +1701,7 @@ private fun ThreadMenu(
|
|||
onShareClick: () -> Unit,
|
||||
onCopyLinkClick: () -> Unit,
|
||||
onReportClick: () -> Unit,
|
||||
onDeleteClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -1802,6 +1836,15 @@ private fun ThreadMenu(
|
|||
onClick = onReportClick,
|
||||
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.Stable
|
||||
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.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.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.getErrorMessage
|
||||
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.PartialChange
|
||||
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.UiState
|
||||
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.ui.common.PbContentRender
|
||||
import com.huanchengfly.tieba.post.utils.BlockManager.shouldBlock
|
||||
|
|
@ -78,6 +83,19 @@ class ThreadViewModel @Inject constructor() :
|
|||
|
||||
ThreadPartialChange.RemoveFavorite.Success -> ThreadUiEvent.RemoveFavoriteSuccess
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -107,6 +125,10 @@ class ThreadViewModel @Inject constructor() :
|
|||
.flatMapConcat { it.producePartialChange() },
|
||||
intentFlow.filterIsInstance<ThreadUiIntent.AgreePost>()
|
||||
.flatMapConcat { it.producePartialChange() },
|
||||
intentFlow.filterIsInstance<ThreadUiIntent.DeletePost>()
|
||||
.flatMapConcat { it.producePartialChange() },
|
||||
intentFlow.filterIsInstance<ThreadUiIntent.DeleteThread>()
|
||||
.flatMapConcat { it.producePartialChange() },
|
||||
)
|
||||
|
||||
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 agree: Boolean
|
||||
) : 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> {
|
||||
|
|
@ -858,6 +927,41 @@ sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
|
|||
val errorMessage: String
|
||||
) : 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(
|
||||
|
|
|
|||
|
|
@ -699,4 +699,8 @@
|
|||
<string name="btn_reply">回复</string>
|
||||
<string name="title_collect_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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue