pref: 楼中楼内容计算在 ViewModel 进行

This commit is contained in:
HuanCheng65 2023-07-21 15:15:52 +08:00
parent 333b50a090
commit 7139bb134b
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
4 changed files with 106 additions and 86 deletions

View File

@ -2,9 +2,11 @@ package com.huanchengfly.tieba.post.api.models.protos
import androidx.compose.foundation.text.appendInlineContent import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withAnnotation import androidx.compose.ui.text.withAnnotation
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
import com.huanchengfly.tieba.post.App import com.huanchengfly.tieba.post.App
@ -20,7 +22,9 @@ import com.huanchengfly.tieba.post.ui.utils.getPhotoViewData
import com.huanchengfly.tieba.post.utils.EmoticonManager import com.huanchengfly.tieba.post.utils.EmoticonManager
import com.huanchengfly.tieba.post.utils.EmoticonUtil.emoticonString import com.huanchengfly.tieba.post.utils.EmoticonUtil.emoticonString
import com.huanchengfly.tieba.post.utils.ImageUtil import com.huanchengfly.tieba.post.utils.ImageUtil
import com.huanchengfly.tieba.post.utils.StringUtil
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
val ThreadInfo.abstractText: String val ThreadInfo.abstractText: String
@ -325,3 +329,38 @@ val User.bawuType: String?
get() = if (is_bawu == 1) { get() = if (is_bawu == 1) {
if (bawu_type == "manager") "吧主" else "小吧主" if (bawu_type == "manager") "吧主" else "小吧主"
} else null } else null
val Post.subPostContents: ImmutableList<AnnotatedString>
get() = sub_post_list?.sub_post_list?.map { it.contentText }?.toImmutableList()
?: persistentListOf()
@OptIn(ExperimentalTextApi::class)
val SubPostList.contentText: AnnotatedString
get() {
val context = App.INSTANCE
val accentColor = Color(ThemeUtils.getColorByAttr(context, R.attr.colorNewAccent))
val userNameString = buildAnnotatedString {
withStyle(
style = SpanStyle(
color = accentColor,
fontWeight = FontWeight.Bold
)
) {
withAnnotation("user", "${author?.id}") {
append(
StringUtil.getUsernameAnnotatedString(
context,
author?.name ?: "",
author?.nameShow
)
)
append(": ")
}
}
}
val contentStrings = content.renders.map { it.toAnnotationString() }
return userNameString + contentStrings.reduce { acc, annotatedString -> acc + annotatedString }
}

View File

