feat: 消息列表屏蔽
This commit is contained in:
parent
478f275ceb
commit
6527155925
|
|
@ -341,7 +341,6 @@ val Post.subPosts: ImmutableList<SubPostItemData>
|
|||
?.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))
|
||||
|
|
|
|||
|
|
@ -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,24 +102,37 @@ fun NotificationsListPage(
|
|||
) {
|
||||
items(
|
||||
items = data,
|
||||
key = { "${it.postId}_${it.replyer?.id}_${it.time}" },
|
||||
key = { "${it.info.postId}_${it.info.replyer?.id}_${it.info.time}" },
|
||||
) {
|
||||
val (info, blocked) = it
|
||||
BlockableContent(
|
||||
blocked = blocked,
|
||||
blockedTip = {
|
||||
BlockTip {
|
||||
Text(
|
||||
text = stringResource(id = R.string.tip_blocked_message)
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
if (it.isFloor == "1") {
|
||||
if (info.isFloor == "1") {
|
||||
navigator.navigate(
|
||||
SubPostsPageDestination(
|
||||
threadId = it.threadId!!.toLong(),
|
||||
subPostId = it.postId!!.toLong(),
|
||||
threadId = info.threadId!!.toLong(),
|
||||
subPostId = info.postId!!.toLong(),
|
||||
loadFromSubPost = true
|
||||
)
|
||||
)
|
||||
} else {
|
||||
navigator.navigate(
|
||||
ThreadPageDestination(
|
||||
threadId = it.threadId!!.toLong(),
|
||||
postId = it.postId!!.toLong()
|
||||
threadId = info.threadId!!.toLong(),
|
||||
postId = info.postId!!.toLong()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -124,46 +140,49 @@ fun NotificationsListPage(
|
|||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (it.replyer != null) {
|
||||
if (info.replyer != null) {
|
||||
UserHeader(
|
||||
avatar = {
|
||||
Avatar(
|
||||
data = StringUtil.getAvatarUrl(it.replyer.portrait),
|
||||
data = StringUtil.getAvatarUrl(info.replyer.portrait),
|
||||
size = Sizes.Small,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
name = {
|
||||
Text(
|
||||
text = it.replyer.nameShow ?: it.replyer.name ?: ""
|
||||
text = info.replyer.nameShow ?: info.replyer.name ?: ""
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
UserActivity.launch(
|
||||
context,
|
||||
it.replyer.id!!,
|
||||
StringUtil.getAvatarUrl(it.replyer.portrait)
|
||||
info.replyer.id!!,
|
||||
StringUtil.getAvatarUrl(info.replyer.portrait)
|
||||
)
|
||||
},
|
||||
desc = {
|
||||
Text(
|
||||
text = DateTimeUtils.getRelativeTimeString(
|
||||
LocalContext.current,
|
||||
it.time!!
|
||||
info.time!!
|
||||
)
|
||||
)
|
||||
},
|
||||
) {}
|
||||
}
|
||||
EmoticonText(text = it.content ?: "")
|
||||
EmoticonText(text = info.content ?: "")
|
||||
val quoteText = if (type == NotificationsType.ReplyMe) {
|
||||
if ("1" == it.isFloor) {
|
||||
it.quoteContent
|
||||
if ("1" == info.isFloor) {
|
||||
info.quoteContent
|
||||
} else {
|
||||
stringResource(id = R.string.text_message_list_item_reply_my_thread, it.title ?: "")
|
||||
stringResource(
|
||||
id = R.string.text_message_list_item_reply_my_thread,
|
||||
info.title ?: ""
|
||||
)
|
||||
}
|
||||
} else {
|
||||
it.title
|
||||
info.title
|
||||
}
|
||||
if (quoteText != null) {
|
||||
EmoticonText(
|
||||
|
|
@ -172,23 +191,26 @@ fun NotificationsListPage(
|
|||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.clickable {
|
||||
if ("1" == it.isFloor && it.quotePid != null) {
|
||||
if ("1" == info.isFloor && info.quotePid != null) {
|
||||
navigator.navigate(
|
||||
SubPostsPageDestination(
|
||||
threadId = it.threadId!!.toLong(),
|
||||
postId = it.quotePid.toLong(),
|
||||
threadId = info.threadId!!.toLong(),
|
||||
postId = info.quotePid.toLong(),
|
||||
loadFromSubPost = true,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
navigator.navigate(
|
||||
ThreadPageDestination(
|
||||
threadId = it.threadId!!.toLong(),
|
||||
threadId = info.threadId!!.toLong(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.background(ExtendedTheme.colors.chip, RoundedCornerShape(6.dp))
|
||||
.background(
|
||||
ExtendedTheme.colors.chip,
|
||||
RoundedCornerShape(6.dp)
|
||||
)
|
||||
.padding(8.dp),
|
||||
color = ExtendedTheme.colors.onChip,
|
||||
fontSize = 12.sp,
|
||||
|
|
@ -198,6 +220,7 @@ fun NotificationsListPage(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = isRefreshing,
|
||||
|
|
|
|||
|
|
@ -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<MessageListBean, NotificationsListPartialChange.Refresh> {
|
||||
val data = (if (type == NotificationsType.ReplyMe) it.replyList else it.atList)!!
|
||||
NotificationsListPartialChange.Refresh.Success(data = data, hasMore = it.page?.hasMore == "1")
|
||||
}).map<MessageListBean, NotificationsListPartialChange.Refresh> { 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<MessageListBean, NotificationsListPartialChange.LoadMore> {
|
||||
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, NotificationsListPartialChange.LoadMore> { 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<NotificationsLis
|
|||
override fun reduce(oldState: NotificationsListUiState): NotificationsListUiState =
|
||||
when (this) {
|
||||
Start -> 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<MessageListBean.MessageInfoBean>,
|
||||
val data: List<MessageItemData>,
|
||||
val hasMore: Boolean,
|
||||
) : Refresh()
|
||||
|
||||
|
|
@ -110,17 +149,17 @@ sealed interface NotificationsListPartialChange : PartialChange<NotificationsLis
|
|||
is Success -> 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<MessageListBean.MessageInfoBean>,
|
||||
val data: List<MessageItemData>,
|
||||
val hasMore: Boolean,
|
||||
) : LoadMore()
|
||||
|
||||
|
|
@ -131,12 +170,18 @@ sealed interface NotificationsListPartialChange : PartialChange<NotificationsLis
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class MessageItemData(
|
||||
val info: MessageListBean.MessageInfoBean,
|
||||
val blocked: Boolean = info.shouldBlock(),
|
||||
)
|
||||
|
||||
data class NotificationsListUiState(
|
||||
val isRefreshing: Boolean = true,
|
||||
val isLoadingMore: Boolean = false,
|
||||
val currentPage: Int = 1,
|
||||
val hasMore: Boolean = true,
|
||||
val data: List<MessageListBean.MessageInfoBean> = emptyList(),
|
||||
val data: ImmutableList<MessageItemData> = persistentListOf(),
|
||||
) : UiState
|
||||
|
||||
sealed interface NotificationsListUiEvent : UiEvent
|
||||
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
|
@ -709,6 +709,7 @@
|
|||
<string name="btn_open_origin_thread">查看原贴</string>
|
||||
<string name="tip_blocked_thread">由于你的屏蔽设置,该贴已被屏蔽</string>
|
||||
<string name="tip_blocked_content">由于你的屏蔽设置,该内容已被屏蔽</string>
|
||||
<string name="tip_blocked_message">由于你的屏蔽设置,该消息已被屏蔽</string>
|
||||
<string name="desc_picture">图片</string>
|
||||
<string name="tip_thread_store_deleted">该贴已被删除</string>
|
||||
<string name="button_expand">展开</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue