diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/threadlist/ForumThreadListPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/threadlist/ForumThreadListPage.kt index de4a0da5..cb30f177 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/threadlist/ForumThreadListPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/threadlist/ForumThreadListPage.kt @@ -347,7 +347,9 @@ fun ForumThreadListPage( ) ) }, - loadEnd = !hasMore + loadEnd = !hasMore, + lazyListState = lazyListState, + isEmpty = threadList.isEmpty(), ) { ThreadList( state = lazyListState, diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt index 6437cd2d..644ad1c6 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.DropdownMenuItem import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -94,6 +95,7 @@ fun HistoryListPage( viewModel.onEvent { snackbarHostState.showSnackbar(context.getString(R.string.delete_history_success)) } + val lazyListState = rememberLazyListState() Box( modifier = Modifier .fillMaxSize() @@ -101,9 +103,15 @@ fun HistoryListPage( LoadMoreLayout( isLoading = isLoadingMore, onLoadMore = { viewModel.send(HistoryListUiIntent.LoadMore(currentPage + 1)) }, - loadEnd = !hasMore + loadEnd = !hasMore, + lazyListState = lazyListState, + isEmpty = todayHistoryData.isEmpty() && beforeHistoryData.isEmpty() ) { - LazyColumn { + LazyColumn( + modifier = Modifier + .fillMaxSize(), + state = lazyListState + ) { if (todayHistoryData.isNotEmpty()) { stickyHeader(key = "TodayHistoryHeader") { Column( diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/concern/ConcernPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/concern/ConcernPage.kt index 973dc31f..9c5c5c1f 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/concern/ConcernPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/concern/ConcernPage.kt @@ -78,7 +78,8 @@ fun ConcernPage( ) { LoadMoreLayout( isLoading = isLoadingMore, - onLoadMore = { viewModel.send(ConcernUiIntent.LoadMore(nextPageTag)) } + onLoadMore = { viewModel.send(ConcernUiIntent.LoadMore(nextPageTag)) }, + lazyListState = lazyListState, ) { val windowSizeClass = BaseComposeActivity.LocalWindowSizeClass.current val itemFraction = when (windowSizeClass.widthSizeClass) { diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedPage.kt index 096a052c..cd39894b 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/explore/personalized/PersonalizedPage.kt @@ -36,6 +36,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState @@ -113,7 +114,7 @@ fun PersonalizedPage( val lazyListState = rememberLazyListState() viewModel.bindScrollToTopEvent(lazyListState = lazyListState) var refreshCount by remember { - mutableStateOf(0) + mutableIntStateOf(0) } var showRefreshTip by remember { mutableStateOf(false) @@ -150,8 +151,10 @@ fun PersonalizedPage( Box(modifier = Modifier.pullRefresh(pullRefreshState)) { LoadMoreLayout( isLoading = isLoadingMore, - loadEnd = false, onLoadMore = { viewModel.send(PersonalizedUiIntent.LoadMore(currentPage + 1)) }, + loadEnd = false, + lazyListState = lazyListState, + isEmpty = data.isEmpty() ) { FeedList( state = lazyListState, 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 23332160..9a80fbc6 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 @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Text @@ -80,17 +81,21 @@ fun NotificationsListPage( ) val pullRefreshState = rememberPullRefreshState( refreshing = isRefreshing, - onRefresh = { viewModel.send(NotificationsListUiIntent.Refresh) }) + onRefresh = { viewModel.send(NotificationsListUiIntent.Refresh) } + ) + val lazyListState = rememberLazyListState() Box( modifier = Modifier.pullRefresh(pullRefreshState) ) { LoadMoreLayout( isLoading = isLoadingMore, - loadEnd = !hasMore, onLoadMore = { viewModel.send(NotificationsListUiIntent.LoadMore(currentPage + 1)) }, + loadEnd = !hasMore, + lazyListState = lazyListState, ) { LazyColumn( - contentPadding = PaddingValues(vertical = 4.dp) + contentPadding = PaddingValues(vertical = 4.dp), + state = lazyListState, ) { items( items = data, diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsPage.kt index 703c2224..2a592b50 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/subposts/SubPostsPage.kt @@ -354,7 +354,6 @@ internal fun SubPostsContent( ) { paddingValues -> LoadMoreLayout( isLoading = isLoading, - loadEnd = !hasMore, onLoadMore = { viewModel.send( SubPostsUiIntent.LoadMore( @@ -365,7 +364,10 @@ internal fun SubPostsContent( currentPage + 1, ) ) - } + }, + loadEnd = !hasMore, + lazyListState = lazyListState, + isEmpty = post == null && subPosts.isEmpty(), ) { LazyColumn( modifier = Modifier.padding(paddingValues), diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt index 324b3c6d..ae09609b 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/thread/ThreadPage.kt @@ -1114,7 +1114,6 @@ fun ThreadPage( ) { LoadMoreLayout( isLoading = isLoadingMore, - loadEnd = !hasMore, onLoadMore = { viewModel.send( ThreadUiIntent.LoadMore( @@ -1128,7 +1127,10 @@ fun ThreadPage( postIds = data.map { it.post.get { id } } ) ) - } + }, + loadEnd = !hasMore, + lazyListState = lazyListState, + isEmpty = firstPost == null && data.isEmpty() ) { LazyColumn( state = lazyListState, diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/threadstore/ThreadStorePage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/threadstore/ThreadStorePage.kt index 55af3fe4..e57e39c8 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/threadstore/ThreadStorePage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/threadstore/ThreadStorePage.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent @@ -157,13 +158,16 @@ fun ThreadStorePage( onRefresh = { viewModel.send(ThreadStoreUiIntent.Refresh) } ) + val lazyListState = rememberLazyListState() + Box(modifier = Modifier.pullRefresh(pullRefreshState)) { LoadMoreLayout( isLoading = isLoadingMore, onLoadMore = { viewModel.send(ThreadStoreUiIntent.LoadMore(currentPage + 1)) }, - loadEnd = !hasMore + loadEnd = !hasMore, + lazyListState = lazyListState ) { - LazyColumn { + LazyColumn(state = lazyListState) { items( items = data, key = { it.threadId } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/LoadMore.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/LoadMore.kt index 15bd5f33..fa1665e5 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/LoadMore.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/LoadMore.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.ExperimentalMaterialApi @@ -23,11 +24,13 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import kotlin.math.abs @@ -66,12 +70,15 @@ fun LoadMoreLayout( 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 curOnLoadMore by rememberUpdatedState(newValue = onLoadMore) - var lastTriggerTime by remember { mutableStateOf(0L) } + var lastTriggerTime by remember { mutableLongStateOf(0L) } var waitingStateReset by remember { mutableStateOf(false) } val loadMoreFlow = remember { MutableSharedFlow( @@ -95,9 +102,32 @@ fun LoadMoreLayout( } val canLoadMore = remember(enableLoadMore, loadEnd) { enableLoadMore && !loadEnd } + val curIsEmpty by rememberUpdatedState(newValue = isEmpty) val curIsLoading by rememberUpdatedState(newValue = isLoading) 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 -> if (newValue && !curIsLoading && curCanLoadMore) { @@ -116,7 +146,7 @@ fun LoadMoreLayout( } } - LaunchedEffect(isLoading) { swipeableState.animateTo(isLoading) } + LaunchedEffect(isLoading) { if (!isLoading) swipeableState.animateTo(isLoading) } Box( modifier = Modifier