@ -1,9 +1,8 @@
package com.huanchengfly.tieba.post.repository package com.huanchengfly.tieba.post.repository
import com.huanchengfly.tieba.post.api.TiebaApi import com.huanchengfly.tieba.post.api.TiebaApi
import com.huanchengfly.tieba.post.api.models.CommonResponse
import com.huanchengfly.tieba.post.api.models.protos.pbPage.PbPageResponse import com.huanchengfly.tieba.post.api.models.protos.pbPage.PbPageResponse
import com.huanchengfly.tieba.post.api.retrofit.exception.TiebaApiException import com.huanchengfly.tieba.post.api.retrofit.exception.TiebaUnknownException
import com.huanchengfly.tieba.post.ui.page.thread.ThreadPageFrom import com.huanchengfly.tieba.post.ui.page.thread.ThreadPageFrom
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -38,7 +37,7 @@ object PbPageRepository {
|| response.data_.forum == null || response.data_.forum == null
|| response.data_.anti == null || response.data_.anti == null
) { ) {
throw TiebaApiException(CommonResponse(-1, "未知错误")) throw TiebaUnknownException
} }
val userList = response.data_.user_list val userList = response.data_.user_list
val postList = response.data_.post_list.map { val postList = response.data_.post_list.map {
@ -62,6 +61,14 @@ object PbPageRepository {
author = response.data_.thread.author, author = response.data_.thread.author,
from_forum = response.data_.forum, from_forum = response.data_.forum,
tid = response.data_.thread.id, tid = response.data_.thread.id,
sub_post_list = response.data_.first_floor_post.sub_post_list?.copy(
sub_post_list = response.data_.first_floor_post.sub_post_list.sub_post_list.map { subPost ->
subPost.copy(
author = subPost.author
?: userList.first { user -> user.id == subPost.author_id }
)
}
)
) )
response.copy( response.copy(

View File

@ -76,14 +76,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withAnnotation
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.airbnb.lottie.compose.LottieAnimation import com.airbnb.lottie.compose.LottieAnimation
@ -100,7 +96,6 @@ import com.huanchengfly.tieba.post.api.models.protos.SimpleForum
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.User import com.huanchengfly.tieba.post.api.models.protos.User
import com.huanchengfly.tieba.post.api.models.protos.bawuType import com.huanchengfly.tieba.post.api.models.protos.bawuType
import com.huanchengfly.tieba.post.api.models.protos.renders
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
import com.huanchengfly.tieba.post.arch.ImmutableHolder import com.huanchengfly.tieba.post.arch.ImmutableHolder
import com.huanchengfly.tieba.post.arch.collectPartialAsState import com.huanchengfly.tieba.post.arch.collectPartialAsState
@ -435,6 +430,10 @@ fun ThreadPage(
prop1 = ThreadUiState::contentRenders, prop1 = ThreadUiState::contentRenders,
initial = persistentListOf() initial = persistentListOf()
) )
val subPostContents by viewModel.uiState.collectPartialAsState(
prop1 = ThreadUiState::subPostContents,
initial = persistentListOf()
)
val author by viewModel.uiState.collectPartialAsState( val author by viewModel.uiState.collectPartialAsState(
prop1 = ThreadUiState::author, prop1 = ThreadUiState::author,
initial = null initial = null
@ -1035,6 +1034,7 @@ fun ThreadPage(
PostCard( PostCard(
postHolder = item, postHolder = item,
contentRenders = contentRenders[index], contentRenders = contentRenders[index],
subPostContents = subPostContents[index],
threadAuthorId = author?.get { id } ?: 0L, threadAuthorId = author?.get { id } ?: 0L,
blocked = blocked, blocked = blocked,
immersiveMode = isImmersiveMode, immersiveMode = isImmersiveMode,
@ -1285,11 +1285,11 @@ private fun BottomBar(
} }
} }
@OptIn(ExperimentalTextApi::class)
@Composable @Composable
fun PostCard( fun PostCard(
postHolder: ImmutableHolder<Post>, postHolder: ImmutableHolder<Post>,
contentRenders: ImmutableList<PbContentRender>, contentRenders: ImmutableList<PbContentRender>,
subPostContents: ImmutableList<AnnotatedString> = persistentListOf(),
threadAuthorId: Long = 0L, threadAuthorId: Long = 0L,
blocked: Boolean = false, blocked: Boolean = false,
immersiveMode: Boolean = false, immersiveMode: Boolean = false,
@ -1324,7 +1324,6 @@ fun PostCard(
postHolder.get { floor > 1 } && !immersiveMode postHolder.get { floor > 1 } && !immersiveMode
} }
val paddingModifier = Modifier.padding(start = if (hasPadding) Sizes.Small + 8.dp else 0.dp) val paddingModifier = Modifier.padding(start = if (hasPadding) Sizes.Small + 8.dp else 0.dp)
val accentColor = ExtendedTheme.colors.accent
val author = postHolder.get { author!! } val author = postHolder.get { author!! }
val showTitle = remember(postHolder) { val showTitle = remember(postHolder) {
post.title.isNotBlank() && post.floor <= 1 post.title.isNotBlank() && post.floor <= 1
@ -1338,32 +1337,6 @@ fun PostCard(
val subPosts = remember(postHolder) { val subPosts = remember(postHolder) {
post.sub_post_list?.sub_post_list?.toImmutableList() ?: persistentListOf() post.sub_post_list?.sub_post_list?.toImmutableList() ?: persistentListOf()
} }
val subPostContents = remember(key1 = subPosts, key2 = accentColor) {
subPosts.map { subPostList ->
val userNameString = buildAnnotatedString {
withStyle(
style = SpanStyle(
color = accentColor,
fontWeight = FontWeight.Bold
)
) {
withAnnotation("user", "${subPostList.author?.id}") {
append(
StringUtil.getUsernameAnnotatedString(
context,
subPostList.author?.name ?: "",
subPostList.author?.nameShow
)
)
append(": ")
}
}
}
val contentStrings = subPostList.content.renders.map { it.toAnnotationString() }
userNameString + contentStrings.reduce { acc, annotatedString -> acc + annotatedString }
}
}
Card( Card(
header = { header = {
if (!immersiveMode) { if (!immersiveMode) {
@ -1429,7 +1402,7 @@ fun PostCard(
} }
} }
if (showSubPosts && post.sub_post_number > 0 && !immersiveMode) { if (showSubPosts && post.sub_post_number > 0 && subPostContents.isNotEmpty() && !immersiveMode) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -1513,7 +1486,7 @@ fun UserNameText(
"Bawu" to buildChipInlineContent( "Bawu" to buildChipInlineContent(
bawuType ?: "", bawuType ?: "",
color = ExtendedTheme.colors.accent, color = ExtendedTheme.colors.accent,
backgroundColor = ExtendedTheme.colors.accent.copy(alpha = 0.25f) backgroundColor = ExtendedTheme.colors.accent.copy(alpha = 0.1f)
), ),
"Lz" to buildChipInlineContent(stringResource(id = R.string.tip_lz)), "Lz" to buildChipInlineContent(stringResource(id = R.string.tip_lz)),
), ),

View File

@ -2,6 +2,7 @@ package com.huanchengfly.tieba.post.ui.page.thread
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.ui.text.AnnotatedString
import com.huanchengfly.tieba.post.api.TiebaApi import com.huanchengfly.tieba.post.api.TiebaApi
import com.huanchengfly.tieba.post.api.models.AgreeBean import com.huanchengfly.tieba.post.api.models.AgreeBean
import com.huanchengfly.tieba.post.api.models.protos.Anti import com.huanchengfly.tieba.post.api.models.protos.Anti
@ -10,7 +11,9 @@ import com.huanchengfly.tieba.post.api.models.protos.SimpleForum
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.User import com.huanchengfly.tieba.post.api.models.protos.User
import com.huanchengfly.tieba.post.api.models.protos.contentRenders import com.huanchengfly.tieba.post.api.models.protos.contentRenders
import com.huanchengfly.tieba.post.api.models.protos.pbPage.PbPageResponse
import com.huanchengfly.tieba.post.api.models.protos.renders import com.huanchengfly.tieba.post.api.models.protos.renders
import com.huanchengfly.tieba.post.api.models.protos.subPostContents
import com.huanchengfly.tieba.post.api.models.protos.updateAgreeStatus import com.huanchengfly.tieba.post.api.models.protos.updateAgreeStatus
import com.huanchengfly.tieba.post.api.models.protos.updateCollectStatus import com.huanchengfly.tieba.post.api.models.protos.updateCollectStatus
import com.huanchengfly.tieba.post.api.retrofit.exception.TiebaUnknownException import com.huanchengfly.tieba.post.api.retrofit.exception.TiebaUnknownException
@ -125,24 +128,10 @@ class ThreadViewModel @Inject constructor() :
threadId, page, postId, forumId, seeLz, sortType, threadId, page, postId, forumId, seeLz, sortType,
from = from.takeIf { it == ThreadPageFrom.FROM_STORE }.orEmpty() from = from.takeIf { it == ThreadPageFrom.FROM_STORE }.orEmpty()
) )
.map { response -> .map<PbPageResponse, ThreadPartialChange.Load> { response ->
if ( if (response.data_?.page == null || response.data_.thread?.author == null || response.data_.forum == null || response.data_.anti == null) throw TiebaUnknownException
response.data_?.page != null val postList = response.data_.post_list
&& response.data_.thread?.author != null val firstPost = response.data_.first_floor_post
&& response.data_.forum != null
&& response.data_.anti != null
) {
val userList = response.data_.user_list
val postList = response.data_.post_list.map {
it.copy(
author = it.author
?: userList.first { user -> user.id == it.author_id },
from_forum = response.data_.forum,
tid = response.data_.thread.id,
)
}
val firstPost = postList.firstOrNull { it.floor == 1 }
?: response.data_.first_floor_post?.copy(author = response.data_.thread.author)
val notFirstPosts = postList.filterNot { it.floor == 1 } val notFirstPosts = postList.filterNot { it.floor == 1 }
ThreadPartialChange.Load.Success( ThreadPartialChange.Load.Success(
response.data_.thread.title, response.data_.thread.title,
@ -163,11 +152,11 @@ class ThreadViewModel @Inject constructor() :
response.data_.page.has_prev != 0, response.data_.page.has_prev != 0,
firstPost?.contentRenders, firstPost?.contentRenders,
notFirstPosts.map { it.contentRenders }, notFirstPosts.map { it.contentRenders },
notFirstPosts.map { it.subPostContents }.toImmutableList(),
postId, postId,
seeLz, seeLz,
sortType, sortType,
) )
} else ThreadPartialChange.Load.Failure(TiebaUnknownException)
} }
.onStart { emit(ThreadPartialChange.Load.Start) } .onStart { emit(ThreadPartialChange.Load.Start) }
.catch { emit(ThreadPartialChange.Load.Failure(it)) } .catch { emit(ThreadPartialChange.Load.Failure(it)) }
@ -206,6 +195,7 @@ class ThreadViewModel @Inject constructor() :
response.data_.page.has_prev != 0, response.data_.page.has_prev != 0,
firstPost?.contentRenders ?: emptyList(), firstPost?.contentRenders ?: emptyList(),
notFirstPosts.map { it.contentRenders }, notFirstPosts.map { it.contentRenders },
notFirstPosts.map { it.subPostContents }.toImmutableList(),
postId = 0, postId = 0,
seeLz, seeLz,
sortType, sortType,
@ -241,7 +231,8 @@ class ThreadViewModel @Inject constructor() :
postIds + posts.map { it.id }, postIds + posts.map { it.id },
sortType sortType
), ),
posts.map { it.contentRenders } posts.map { it.contentRenders },
posts.map { it.subPostContents }.toImmutableList(),
) )
} else ThreadPartialChange.LoadMore.Failure(-1, "未知错误") } else ThreadPartialChange.LoadMore.Failure(-1, "未知错误")
} }
@ -277,7 +268,8 @@ class ThreadViewModel @Inject constructor() :
response.data_.page.current_page, response.data_.page.current_page,
response.data_.page.new_total_page, response.data_.page.new_total_page,
response.data_.page.has_prev != 0, response.data_.page.has_prev != 0,
posts.map { it.contentRenders } posts.map { it.contentRenders },
posts.map { it.subPostContents }.toImmutableList(),
) )
} else ThreadPartialChange.LoadPrevious.Failure(-1, "未知错误") } else ThreadPartialChange.LoadPrevious.Failure(-1, "未知错误")
} }
@ -537,6 +529,7 @@ sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
firstPostContentRenders = firstPostContentRenders?.toImmutableList() firstPostContentRenders = firstPostContentRenders?.toImmutableList()
?: oldState.firstPostContentRenders, ?: oldState.firstPostContentRenders,
contentRenders = contentRenders.toImmutableList(), contentRenders = contentRenders.toImmutableList(),
subPostContents = subPostContents.toImmutableList(),
postId = postId, postId = postId,
seeLz = seeLz, seeLz = seeLz,
sortType = sortType, sortType = sortType,
@ -567,6 +560,7 @@ sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
val hasPrevious: Boolean, val hasPrevious: Boolean,
val firstPostContentRenders: List<PbContentRender>?, val firstPostContentRenders: List<PbContentRender>?,
val contentRenders: List<ImmutableList<PbContentRender>>, val contentRenders: List<ImmutableList<PbContentRender>>,
val subPostContents: List<ImmutableList<AnnotatedString>>,
val postId: Long = 0, val postId: Long = 0,
val seeLz: Boolean = false, val seeLz: Boolean = false,
val sortType: Int = 0, val sortType: Int = 0,
@ -596,6 +590,7 @@ sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
hasPrevious = hasPrevious, hasPrevious = hasPrevious,
firstPostContentRenders = firstPostContentRenders.toImmutableList(), firstPostContentRenders = firstPostContentRenders.toImmutableList(),
contentRenders = contentRenders.toImmutableList(), contentRenders = contentRenders.toImmutableList(),
subPostContents = subPostContents.toImmutableList(),
postId = postId, postId = postId,
seeLz = seeLz, seeLz = seeLz,
sortType = sortType, sortType = sortType,
@ -622,6 +617,7 @@ sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
val hasPrevious: Boolean, val hasPrevious: Boolean,
val firstPostContentRenders: List<PbContentRender>, val firstPostContentRenders: List<PbContentRender>,
val contentRenders: List<ImmutableList<PbContentRender>>, val contentRenders: List<ImmutableList<PbContentRender>>,
val subPostContents: List<ImmutableList<AnnotatedString>>,
val postId: Long, val postId: Long,
val seeLz: Boolean, val seeLz: Boolean,
val sortType: Int, val sortType: Int,
@ -644,7 +640,8 @@ sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
totalPage = totalPage, totalPage = totalPage,
hasMore = hasMore, hasMore = hasMore,
nextPagePostId = nextPagePostId, nextPagePostId = nextPagePostId,
contentRenders = (oldState.contentRenders + contentRenders).toImmutableList() contentRenders = (oldState.contentRenders + contentRenders).toImmutableList(),
subPostContents = (oldState.subPostContents + subPostContents).toImmutableList()
) )
is Failure -> oldState.copy(isLoadingMore = false) is Failure -> oldState.copy(isLoadingMore = false)
@ -661,6 +658,7 @@ sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
val hasMore: Boolean, val hasMore: Boolean,
val nextPagePostId: Long, val nextPagePostId: Long,
val contentRenders: List<ImmutableList<PbContentRender>>, val contentRenders: List<ImmutableList<PbContentRender>>,
val subPostContents: List<ImmutableList<AnnotatedString>>,
) : LoadMore() ) : LoadMore()
data class Failure( data class Failure(
@ -680,7 +678,8 @@ sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
currentPageMin = currentPage, currentPageMin = currentPage,
totalPage = totalPage, totalPage = totalPage,
hasPrevious = hasPrevious, hasPrevious = hasPrevious,
contentRenders = (contentRenders + oldState.contentRenders).toImmutableList() contentRenders = (contentRenders + oldState.contentRenders).toImmutableList(),
subPostContents = (subPostContents + oldState.subPostContents).toImmutableList()
) )
is Failure -> oldState.copy(isRefreshing = false) is Failure -> oldState.copy(isRefreshing = false)
@ -696,6 +695,7 @@ sealed interface ThreadPartialChange : PartialChange<ThreadUiState> {
val totalPage: Int, val totalPage: Int,
val hasPrevious: Boolean, val hasPrevious: Boolean,
val contentRenders: List<ImmutableList<PbContentRender>>, val contentRenders: List<ImmutableList<PbContentRender>>,
val subPostContents: List<ImmutableList<AnnotatedString>>,
) : LoadPrevious() ) : LoadPrevious()
data class Failure( data class Failure(
@ -888,6 +888,7 @@ data class ThreadUiState(
val data: ImmutableList<PostItemData> = persistentListOf(), val data: ImmutableList<PostItemData> = persistentListOf(),
val firstPostContentRenders: ImmutableList<PbContentRender> = persistentListOf(), val firstPostContentRenders: ImmutableList<PbContentRender> = persistentListOf(),
val contentRenders: ImmutableList<ImmutableList<PbContentRender>> = persistentListOf(), val contentRenders: ImmutableList<ImmutableList<PbContentRender>> = persistentListOf(),
val subPostContents: ImmutableList<ImmutableList<AnnotatedString>> = persistentListOf(),
val isImmersiveMode: Boolean = false, val isImmersiveMode: Boolean = false,
) : UiState ) : UiState