feat: 列表滑动预加载
This commit is contained in:
parent
4f3538339e
commit
1e99d0fe21
|
|
@ -347,7 +347,9 @@ fun ForumThreadListPage(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
loadEnd = !hasMore
|
loadEnd = !hasMore,
|
||||||
|
lazyListState = lazyListState,
|
||||||
|
isEmpty = threadList.isEmpty(),
|
||||||
) {
|
) {
|
||||||
ThreadList(
|
ThreadList(
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.DropdownMenuItem
|
import androidx.compose.material.DropdownMenuItem
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -94,6 +95,7 @@ fun HistoryListPage(
|
||||||
viewModel.onEvent<HistoryListUiEvent.Delete.Success> {
|
viewModel.onEvent<HistoryListUiEvent.Delete.Success> {
|
||||||
snackbarHostState.showSnackbar(context.getString(R.string.delete_history_success))
|
snackbarHostState.showSnackbar(context.getString(R.string.delete_history_success))
|
||||||
}
|
}
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -101,9 +103,15 @@ fun HistoryListPage(
|
||||||
LoadMoreLayout(
|
LoadMoreLayout(
|
||||||
isLoading = isLoadingMore,
|
isLoading = isLoadingMore,
|
||||||
onLoadMore = { viewModel.send(HistoryListUiIntent.LoadMore(currentPage + 1)) },
|
onLoadMore = { viewModel.send(HistoryListUiIntent.LoadMore(currentPage + 1)) },
|
||||||
loadEnd = !hasMore
|
loadEnd = !hasMore,
|
||||||
|
lazyListState = lazyListState,
|
||||||
|
isEmpty = todayHistoryData.isEmpty() && beforeHistoryData.isEmpty()
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
state = lazyListState
|
||||||
) {
|
) {
|
||||||
LazyColumn {
|
|
||||||
if (todayHistoryData.isNotEmpty()) {
|
if (todayHistoryData.isNotEmpty()) {
|
||||||
stickyHeader(key = "TodayHistoryHeader") {
|
stickyHeader(key = "TodayHistoryHeader") {
|
||||||
Column(
|
Column(
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,8 @@ fun ConcernPage(
|
||||||
) {
|
) {
|
||||||
LoadMoreLayout(
|
LoadMoreLayout(
|
||||||
isLoading = isLoadingMore,
|
isLoading = isLoadingMore,
|
||||||
onLoadMore = { viewModel.send(ConcernUiIntent.LoadMore(nextPageTag)) }
|
onLoadMore = { viewModel.send(ConcernUiIntent.LoadMore(nextPageTag)) },
|
||||||
|
lazyListState = lazyListState,
|
||||||
) {
|
) {
|
||||||
val windowSizeClass = BaseComposeActivity.LocalWindowSizeClass.current
|
val windowSizeClass = BaseComposeActivity.LocalWindowSizeClass.current
|
||||||
val itemFraction = when (windowSizeClass.widthSizeClass) {
|
val itemFraction = when (windowSizeClass.widthSizeClass) {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
|
@ -113,7 +114,7 @@ fun PersonalizedPage(
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
viewModel.bindScrollToTopEvent(lazyListState = lazyListState)
|
viewModel.bindScrollToTopEvent(lazyListState = lazyListState)
|
||||||
var refreshCount by remember {
|
var refreshCount by remember {
|
||||||
mutableStateOf(0)
|
mutableIntStateOf(0)
|
||||||
}
|
}
|
||||||
var showRefreshTip by remember {
|
var showRefreshTip by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
|
|
@ -150,8 +151,10 @@ fun PersonalizedPage(
|
||||||
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
|
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
|
||||||
LoadMoreLayout(
|
LoadMoreLayout(
|
||||||
isLoading = isLoadingMore,
|
isLoading = isLoadingMore,
|
||||||
loadEnd = false,
|
|
||||||
onLoadMore = { viewModel.send(PersonalizedUiIntent.LoadMore(currentPage + 1)) },
|
onLoadMore = { viewModel.send(PersonalizedUiIntent.LoadMore(currentPage + 1)) },
|
||||||
|
loadEnd = false,
|
||||||
|
lazyListState = lazyListState,
|
||||||
|
isEmpty = data.isEmpty()
|
||||||
) {
|
) {
|
||||||
FeedList(
|
FeedList(
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
|
|
@ -80,17 +81,21 @@ fun NotificationsListPage(
|
||||||
)
|
)
|
||||||
val pullRefreshState = rememberPullRefreshState(
|
val pullRefreshState = rememberPullRefreshState(
|
||||||
refreshing = isRefreshing,
|
refreshing = isRefreshing,
|
||||||
onRefresh = { viewModel.send(NotificationsListUiIntent.Refresh) })
|
onRefresh = { viewModel.send(NotificationsListUiIntent.Refresh) }
|
||||||
|
)
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.pullRefresh(pullRefreshState)
|
modifier = Modifier.pullRefresh(pullRefreshState)
|
||||||
) {
|
) {
|
||||||
LoadMoreLayout(
|
LoadMoreLayout(
|
||||||
isLoading = isLoadingMore,
|
isLoading = isLoadingMore,
|
||||||
loadEnd = !hasMore,
|
|
||||||
onLoadMore = { viewModel.send(NotificationsListUiIntent.LoadMore(currentPage + 1)) },
|
onLoadMore = { viewModel.send(NotificationsListUiIntent.LoadMore(currentPage + 1)) },
|
||||||
|
loadEnd = !hasMore,
|
||||||
|
lazyListState = lazyListState,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = PaddingValues(vertical = 4.dp)
|
contentPadding = PaddingValues(vertical = 4.dp),
|
||||||
|
state = lazyListState,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = data,
|
items = data,
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,6 @@ internal fun SubPostsContent(
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
LoadMoreLayout(
|
LoadMoreLayout(
|
||||||
isLoading = isLoading,
|
isLoading = isLoading,
|
||||||
loadEnd = !hasMore,
|
|
||||||
onLoadMore = {
|
onLoadMore = {
|
||||||
viewModel.send(
|
viewModel.send(
|
||||||
SubPostsUiIntent.LoadMore(
|
SubPostsUiIntent.LoadMore(
|
||||||
|
|
@ -365,7 +364,10 @@ internal fun SubPostsContent(
|
||||||
currentPage + 1,
|
currentPage + 1,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
loadEnd = !hasMore,
|
||||||
|
lazyListState = lazyListState,
|
||||||
|
isEmpty = post == null && subPosts.isEmpty(),
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
|
|
|
||||||
|
|
@ -1114,7 +1114,6 @@ fun ThreadPage(
|
||||||
) {
|
) {
|
||||||
LoadMoreLayout(
|
LoadMoreLayout(
|
||||||
isLoading = isLoadingMore,
|
isLoading = isLoadingMore,
|
||||||
loadEnd = !hasMore,
|
|
||||||
onLoadMore = {
|
onLoadMore = {
|
||||||
viewModel.send(
|
viewModel.send(
|
||||||
ThreadUiIntent.LoadMore(
|
ThreadUiIntent.LoadMore(
|
||||||
|
|
@ -1128,7 +1127,10 @@ fun ThreadPage(
|
||||||
postIds = data.map { it.post.get { id } }
|
postIds = data.map { it.post.get { id } }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
loadEnd = !hasMore,
|
||||||
|
lazyListState = lazyListState,
|
||||||
|
isEmpty = firstPost == null && data.isEmpty()
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.InlineTextContent
|
import androidx.compose.foundation.text.InlineTextContent
|
||||||
import androidx.compose.foundation.text.appendInlineContent
|
import androidx.compose.foundation.text.appendInlineContent
|
||||||
|
|
@ -157,13 +158,16 @@ fun ThreadStorePage(
|
||||||
onRefresh = { viewModel.send(ThreadStoreUiIntent.Refresh) }
|
onRefresh = { viewModel.send(ThreadStoreUiIntent.Refresh) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
||||||
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
|
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
|
||||||
LoadMoreLayout(
|
LoadMoreLayout(
|
||||||
isLoading = isLoadingMore,
|
isLoading = isLoadingMore,
|
||||||
onLoadMore = { viewModel.send(ThreadStoreUiIntent.LoadMore(currentPage + 1)) },
|
onLoadMore = { viewModel.send(ThreadStoreUiIntent.LoadMore(currentPage + 1)) },
|
||||||
loadEnd = !hasMore
|
loadEnd = !hasMore,
|
||||||
|
lazyListState = lazyListState
|
||||||
) {
|
) {
|
||||||
LazyColumn {
|
LazyColumn(state = lazyListState) {
|
||||||
items(
|
items(
|
||||||
items = data,
|
items = data,
|
||||||
key = { it.threadId }
|
key = { it.threadId }
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.CircularProgressIndicator
|
import androidx.compose.material.CircularProgressIndicator
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
|
@ -23,11 +24,13 @@ import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
|
@ -45,6 +48,7 @@ import com.huanchengfly.tieba.post.ui.common.theme.compose.loadMoreIndicator
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.sample
|
import kotlinx.coroutines.flow.sample
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
@ -66,12 +70,15 @@ fun LoadMoreLayout(
|
||||||
willLoad = willLoad
|
willLoad = willLoad
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content: @Composable () -> Unit
|
lazyListState: LazyListState? = null,
|
||||||
|
isEmpty: Boolean = lazyListState?.layoutInfo?.totalItemsCount == 0,
|
||||||
|
preloadCount: Int = 1,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val loadDistance = with(LocalDensity.current) { LoadDistance.toPx() }
|
val loadDistance = with(LocalDensity.current) { LoadDistance.toPx() }
|
||||||
|
|
||||||
val curOnLoadMore by rememberUpdatedState(newValue = onLoadMore)
|
val curOnLoadMore by rememberUpdatedState(newValue = onLoadMore)
|
||||||
var lastTriggerTime by remember { mutableStateOf(0L) }
|
var lastTriggerTime by remember { mutableLongStateOf(0L) }
|
||||||
var waitingStateReset by remember { mutableStateOf(false) }
|
var waitingStateReset by remember { mutableStateOf(false) }
|
||||||
val loadMoreFlow = remember {
|
val loadMoreFlow = remember {
|
||||||
MutableSharedFlow<Long>(
|
MutableSharedFlow<Long>(
|
||||||
|
|
@ -95,9 +102,32 @@ fun LoadMoreLayout(
|
||||||
}
|
}
|
||||||
|
|
||||||
val canLoadMore = remember(enableLoadMore, loadEnd) { enableLoadMore && !loadEnd }
|
val canLoadMore = remember(enableLoadMore, loadEnd) { enableLoadMore && !loadEnd }
|
||||||
|
val curIsEmpty by rememberUpdatedState(newValue = isEmpty)
|
||||||
val curIsLoading by rememberUpdatedState(newValue = isLoading)
|
val curIsLoading by rememberUpdatedState(newValue = isLoading)
|
||||||
val curCanLoadMore by rememberUpdatedState(newValue = canLoadMore)
|
val curCanLoadMore by rememberUpdatedState(newValue = canLoadMore)
|
||||||
|
|
||||||
|
// 处理列表滚动到底部时自动加载更多
|
||||||
|
val curLazyListState by rememberUpdatedState(newValue = lazyListState)
|
||||||
|
LaunchedEffect(curLazyListState) {
|
||||||
|
curLazyListState?.let { state ->
|
||||||
|
snapshotFlow {
|
||||||
|
val shouldPreload = !curIsEmpty && curCanLoadMore && !curIsLoading
|
||||||
|
val isInPreloadRange =
|
||||||
|
state.firstVisibleItemIndex + state.layoutInfo.visibleItemsInfo.size - 1 >= state.layoutInfo.totalItemsCount - preloadCount
|
||||||
|
shouldPreload && isInPreloadRange
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collect {
|
||||||
|
if (it) {
|
||||||
|
val curTime = System.currentTimeMillis()
|
||||||
|
coroutineScope.launch {
|
||||||
|
loadMoreFlow.emit(curTime)
|
||||||
|
}
|
||||||
|
curTime - lastTriggerTime >= 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val swipeableState = rememberSwipeableState(false) { newValue ->
|
val swipeableState = rememberSwipeableState(false) { newValue ->
|
||||||
if (newValue && !curIsLoading && curCanLoadMore) {
|
if (newValue && !curIsLoading && curCanLoadMore) {
|
||||||
|
|
@ -116,7 +146,7 @@ fun LoadMoreLayout(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(isLoading) { swipeableState.animateTo(isLoading) }
|
LaunchedEffect(isLoading) { if (!isLoading) swipeableState.animateTo(isLoading) }
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue