diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/models/ThreadItemData.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/models/ThreadItemData.kt new file mode 100644 index 00000000..868878c6 --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/models/ThreadItemData.kt @@ -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, + val blocked: Boolean = thread.get { shouldBlock() }, + val personalized: ImmutableHolder? = null, + val hidden: Boolean = blocked && App.INSTANCE.appPreferences.hideBlockedContent, +) \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedPage.kt index 7d1af141..096a052c 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedPage.kt @@ -48,7 +48,6 @@ import androidx.compose.ui.unit.dp import com.huanchengfly.tieba.post.R 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.ThreadPersonalized import com.huanchengfly.tieba.post.arch.BaseComposeActivity.Companion.LocalWindowSizeClass import com.huanchengfly.tieba.post.arch.CommonUiEvent.ScrollToTop.bindScrollToTopEvent 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.pullRefreshIndicator 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.destinations.ForumPageDestination import com.huanchengfly.tieba.post.ui.page.destinations.ThreadPageDestination @@ -98,10 +98,6 @@ fun PersonalizedPage( prop1 = PersonalizedUiState::data, initial = persistentListOf() ) - val threadPersonalizedData by viewModel.uiState.collectPartialAsState( - prop1 = PersonalizedUiState::threadPersonalizedData, - initial = persistentListOf() - ) val refreshPosition by viewModel.uiState.collectPartialAsState( prop1 = PersonalizedUiState::refreshPosition, initial = 0 @@ -160,7 +156,6 @@ fun PersonalizedPage( FeedList( state = lazyListState, dataProvider = { data }, - personalizedDataProvider = { threadPersonalizedData }, refreshPositionProvider = { refreshPosition }, hiddenThreadIdsProvider = { hiddenThreadIds }, onItemClick = { @@ -248,8 +243,7 @@ private fun BoxScope.RefreshTip(refreshCount: Int) { @Composable private fun FeedList( state: LazyListState, - dataProvider: () -> ImmutableList>, - personalizedDataProvider: () -> ImmutableList?>, + dataProvider: () -> ImmutableList, refreshPositionProvider: () -> Int, hiddenThreadIdsProvider: () -> ImmutableList, onItemClick: (ThreadInfo) -> Unit, @@ -260,7 +254,6 @@ private fun FeedList( onOpenForum: (forumName: String) -> Unit = {}, ) { val data = dataProvider() - val threadPersonalizedData = personalizedDataProvider() val refreshPosition = refreshPositionProvider() val hiddenThreadIds = hiddenThreadIdsProvider() val windowWidthSizeClass by rememberUpdatedState(newValue = LocalWindowSizeClass.current.widthSizeClass) @@ -279,8 +272,8 @@ private fun FeedList( ) { itemsIndexed( items = data, - key = { _, item -> "${item.get { id }}" }, - contentType = { _, item -> + key = { _, (item) -> "${item.get { id }}" }, + contentType = { _, (item) -> when { item.get { videoInfo } != null -> "Video" item.get { media }.size == 1 -> "SingleMedia" @@ -288,11 +281,13 @@ private fun FeedList( else -> "PlainText" } } - ) { index, item -> + ) { index, (item, blocked, personalized, hidden) -> val isHidden = - remember(hiddenThreadIds, item) { hiddenThreadIds.contains(item.get { threadId }) } - val personalized = - remember(threadPersonalizedData, index) { threadPersonalizedData.getOrNull(index) } + remember( + hiddenThreadIds, + item, + hidden + ) { hiddenThreadIds.contains(item.get { threadId }) || hidden } val isRefreshPosition = remember(index, refreshPosition) { index + 1 == refreshPosition } val isNotLast = remember(index, data.size) { index < data.size - 1 } @@ -309,23 +304,40 @@ private fun FeedList( enter = EnterTransition.None, exit = shrinkVertically() + fadeOut() ) { - FeedCard( - item = item, - onClick = onItemClick, - onReplyClick = onItemReplyClick, - onAgree = onAgree, - onClickForum = remember { - { - onOpenForum(it.name) + if (!blocked) { + FeedCard( + item = item, + onClick = onItemClick, + onReplyClick = onItemReplyClick, + onAgree = onAgree, + onClickForum = remember { + { + onOpenForum(it.name) + } + } + ) { + if (personalized != null) { + Dislike( + personalized = personalized, + onDislike = { clickTime, reasons -> + onDislike(item.get(), clickTime, reasons) + } + ) } } - ) { - if (personalized != null) { - Dislike( - personalized = personalized, - onDislike = { clickTime, reasons -> - onDislike(item.get(), clickTime, reasons) - } + } else { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp, horizontal = 16.dp) + .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 ) } } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedViewModel.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedViewModel.kt index c8ff5665..8c1aea42 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedViewModel.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedViewModel.kt @@ -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.personalized.DislikeReason 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.arch.BaseViewModel 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.models.DislikeBean import com.huanchengfly.tieba.post.repository.PersonalizedRepository +import com.huanchengfly.tieba.post.ui.models.ThreadItemData import com.huanchengfly.tieba.post.utils.appPreferences import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList @@ -73,13 +73,14 @@ class PersonalizedViewModel @Inject constructor() : val data = response.toData().filter { !App.INSTANCE.appPreferences.blockVideo || it.get { videoInfo } == null } - val threadPersonalizedData = data.map { thread -> - response.data_?.thread_personalized?.firstOrNull { thread.get { id } == it.tid } - ?.wrapImmutable() - } + val threadPersonalizedData = response.data_?.thread_personalized ?: emptyList() PersonalizedPartialChange.Refresh.Success( - data = data, - threadPersonalizedData = threadPersonalizedData.toImmutableList(), + data = data.map { thread -> + val threadPersonalized = + threadPersonalizedData.firstOrNull { it.tid == thread.get { id } } + ?.wrapImmutable() + ThreadItemData(thread = thread, personalized = threadPersonalized) + }.toImmutableList(), ) } .onStart { emit(PersonalizedPartialChange.Refresh.Start) } @@ -92,14 +93,15 @@ class PersonalizedViewModel @Inject constructor() : val data = response.toData().filter { !App.INSTANCE.appPreferences.blockVideo || it.get { videoInfo } == null } - val threadPersonalizedData = data.map { thread -> - response.data_?.thread_personalized?.firstOrNull { thread.get { id } == it.tid } - ?.wrapImmutable() - } + val threadPersonalizedData = response.data_?.thread_personalized ?: emptyList() PersonalizedPartialChange.LoadMore.Success( currentPage = page, - data = data, - threadPersonalizedData = threadPersonalizedData.toImmutableList(), + data = data.map { thread -> + val threadPersonalized = + threadPersonalizedData.firstOrNull { it.tid == thread.get { id } } + ?.wrapImmutable() + ThreadItemData(thread = thread, personalized = threadPersonalized) + }.toImmutableList(), ) } .onStart { emit(PersonalizedPartialChange.LoadMore.Start) } @@ -132,7 +134,7 @@ class PersonalizedViewModel @Inject constructor() : .catch { emit(PersonalizedPartialChange.Agree.Failure(threadId, hasAgree, it)) } .onStart { emit(PersonalizedPartialChange.Agree.Start(threadId, hasAgree xor 1)) } - private fun PersonalizedResponse.toData(): List> { + private fun PersonalizedResponse.toData(): ImmutableList> { return (data_?.thread_list ?: emptyList()).wrapImmutable() } } @@ -159,13 +161,13 @@ sealed interface PersonalizedUiIntent : UiIntent { sealed interface PersonalizedPartialChange : PartialChange { sealed class Agree private constructor() : PersonalizedPartialChange { - private fun List>.updateAgreeStatus( + private fun List.updateAgreeStatus( threadId: Long, hasAgree: Int, - ): ImmutableList> { + ): ImmutableList { return map { - val threadInfo = it.get() - if (threadInfo.threadId == threadId) { + val (threadInfo) = it.thread + val newThreadInfo = if (threadInfo.threadId == threadId) { if (threadInfo.agree != null) { if (hasAgree != threadInfo.agree.hasAgree) { if (hasAgree == 1) { @@ -198,7 +200,8 @@ sealed interface PersonalizedPartialChange : PartialChange } else { threadInfo } - }.wrapImmutable() + it.copy(thread = newThreadInfo.wrapImmutable()) + }.toImmutableList() } override fun reduce(oldState: PersonalizedUiState): PersonalizedUiState = @@ -273,7 +276,6 @@ sealed interface PersonalizedPartialChange : PartialChange isRefreshing = false, currentPage = 1, data = (data + oldState.data).toImmutableList(), - threadPersonalizedData = (threadPersonalizedData + oldState.threadPersonalizedData).toImmutableList(), refreshPosition = if (oldState.data.isEmpty()) 0 else data.size ) is Failure -> oldState.copy(isRefreshing = false) @@ -282,8 +284,7 @@ sealed interface PersonalizedPartialChange : PartialChange object Start: Refresh() data class Success( - val data: List>, - val threadPersonalizedData: List?>, + val data: List, ) : Refresh() data class Failure( @@ -299,7 +300,6 @@ sealed interface PersonalizedPartialChange : PartialChange isLoadingMore = false, currentPage = currentPage, data = (oldState.data + data).toImmutableList(), - threadPersonalizedData = (oldState.threadPersonalizedData + threadPersonalizedData).toImmutableList(), ) is Failure -> oldState.copy(isLoadingMore = false) } @@ -308,8 +308,7 @@ sealed interface PersonalizedPartialChange : PartialChange data class Success( val currentPage: Int, - val data: List>, - val threadPersonalizedData: List?>, + val data: List, ) : LoadMore() data class Failure( @@ -323,8 +322,7 @@ data class PersonalizedUiState( val isRefreshing: Boolean = true, val isLoadingMore: Boolean = false, val currentPage: Int = 1, - val data: ImmutableList> = persistentListOf(), - val threadPersonalizedData: ImmutableList?> = persistentListOf(), + val data: ImmutableList = persistentListOf(), val hiddenThreadIds: ImmutableList = persistentListOf(), val refreshPosition: Int = 0, ): UiState