fix: 动态页面贴子屏蔽

This commit is contained in:
HuanCheng65 2023-07-26 22:26:23 +08:00
parent 050d469ac7
commit ea5c39e7e4
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
3 changed files with 84 additions and 57 deletions

View File

@ -0,0 +1,17 @@
package com.huanchengfly.tieba.post.ui.models
import androidx.compose.runtime.Immutable
import com.huanchengfly.tieba.post.App
import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo
import com.huanchengfly.tieba.post.api.models.protos.personalized.ThreadPersonalized
import com.huanchengfly.tieba.post.arch.ImmutableHolder
import com.huanchengfly.tieba.post.utils.BlockManager.shouldBlock
import com.huanchengfly.tieba.post.utils.appPreferences
@Immutable
data class ThreadItemData(
val thread: ImmutableHolder<ThreadInfo>,
val blocked: Boolean = thread.get { shouldBlock() },
val personalized: ImmutableHolder<ThreadPersonalized>? = null,
val hidden: Boolean = blocked && App.INSTANCE.appPreferences.hideBlockedContent,
)

View File

@ -48,7 +48,6 @@ import androidx.compose.ui.unit.dp
import com.huanchengfly.tieba.post.R import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo
import com.huanchengfly.tieba.post.api.models.protos.personalized.DislikeReason import com.huanchengfly.tieba.post.api.models.protos.personalized.DislikeReason
import com.huanchengfly.tieba.post.api.models.protos.personalized.ThreadPersonalized
import com.huanchengfly.tieba.post.arch.BaseComposeActivity.Companion.LocalWindowSizeClass import com.huanchengfly.tieba.post.arch.BaseComposeActivity.Companion.LocalWindowSizeClass
import com.huanchengfly.tieba.post.arch.CommonUiEvent.ScrollToTop.bindScrollToTopEvent import com.huanchengfly.tieba.post.arch.CommonUiEvent.ScrollToTop.bindScrollToTopEvent
import com.huanchengfly.tieba.post.arch.GlobalEvent import com.huanchengfly.tieba.post.arch.GlobalEvent
@ -60,6 +59,7 @@ import com.huanchengfly.tieba.post.arch.pageViewModel
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.common.theme.compose.pullRefreshIndicator import com.huanchengfly.tieba.post.ui.common.theme.compose.pullRefreshIndicator
import com.huanchengfly.tieba.post.ui.common.windowsizeclass.WindowWidthSizeClass import com.huanchengfly.tieba.post.ui.common.windowsizeclass.WindowWidthSizeClass
import com.huanchengfly.tieba.post.ui.models.ThreadItemData
import com.huanchengfly.tieba.post.ui.page.LocalNavigator import com.huanchengfly.tieba.post.ui.page.LocalNavigator
import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination
import com.huanchengfly.tieba.post.ui.page.destinations.ThreadPageDestination import com.huanchengfly.tieba.post.ui.page.destinations.ThreadPageDestination
@ -98,10 +98,6 @@ fun PersonalizedPage(
prop1 = PersonalizedUiState::data, prop1 = PersonalizedUiState::data,
initial = persistentListOf() initial = persistentListOf()
) )
val threadPersonalizedData by viewModel.uiState.collectPartialAsState(
prop1 = PersonalizedUiState::threadPersonalizedData,
initial = persistentListOf()
)
val refreshPosition by viewModel.uiState.collectPartialAsState( val refreshPosition by viewModel.uiState.collectPartialAsState(
prop1 = PersonalizedUiState::refreshPosition, prop1 = PersonalizedUiState::refreshPosition,
initial = 0 initial = 0
@ -160,7 +156,6 @@ fun PersonalizedPage(
FeedList( FeedList(
state = lazyListState, state = lazyListState,
dataProvider = { data }, dataProvider = { data },
personalizedDataProvider = { threadPersonalizedData },
refreshPositionProvider = { refreshPosition }, refreshPositionProvider = { refreshPosition },
hiddenThreadIdsProvider = { hiddenThreadIds }, hiddenThreadIdsProvider = { hiddenThreadIds },
onItemClick = { onItemClick = {
@ -248,8 +243,7 @@ private fun BoxScope.RefreshTip(refreshCount: Int) {
@Composable @Composable
private fun FeedList( private fun FeedList(
state: LazyListState, state: LazyListState,
dataProvider: () -> ImmutableList<ImmutableHolder<ThreadInfo>>, dataProvider: () -> ImmutableList<ThreadItemData>,
personalizedDataProvider: () -> ImmutableList<ImmutableHolder<ThreadPersonalized>?>,
refreshPositionProvider: () -> Int, refreshPositionProvider: () -> Int,
hiddenThreadIdsProvider: () -> ImmutableList<Long>, hiddenThreadIdsProvider: () -> ImmutableList<Long>,
onItemClick: (ThreadInfo) -> Unit, onItemClick: (ThreadInfo) -> Unit,
@ -260,7 +254,6 @@ private fun FeedList(
onOpenForum: (forumName: String) -> Unit = {}, onOpenForum: (forumName: String) -> Unit = {},
) { ) {
val data = dataProvider() val data = dataProvider()
val threadPersonalizedData = personalizedDataProvider()
val refreshPosition = refreshPositionProvider() val refreshPosition = refreshPositionProvider()
val hiddenThreadIds = hiddenThreadIdsProvider() val hiddenThreadIds = hiddenThreadIdsProvider()
val windowWidthSizeClass by rememberUpdatedState(newValue = LocalWindowSizeClass.current.widthSizeClass) val windowWidthSizeClass by rememberUpdatedState(newValue = LocalWindowSizeClass.current.widthSizeClass)
@ -279,8 +272,8 @@ private fun FeedList(
) { ) {
itemsIndexed( itemsIndexed(
items = data, items = data,
key = { _, item -> "${item.get { id }}" }, key = { _, (item) -> "${item.get { id }}" },
contentType = { _, item -> contentType = { _, (item) ->
when { when {
item.get { videoInfo } != null -> "Video" item.get { videoInfo } != null -> "Video"
item.get { media }.size == 1 -> "SingleMedia" item.get { media }.size == 1 -> "SingleMedia"
@ -288,11 +281,13 @@ private fun FeedList(
else -> "PlainText" else -> "PlainText"
} }
} }
) { index, item -> ) { index, (item, blocked, personalized, hidden) ->
val isHidden = val isHidden =
remember(hiddenThreadIds, item) { hiddenThreadIds.contains(item.get { threadId }) } remember(
val personalized = hiddenThreadIds,
remember(threadPersonalizedData, index) { threadPersonalizedData.getOrNull(index) } item,
hidden
) { hiddenThreadIds.contains(item.get { threadId }) || hidden }
val isRefreshPosition = val isRefreshPosition =
remember(index, refreshPosition) { index + 1 == refreshPosition } remember(index, refreshPosition) { index + 1 == refreshPosition }
val isNotLast = remember(index, data.size) { index < data.size - 1 } val isNotLast = remember(index, data.size) { index < data.size - 1 }
@ -309,23 +304,40 @@ private fun FeedList(
enter = EnterTransition.None, enter = EnterTransition.None,
exit = shrinkVertically() + fadeOut() exit = shrinkVertically() + fadeOut()
) { ) {
FeedCard( if (!blocked) {
item = item, FeedCard(
onClick = onItemClick, item = item,
onReplyClick = onItemReplyClick, onClick = onItemClick,
onAgree = onAgree, onReplyClick = onItemReplyClick,
onClickForum = remember { onAgree = onAgree,
{ onClickForum = remember {
onOpenForum(it.name) {
onOpenForum(it.name)
}
}
) {
if (personalized != null) {
Dislike(
personalized = personalized,
onDislike = { clickTime, reasons ->
onDislike(item.get(), clickTime, reasons)
}
)
} }
} }
) { } else {
if (personalized != null) { Column(
Dislike( modifier = Modifier
personalized = personalized, .fillMaxWidth()
onDislike = { clickTime, reasons -> .padding(vertical = 8.dp, horizontal = 16.dp)
onDislike(item.get(), clickTime, reasons) .clip(RoundedCornerShape(6.dp))
} .background(ExtendedTheme.colors.card)
.padding(vertical = 8.dp, horizontal = 16.dp)
) {
Text(
text = stringResource(id = R.string.tip_blocked_thread),
style = MaterialTheme.typography.caption,
color = ExtendedTheme.colors.textSecondary
) )
} }
} }

View File

@ -8,7 +8,6 @@ import com.huanchengfly.tieba.post.api.models.CommonResponse
import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo
import com.huanchengfly.tieba.post.api.models.protos.personalized.DislikeReason import com.huanchengfly.tieba.post.api.models.protos.personalized.DislikeReason
import com.huanchengfly.tieba.post.api.models.protos.personalized.PersonalizedResponse import com.huanchengfly.tieba.post.api.models.protos.personalized.PersonalizedResponse
import com.huanchengfly.tieba.post.api.models.protos.personalized.ThreadPersonalized
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.CommonUiEvent
@ -21,6 +20,7 @@ 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.models.DislikeBean import com.huanchengfly.tieba.post.models.DislikeBean
import com.huanchengfly.tieba.post.repository.PersonalizedRepository import com.huanchengfly.tieba.post.repository.PersonalizedRepository
import com.huanchengfly.tieba.post.ui.models.ThreadItemData
import com.huanchengfly.tieba.post.utils.appPreferences import com.huanchengfly.tieba.post.utils.appPreferences
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@ -73,13 +73,14 @@ class PersonalizedViewModel @Inject constructor() :
val data = response.toData().filter { val data = response.toData().filter {
!App.INSTANCE.appPreferences.blockVideo || it.get { videoInfo } == null !App.INSTANCE.appPreferences.blockVideo || it.get { videoInfo } == null
} }
val threadPersonalizedData = data.map { thread -> val threadPersonalizedData = response.data_?.thread_personalized ?: emptyList()
response.data_?.thread_personalized?.firstOrNull { thread.get { id } == it.tid }
?.wrapImmutable()
}
PersonalizedPartialChange.Refresh.Success( PersonalizedPartialChange.Refresh.Success(
data = data, data = data.map { thread ->
threadPersonalizedData = threadPersonalizedData.toImmutableList(), val threadPersonalized =
threadPersonalizedData.firstOrNull { it.tid == thread.get { id } }
?.wrapImmutable()
ThreadItemData(thread = thread, personalized = threadPersonalized)
}.toImmutableList(),
) )
} }
.onStart { emit(PersonalizedPartialChange.Refresh.Start) } .onStart { emit(PersonalizedPartialChange.Refresh.Start) }
@ -92,14 +93,15 @@ class PersonalizedViewModel @Inject constructor() :
val data = response.toData().filter { val data = response.toData().filter {
!App.INSTANCE.appPreferences.blockVideo || it.get { videoInfo } == null !App.INSTANCE.appPreferences.blockVideo || it.get { videoInfo } == null
} }
val threadPersonalizedData = data.map { thread -> val threadPersonalizedData = response.data_?.thread_personalized ?: emptyList()
response.data_?.thread_personalized?.firstOrNull { thread.get { id } == it.tid }
?.wrapImmutable()
}
PersonalizedPartialChange.LoadMore.Success( PersonalizedPartialChange.LoadMore.Success(
currentPage = page, currentPage = page,
data = data, data = data.map { thread ->
threadPersonalizedData = threadPersonalizedData.toImmutableList(), val threadPersonalized =
threadPersonalizedData.firstOrNull { it.tid == thread.get { id } }
?.wrapImmutable()
ThreadItemData(thread = thread, personalized = threadPersonalized)
}.toImmutableList(),
) )
} }
.onStart { emit(PersonalizedPartialChange.LoadMore.Start) } .onStart { emit(PersonalizedPartialChange.LoadMore.Start) }
@ -132,7 +134,7 @@ class PersonalizedViewModel @Inject constructor() :
.catch { emit(PersonalizedPartialChange.Agree.Failure(threadId, hasAgree, it)) } .catch { emit(PersonalizedPartialChange.Agree.Failure(threadId, hasAgree, it)) }
.onStart { emit(PersonalizedPartialChange.Agree.Start(threadId, hasAgree xor 1)) } .onStart { emit(PersonalizedPartialChange.Agree.Start(threadId, hasAgree xor 1)) }
private fun PersonalizedResponse.toData(): List<ImmutableHolder<ThreadInfo>> { private fun PersonalizedResponse.toData(): ImmutableList<ImmutableHolder<ThreadInfo>> {
return (data_?.thread_list ?: emptyList()).wrapImmutable() return (data_?.thread_list ?: emptyList()).wrapImmutable()
} }
} }
@ -159,13 +161,13 @@ sealed interface PersonalizedUiIntent : UiIntent {
sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState> { sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState> {
sealed class Agree private constructor() : PersonalizedPartialChange { sealed class Agree private constructor() : PersonalizedPartialChange {
private fun List<ImmutableHolder<ThreadInfo>>.updateAgreeStatus( private fun List<ThreadItemData>.updateAgreeStatus(
threadId: Long, threadId: Long,
hasAgree: Int, hasAgree: Int,
): ImmutableList<ImmutableHolder<ThreadInfo>> { ): ImmutableList<ThreadItemData> {
return map { return map {
val threadInfo = it.get() val (threadInfo) = it.thread
if (threadInfo.threadId == threadId) { val newThreadInfo = if (threadInfo.threadId == threadId) {
if (threadInfo.agree != null) { if (threadInfo.agree != null) {
if (hasAgree != threadInfo.agree.hasAgree) { if (hasAgree != threadInfo.agree.hasAgree) {
if (hasAgree == 1) { if (hasAgree == 1) {
@ -198,7 +200,8 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
} else { } else {
threadInfo threadInfo
} }
}.wrapImmutable() it.copy(thread = newThreadInfo.wrapImmutable())
}.toImmutableList()
} }
override fun reduce(oldState: PersonalizedUiState): PersonalizedUiState = override fun reduce(oldState: PersonalizedUiState): PersonalizedUiState =
@ -273,7 +276,6 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
isRefreshing = false, isRefreshing = false,
currentPage = 1, currentPage = 1,
data = (data + oldState.data).toImmutableList(), data = (data + oldState.data).toImmutableList(),
threadPersonalizedData = (threadPersonalizedData + oldState.threadPersonalizedData).toImmutableList(),
refreshPosition = if (oldState.data.isEmpty()) 0 else data.size refreshPosition = if (oldState.data.isEmpty()) 0 else data.size
) )
is Failure -> oldState.copy(isRefreshing = false) is Failure -> oldState.copy(isRefreshing = false)
@ -282,8 +284,7 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
object Start: Refresh() object Start: Refresh()
data class Success( data class Success(
val data: List<ImmutableHolder<ThreadInfo>>, val data: List<ThreadItemData>,
val threadPersonalizedData: List<ImmutableHolder<ThreadPersonalized>?>,
) : Refresh() ) : Refresh()
data class Failure( data class Failure(
@ -299,7 +300,6 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
isLoadingMore = false, isLoadingMore = false,
currentPage = currentPage, currentPage = currentPage,
data = (oldState.data + data).toImmutableList(), data = (oldState.data + data).toImmutableList(),
threadPersonalizedData = (oldState.threadPersonalizedData + threadPersonalizedData).toImmutableList(),
) )
is Failure -> oldState.copy(isLoadingMore = false) is Failure -> oldState.copy(isLoadingMore = false)
} }
@ -308,8 +308,7 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
data class Success( data class Success(
val currentPage: Int, val currentPage: Int,
val data: List<ImmutableHolder<ThreadInfo>>, val data: List<ThreadItemData>,
val threadPersonalizedData: List<ImmutableHolder<ThreadPersonalized>?>,
) : LoadMore() ) : LoadMore()
data class Failure( data class Failure(
@ -323,8 +322,7 @@ data class PersonalizedUiState(
val isRefreshing: Boolean = true, val isRefreshing: Boolean = true,
val isLoadingMore: Boolean = false, val isLoadingMore: Boolean = false,
val currentPage: Int = 1, val currentPage: Int = 1,
val data: ImmutableList<ImmutableHolder<ThreadInfo>> = persistentListOf(), val data: ImmutableList<ThreadItemData> = persistentListOf(),
val threadPersonalizedData: ImmutableList<ImmutableHolder<ThreadPersonalized>?> = persistentListOf(),
val hiddenThreadIds: ImmutableList<Long> = persistentListOf(), val hiddenThreadIds: ImmutableList<Long> = persistentListOf(),
val refreshPosition: Int = 0, val refreshPosition: Int = 0,
): UiState ): UiState