Add implement for infinite scroll

This commit is contained in:
Ivan Agosto
2025-02-04 21:48:36 -06:00
parent b3d8347200
commit ac9004fe36
5 changed files with 137 additions and 23 deletions

View File

@@ -3,9 +3,9 @@ package org.libre.agosto.p2play
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.libre.agosto.p2play.activities.MainActivity
import org.libre.agosto.p2play.ajax.Auth import org.libre.agosto.p2play.ajax.Auth
import org.libre.agosto.p2play.helpers.TaskManager import org.libre.agosto.p2play.helpers.TaskManager
import org.libre.agosto.p2play.models.TokenModel import org.libre.agosto.p2play.models.TokenModel

View File

@@ -35,9 +35,7 @@ class MainActivity : ComponentActivity() {
) { innerPadding -> ) { innerPadding ->
VideosView( VideosView(
modifier = Modifier.padding(innerPadding) modifier = Modifier.padding(innerPadding)
) { )
}
} }
} }
} }

View File

@@ -1,5 +1,8 @@
package org.libre.agosto.p2play.components.lists package org.libre.agosto.p2play.components.lists
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
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.lazy.rememberLazyListState
@@ -8,29 +11,69 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import org.libre.agosto.p2play.components.organisms.VideoItem import org.libre.agosto.p2play.components.organisms.VideoItem
import org.libre.agosto.p2play.models.VideoModel import org.libre.agosto.p2play.models.VideoModel
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.libre.agosto.p2play.helpers.InfiniteScrollHandler
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun VideoList (videos: ArrayList<VideoModel>, header: @Composable (() -> Unit)?, onLoadMore: (() -> Unit)? = null) { fun VideoList (videos: List<VideoModel>, header: @Composable (() -> Unit)?, isLoading: Boolean = false, onRefresh: (() -> Unit)? = null, onLoadMore: (() -> Unit)? = null) {
val videoList by remember { derivedStateOf { videos } }
var isRefreshing by remember { mutableStateOf(false) }
val lazyState = rememberLazyListState() val lazyState = rememberLazyListState()
LazyColumn { PullToRefreshBox(
if (header !== null) { isRefreshing,
item { header() } {
if (onRefresh !== null) {
onRefresh()
}
} }
items (videos) { ) {
VideoItem(it) LazyColumn(state = lazyState) {
if (header !== null) {
item(key = "header") { header() }
}
items(videoList, key = { it.id }) {
VideoItem(it)
}
if (isLoading) {
item(key = "loading") {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
}
} }
} }
if (onLoadMore !== null) { if (onLoadMore !== null) {
LaunchedEffect(lazyState) { LaunchedEffect (isLoading) {
snapshotFlow { lazyState.layoutInfo.visibleItemsInfo } snapshotFlow { isLoading }
.collect { visibleItems -> .collect {
if (visibleItems.isNotEmpty() && if (!it) {
visibleItems.last().index >= videos.size - 1) { isRefreshing = false
onLoadMore()
} }
} }
} }
InfiniteScrollHandler(lazyState, 2) {
onLoadMore()
}
} }
} }

View File

@@ -5,14 +5,17 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.filled.Star
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import kotlinx.coroutines.flow.distinctUntilChanged
import org.libre.agosto.p2play.R import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.ajax.Videos import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.components.lists.VideoList import org.libre.agosto.p2play.components.lists.VideoList
@@ -23,9 +26,7 @@ import org.libre.agosto.p2play.models.VideoModel
@SuppressLint("MutableCollectionMutableState") @SuppressLint("MutableCollectionMutableState")
@Composable @Composable
fun VideosView (modifier: Modifier, click: (VideoModel) -> Unit) { fun VideosView (modifier: Modifier) {
val client: Videos = Videos()
var videoFilter by rememberSaveable { mutableStateOf("trending") }
val chips = arrayListOf( val chips = arrayListOf(
object : ChipValues<String> { object : ChipValues<String> {
override val text = stringResource(R.string.nav_trending) override val text = stringResource(R.string.nav_trending)
@@ -53,11 +54,46 @@ fun VideosView (modifier: Modifier, click: (VideoModel) -> Unit) {
override val icon = ImageVector.vectorResource(R.drawable.ic_home_black_24dp) override val icon = ImageVector.vectorResource(R.drawable.ic_home_black_24dp)
} }
) )
var videos by rememberSaveable { mutableStateOf<ArrayList<VideoModel>>(arrayListOf<VideoModel>()) }
val task = TaskManager<ArrayList<VideoModel>>()
task.runTask({ client.getLastVideos() }) { var videoFilter by rememberSaveable { mutableStateOf("trending") }
videos = it val videos by rememberSaveable { mutableStateOf(mutableListOf<VideoModel>()) }
var isLoading by rememberSaveable { mutableStateOf(true) }
LaunchedEffect(isLoading) {
if (isLoading) {
val task = TaskManager<List<VideoModel>>()
task.runTask(
{
val client = Videos()
when (videoFilter) {
"trending" -> {
client.getTrendingVideos(videos.size)
}
"popular" -> {
client.getPopularVideos(videos.size)
}
"likes" -> {
client.getMostLikedVideos(videos.size)
}
"recent" -> {
client.getLastVideos(videos.size)
}
"local" -> {
client.getLocalVideos(videos.size)
}
else -> {
videoFilter = "trending"
client.getTrendingVideos(videos.size)
}
}
},
{
videos.addAll(it)
isLoading = false
}
)
}
} }
Column (modifier) { Column (modifier) {
@@ -66,6 +102,20 @@ fun VideosView (modifier: Modifier, click: (VideoModel) -> Unit) {
header = { header = {
ChipSelector(chips, videoFilter) { ChipSelector(chips, videoFilter) {
videoFilter = it videoFilter = it
videos.clear()
isLoading = true
}
},
isLoading = isLoading,
onRefresh = {
if (!isLoading) {
videos.clear()
isLoading = true
}
},
onLoadMore = {
if (!isLoading) {
isLoading = true
} }
} }
) )

View File

@@ -0,0 +1,23 @@
package org.libre.agosto.p2play.helpers
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
@Composable
fun InfiniteScrollHandler(
lazyState: LazyListState,
buffer: Int = 1,
onLoadMore: () -> Unit
) {
LaunchedEffect(lazyState) {
snapshotFlow { lazyState.layoutInfo.visibleItemsInfo }
.collect { visibleItems ->
val items = lazyState.layoutInfo.totalItemsCount
if (visibleItems.isNotEmpty() && items > buffer && visibleItems.last().index >= items - 1) {
onLoadMore()
}
}
}
}