diff --git a/app/src/main/java/com/huanchengfly/tieba/post/api/models/protos/Extensions.kt b/app/src/main/java/com/huanchengfly/tieba/post/api/models/protos/Extensions.kt index 3681bf5d..78d2feba 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/api/models/protos/Extensions.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/api/models/protos/Extensions.kt @@ -341,7 +341,6 @@ val Post.subPosts: ImmutableList ?.toImmutableList() ?: persistentListOf() -@OptIn(ExperimentalTextApi::class) fun SubPostList.getContentText(threadAuthorId: Long? = null): AnnotatedString { val context = App.INSTANCE val accentColor = Color(ThemeUtils.getColorByAttr(context, R.attr.colorNewPrimary)) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/notifications/list/NotificationsListPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/notifications/list/NotificationsListPage.kt index 9a80fbc6..5680fbf0 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/notifications/list/NotificationsListPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/notifications/list/NotificationsListPage.kt @@ -36,6 +36,8 @@ import com.huanchengfly.tieba.post.ui.page.LocalNavigator import com.huanchengfly.tieba.post.ui.page.destinations.SubPostsPageDestination import com.huanchengfly.tieba.post.ui.page.destinations.ThreadPageDestination import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar +import com.huanchengfly.tieba.post.ui.widgets.compose.BlockTip +import com.huanchengfly.tieba.post.ui.widgets.compose.BlockableContent import com.huanchengfly.tieba.post.ui.widgets.compose.EmoticonText import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout @@ -43,6 +45,7 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes import com.huanchengfly.tieba.post.ui.widgets.compose.UserHeader import com.huanchengfly.tieba.post.utils.DateTimeUtils import com.huanchengfly.tieba.post.utils.StringUtil +import kotlinx.collections.immutable.persistentListOf @OptIn(ExperimentalMaterialApi::class) @Composable @@ -73,7 +76,7 @@ fun NotificationsListPage( ) val data by viewModel.uiState.collectPartialAsState( prop1 = NotificationsListUiState::data, - initial = emptyList() + initial = persistentListOf() ) val currentPage by viewModel.uiState.collectPartialAsState( prop1 = NotificationsListUiState::currentPage, @@ -99,100 +102,120 @@ fun NotificationsListPage( ) { items( items = data, - key = { "${it.postId}_${it.replyer?.id}_${it.time}" }, + key = { "${it.info.postId}_${it.info.replyer?.id}_${it.info.time}" }, ) { - Column( + val (info, blocked) = it + BlockableContent( + blocked = blocked, + blockedTip = { + BlockTip { + Text( + text = stringResource(id = R.string.tip_blocked_message) + ) + } + }, modifier = Modifier - .clickable { - if (it.isFloor == "1") { - navigator.navigate( - SubPostsPageDestination( - threadId = it.threadId!!.toLong(), - subPostId = it.postId!!.toLong(), - loadFromSubPost = true + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + Column( + modifier = Modifier + .clickable { + if (info.isFloor == "1") { + navigator.navigate( + SubPostsPageDestination( + threadId = info.threadId!!.toLong(), + subPostId = info.postId!!.toLong(), + loadFromSubPost = true + ) ) - ) + } else { + navigator.navigate( + ThreadPageDestination( + threadId = info.threadId!!.toLong(), + postId = info.postId!!.toLong() + ) + ) + } + } + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + if (info.replyer != null) { + UserHeader( + avatar = { + Avatar( + data = StringUtil.getAvatarUrl(info.replyer.portrait), + size = Sizes.Small, + contentDescription = null + ) + }, + name = { + Text( + text = info.replyer.nameShow ?: info.replyer.name ?: "" + ) + }, + onClick = { + UserActivity.launch( + context, + info.replyer.id!!, + StringUtil.getAvatarUrl(info.replyer.portrait) + ) + }, + desc = { + Text( + text = DateTimeUtils.getRelativeTimeString( + LocalContext.current, + info.time!! + ) + ) + }, + ) {} + } + EmoticonText(text = info.content ?: "") + val quoteText = if (type == NotificationsType.ReplyMe) { + if ("1" == info.isFloor) { + info.quoteContent } else { - navigator.navigate( - ThreadPageDestination( - threadId = it.threadId!!.toLong(), - postId = it.postId!!.toLong() - ) + stringResource( + id = R.string.text_message_list_item_reply_my_thread, + info.title ?: "" ) } - } - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - if (it.replyer != null) { - UserHeader( - avatar = { - Avatar( - data = StringUtil.getAvatarUrl(it.replyer.portrait), - size = Sizes.Small, - contentDescription = null - ) - }, - name = { - Text( - text = it.replyer.nameShow ?: it.replyer.name ?: "" - ) - }, - onClick = { - UserActivity.launch( - context, - it.replyer.id!!, - StringUtil.getAvatarUrl(it.replyer.portrait) - ) - }, - desc = { - Text( - text = DateTimeUtils.getRelativeTimeString( - LocalContext.current, - it.time!! - ) - ) - }, - ) {} - } - EmoticonText(text = it.content ?: "") - val quoteText = if (type == NotificationsType.ReplyMe) { - if ("1" == it.isFloor) { - it.quoteContent } else { - stringResource(id = R.string.text_message_list_item_reply_my_thread, it.title ?: "") + info.title } - } else { - it.title - } - if (quoteText != null) { - EmoticonText( - text = quoteText, - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(6.dp)) - .clickable { - if ("1" == it.isFloor && it.quotePid != null) { - navigator.navigate( - SubPostsPageDestination( - threadId = it.threadId!!.toLong(), - postId = it.quotePid.toLong(), - loadFromSubPost = true, + if (quoteText != null) { + EmoticonText( + text = quoteText, + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(6.dp)) + .clickable { + if ("1" == info.isFloor && info.quotePid != null) { + navigator.navigate( + SubPostsPageDestination( + threadId = info.threadId!!.toLong(), + postId = info.quotePid.toLong(), + loadFromSubPost = true, + ) ) - ) - } else { - navigator.navigate( - ThreadPageDestination( - threadId = it.threadId!!.toLong(), + } else { + navigator.navigate( + ThreadPageDestination( + threadId = info.threadId!!.toLong(), + ) ) - ) + } } - } - .background(ExtendedTheme.colors.chip, RoundedCornerShape(6.dp)) - .padding(8.dp), - color = ExtendedTheme.colors.onChip, - fontSize = 12.sp, - ) + .background( + ExtendedTheme.colors.chip, + RoundedCornerShape(6.dp) + ) + .padding(8.dp), + color = ExtendedTheme.colors.onChip, + fontSize = 12.sp, + ) + } } } } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/notifications/list/NotificationsListViewModel.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/notifications/list/NotificationsListViewModel.kt index 352bcb1f..b046bf88 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/notifications/list/NotificationsListViewModel.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/notifications/list/NotificationsListViewModel.kt @@ -1,13 +1,31 @@ package com.huanchengfly.tieba.post.ui.page.main.notifications.list +import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable +import androidx.compose.ui.util.fastMap import com.huanchengfly.tieba.post.api.TiebaApi import com.huanchengfly.tieba.post.api.models.MessageListBean import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage -import com.huanchengfly.tieba.post.arch.* +import com.huanchengfly.tieba.post.arch.BaseViewModel +import com.huanchengfly.tieba.post.arch.CommonUiEvent +import com.huanchengfly.tieba.post.arch.PartialChange +import com.huanchengfly.tieba.post.arch.PartialChangeProducer +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.utils.BlockManager.shouldBlock import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart import javax.inject.Inject abstract class NotificationsListViewModel : @@ -53,9 +71,16 @@ private class NotificationsListPartialChangeProducer(private val type: Notificat (when (type) { NotificationsType.ReplyMe -> TiebaApi.getInstance().replyMeFlow() NotificationsType.AtMe -> TiebaApi.getInstance().atMeFlow() - }).map { - val data = (if (type == NotificationsType.ReplyMe) it.replyList else it.atList)!! - NotificationsListPartialChange.Refresh.Success(data = data, hasMore = it.page?.hasMore == "1") + }).map { messageListBean -> + val data = + ((if (type == NotificationsType.ReplyMe) messageListBean.replyList else messageListBean.atList) + ?: emptyList()).fastMap { + MessageItemData(it) + } + NotificationsListPartialChange.Refresh.Success( + data = data, + hasMore = messageListBean.page?.hasMore == "1" + ) } .onStart { emit(NotificationsListPartialChange.Refresh.Start) } .catch { emit(NotificationsListPartialChange.Refresh.Failure(it)) } @@ -64,9 +89,17 @@ private class NotificationsListPartialChangeProducer(private val type: Notificat (when (type) { NotificationsType.ReplyMe -> TiebaApi.getInstance().replyMeFlow(page = page) NotificationsType.AtMe -> TiebaApi.getInstance().atMeFlow(page = page) - }).map { - val data = (if (type == NotificationsType.ReplyMe) it.replyList else it.atList)!! - NotificationsListPartialChange.LoadMore.Success(currentPage = page, data = data, hasMore = it.page?.hasMore == "1") + }).map { messageListBean -> + val data = + ((if (type == NotificationsType.ReplyMe) messageListBean.replyList else messageListBean.atList) + ?: emptyList()).fastMap { + MessageItemData(it) + } + NotificationsListPartialChange.LoadMore.Success( + currentPage = page, + data = data, + hasMore = messageListBean.page?.hasMore == "1" + ) } .onStart { emit(NotificationsListPartialChange.LoadMore.Start) } .catch { emit(NotificationsListPartialChange.LoadMore.Failure(currentPage = page, error = it)) } @@ -77,7 +110,7 @@ enum class NotificationsType { } sealed interface NotificationsListUiIntent : UiIntent { - object Refresh : NotificationsListUiIntent + data object Refresh : NotificationsListUiIntent data class LoadMore(val page: Int) : NotificationsListUiIntent } @@ -87,14 +120,20 @@ sealed interface NotificationsListPartialChange : PartialChange oldState.copy(isRefreshing = true) - is Success -> oldState.copy(isRefreshing = false, currentPage = 1, data = data, hasMore = hasMore) + is Success -> oldState.copy( + isRefreshing = false, + currentPage = 1, + data = data.toImmutableList(), + hasMore = hasMore + ) + is Failure -> oldState.copy(isRefreshing = false) } - object Start: Refresh() + data object Start : Refresh() data class Success( - val data: List, + val data: List, val hasMore: Boolean, ) : Refresh() @@ -110,17 +149,17 @@ sealed interface NotificationsListPartialChange : PartialChange oldState.copy( isLoadingMore = false, currentPage = currentPage, - data = oldState.data + data, + data = (oldState.data + data).toImmutableList(), hasMore = hasMore ) is Failure -> oldState.copy(isLoadingMore = false) } - object Start: LoadMore() + data object Start : LoadMore() data class Success( val currentPage: Int, - val data: List, + val data: List, val hasMore: Boolean, ) : LoadMore() @@ -131,12 +170,18 @@ sealed interface NotificationsListPartialChange : PartialChange = emptyList(), + val data: ImmutableList = persistentListOf(), ) : UiState sealed interface NotificationsListUiEvent : UiEvent \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/BlockManager.kt b/app/src/main/java/com/huanchengfly/tieba/post/utils/BlockManager.kt index 70b35c99..dc107022 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/BlockManager.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/BlockManager.kt @@ -1,5 +1,6 @@ package com.huanchengfly.tieba.post.utils +import com.huanchengfly.tieba.post.api.models.MessageListBean import com.huanchengfly.tieba.post.api.models.protos.Post import com.huanchengfly.tieba.post.api.models.protos.SubPostList import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo @@ -64,4 +65,10 @@ object BlockManager { fun SubPostList.shouldBlock(): Boolean = shouldBlock(content.plainText) || shouldBlock(author_id, author?.name) + + fun MessageListBean.MessageInfoBean.shouldBlock(): Boolean = + shouldBlock(content.orEmpty()) || shouldBlock( + this.replyer?.id?.toLongOrNull() ?: -1, + this.replyer?.name.orEmpty() + ) } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aff1415c..d42e5c8a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -709,6 +709,7 @@ 查看原贴 由于你的屏蔽设置,该贴已被屏蔽 由于你的屏蔽设置,该内容已被屏蔽 + 由于你的屏蔽设置,该消息已被屏蔽 图片 该贴已被删除 展开