pref: 性能优化
This commit is contained in:
parent
f4c64bbd2f
commit
ac5839eb9d
|
|
@ -1,16 +1,25 @@
|
|||
package com.huanchengfly.tieba.post.arch
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.scan
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
interface PartialChangeProducer<Intent : UiIntent, PC : PartialChange<State>, State : UiState> {
|
||||
fun toPartialChangeFlow(intentFlow: Flow<Intent>): Flow<PC>
|
||||
}
|
||||
|
||||
@Stable
|
||||
abstract class BaseViewModel<
|
||||
Intent : UiIntent,
|
||||
PC : PartialChange<State>,
|
||||
|
|
@ -58,4 +67,15 @@ abstract class BaseViewModel<
|
|||
_intentFlow.emit(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as BaseViewModel<*, *, *, *>
|
||||
|
||||
if (initialized != other.initialized) return false
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed
|
||||
|
|
@ -33,6 +34,7 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
|||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -44,8 +46,12 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.panpf.sketch.request.PauseLoadWhenScrollingDrawableDecodeInterceptor
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.activities.ThreadActivity
|
||||
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.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||
|
|
@ -123,41 +129,29 @@ fun PersonalizedPage(
|
|||
showRefreshTip = false
|
||||
}
|
||||
}
|
||||
|
||||
if (lazyStaggeredGridState.isScrollInProgress) {
|
||||
DisposableEffect(Unit) {
|
||||
PauseLoadWhenScrollingDrawableDecodeInterceptor.scrolling = true
|
||||
onDispose {
|
||||
PauseLoadWhenScrollingDrawableDecodeInterceptor.scrolling = false
|
||||
}
|
||||
}
|
||||
}
|
||||
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
|
||||
LoadMoreLayout(
|
||||
isLoading = isLoadingMore,
|
||||
loadEnd = false,
|
||||
onLoadMore = { viewModel.send(PersonalizedUiIntent.LoadMore(currentPage + 1)) },
|
||||
) {
|
||||
LazyVerticalStaggeredGrid(
|
||||
columns = StaggeredGridCells.Adaptive(240.dp),
|
||||
state = lazyStaggeredGridState
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = data,
|
||||
key = { _, item -> "${item.id}" },
|
||||
contentType = { _, item ->
|
||||
when {
|
||||
item.videoInfo != null -> "Video"
|
||||
item.media.size == 1 -> "SingleMedia"
|
||||
item.media.size > 1 -> "MultiMedia"
|
||||
else -> "PlainText"
|
||||
}
|
||||
}
|
||||
) { index, item ->
|
||||
Column {
|
||||
AnimatedVisibility(
|
||||
visible = !hiddenThreadIds.contains(item.threadId),
|
||||
enter = EnterTransition.None,
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
) {
|
||||
FeedCard(
|
||||
info = wrapImmutable(item),
|
||||
onClick = {
|
||||
ThreadActivity.launch(context, item.threadId.toString())
|
||||
FeedList(
|
||||
dataProvider = { data },
|
||||
personalizedDataProvider = { threadPersonalizedData },
|
||||
refreshPositionProvider = { refreshPosition },
|
||||
hiddenThreadIdsProvider = { hiddenThreadIds },
|
||||
onItemClick = { threadInfo ->
|
||||
ThreadActivity.launch(context, threadInfo.id.toString())
|
||||
},
|
||||
onAgree = {
|
||||
onAgree = { item ->
|
||||
viewModel.send(
|
||||
PersonalizedUiIntent.Agree(
|
||||
item.threadId,
|
||||
|
|
@ -166,10 +160,7 @@ fun PersonalizedPage(
|
|||
)
|
||||
)
|
||||
},
|
||||
) {
|
||||
Dislike(
|
||||
personalized = threadPersonalizedData[index],
|
||||
onDislike = { clickTime, reasons ->
|
||||
onDislike = { item, clickTime, reasons ->
|
||||
viewModel.send(
|
||||
PersonalizedUiIntent.Dislike(
|
||||
item.forumInfo?.id ?: 0,
|
||||
|
|
@ -178,41 +169,10 @@ fun PersonalizedPage(
|
|||
clickTime
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
onRefresh = { viewModel.send(PersonalizedUiIntent.Refresh) },
|
||||
state = lazyStaggeredGridState
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!hiddenThreadIds.contains(item.threadId)) {
|
||||
if ((refreshPosition == 0 || index + 1 != refreshPosition) && index < data.size - 1) {
|
||||
VerticalDivider(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
thickness = 2.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
if (refreshPosition != 0 && index + 1 == refreshPosition) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { viewModel.send(PersonalizedUiIntent.Refresh) }
|
||||
.padding(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Refresh,
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.tip_refresh),
|
||||
style = MaterialTheme.typography.subtitle1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(data.firstOrNull()?.id) {
|
||||
//delay(50)
|
||||
lazyStaggeredGridState.scrollToItem(0, 0)
|
||||
|
|
@ -242,7 +202,99 @@ fun PersonalizedPage(
|
|||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.align(Alignment.TopCenter)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.toast_feed_refresh, refreshCount), color = ExtendedTheme.colors.onAccent)
|
||||
Text(
|
||||
text = stringResource(id = R.string.toast_feed_refresh, refreshCount),
|
||||
color = ExtendedTheme.colors.onAccent
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun FeedList(
|
||||
dataProvider: () -> List<ThreadInfo>,
|
||||
personalizedDataProvider: () -> List<ThreadPersonalized>,
|
||||
refreshPositionProvider: () -> Int,
|
||||
hiddenThreadIdsProvider: () -> List<Long>,
|
||||
onItemClick: (ThreadInfo) -> Unit,
|
||||
onAgree: (ThreadInfo) -> Unit,
|
||||
onDislike: (ThreadInfo, Long, List<DislikeReason>) -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
state: LazyStaggeredGridState,
|
||||
) {
|
||||
val data = dataProvider()
|
||||
val threadPersonalizedData = personalizedDataProvider()
|
||||
val refreshPosition = refreshPositionProvider()
|
||||
val hiddenThreadIds = hiddenThreadIdsProvider()
|
||||
LazyVerticalStaggeredGrid(
|
||||
columns = StaggeredGridCells.Adaptive(240.dp),
|
||||
state = state
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = data,
|
||||
key = { _, item -> "${item.id}" },
|
||||
contentType = { _, item ->
|
||||
when {
|
||||
item.videoInfo != null -> "Video"
|
||||
item.media.size == 1 -> "SingleMedia"
|
||||
item.media.size > 1 -> "MultiMedia"
|
||||
else -> "PlainText"
|
||||
}
|
||||
}
|
||||
) { index, item ->
|
||||
Column {
|
||||
AnimatedVisibility(
|
||||
visible = !hiddenThreadIds.contains(item.threadId),
|
||||
enter = EnterTransition.None,
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
) {
|
||||
FeedCard(
|
||||
info = wrapImmutable(item),
|
||||
onClick = {
|
||||
onItemClick(item)
|
||||
},
|
||||
onAgree = {
|
||||
onAgree(item)
|
||||
},
|
||||
) {
|
||||
Dislike(
|
||||
personalized = threadPersonalizedData[index],
|
||||
onDislike = { clickTime, reasons ->
|
||||
onDislike(item, clickTime, reasons)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!hiddenThreadIds.contains(item.threadId)) {
|
||||
if ((refreshPosition == 0 || index + 1 != refreshPosition) && index < data.size - 1) {
|
||||
VerticalDivider(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
thickness = 2.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
if (refreshPosition != 0 && index + 1 == refreshPosition) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onRefresh)
|
||||
.padding(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Refresh,
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.tip_refresh),
|
||||
style = MaterialTheme.typography.subtitle1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import com.huanchengfly.tieba.post.activities.UserActivity
|
|||
import com.huanchengfly.tieba.post.api.abstractText
|
||||
import com.huanchengfly.tieba.post.api.hasAbstract
|
||||
import com.huanchengfly.tieba.post.api.models.protos.Media
|
||||
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.User
|
||||
import com.huanchengfly.tieba.post.arch.ImmutableHolder
|
||||
|
|
@ -178,30 +179,35 @@ private fun Badge(
|
|||
}
|
||||
}
|
||||
|
||||
private val ThreadContent: @Composable ColumnScope.(ThreadInfo) -> Unit = {
|
||||
val showTitle = it.isNoTitle != 1 && it.title.isNotBlank()
|
||||
val showAbstract = it.hasAbstract
|
||||
@Composable
|
||||
fun ThreadContent(
|
||||
info: ImmutableHolder<ThreadInfo>,
|
||||
) {
|
||||
val (item) = info
|
||||
|
||||
val showTitle = item.isNoTitle != 1 && item.title.isNotBlank()
|
||||
val showAbstract = item.hasAbstract
|
||||
val content = buildAnnotatedString {
|
||||
if (showTitle) {
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
if (it.isGood == 1) {
|
||||
if (item.isGood == 1) {
|
||||
withStyle(style = SpanStyle(color = ExtendedTheme.colors.primary)) {
|
||||
append(stringResource(id = R.string.tip_good))
|
||||
}
|
||||
append(" ")
|
||||
}
|
||||
|
||||
if (it.tabName.isNotBlank()) {
|
||||
append(it.tabName)
|
||||
if (item.tabName.isNotBlank()) {
|
||||
append(item.tabName)
|
||||
append(" | ")
|
||||
}
|
||||
|
||||
append(it.title)
|
||||
append(item.title)
|
||||
}
|
||||
}
|
||||
if (showTitle && showAbstract) append('\n')
|
||||
if (showAbstract) {
|
||||
append(it.abstractText.emoticonString)
|
||||
append(item.abstractText.emoticonString)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -261,6 +267,37 @@ fun FeedCardPlaceholder() {
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ForumInfoChip(
|
||||
info: ImmutableHolder<SimpleForum>,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val (forumInfo) = info
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.background(color = ExtendedTheme.colors.chip)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
NetworkImage(
|
||||
imageUri = StringUtil.getAvatarUrl(forumInfo.avatar),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.title_forum_name, forumInfo.name),
|
||||
style = MaterialTheme.typography.body2,
|
||||
color = ExtendedTheme.colors.onChip,
|
||||
fontSize = 12.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FeedCard(
|
||||
info: ImmutableHolder<ThreadInfo>,
|
||||
|
|
@ -276,7 +313,7 @@ fun FeedCard(
|
|||
}
|
||||
},
|
||||
content = {
|
||||
ThreadContent(item)
|
||||
ThreadContent(info)
|
||||
|
||||
if (item.videoInfo != null) {
|
||||
VideoPlayer(
|
||||
|
|
@ -326,31 +363,12 @@ fun FeedCard(
|
|||
|
||||
if (item.forumInfo != null) {
|
||||
val navigator = LocalNavigator.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.background(color = ExtendedTheme.colors.chip)
|
||||
.clickable {
|
||||
ForumInfoChip(
|
||||
info = wrapImmutable(item.forumInfo),
|
||||
onClick = {
|
||||
navigator.navigate(ForumPageDestination(item.forumInfo.name))
|
||||
}
|
||||
.padding(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
NetworkImage(
|
||||
imageUri = StringUtil.getAvatarUrl(item.forumInfo.avatar),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.title_forum_name, item.forumInfo.name),
|
||||
style = MaterialTheme.typography.body2,
|
||||
color = ExtendedTheme.colors.onChip,
|
||||
fontSize = 12.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
action = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue