Add PageCurl override with page keys

This commit is contained in:
Oleksandr Balan
2023-03-27 18:38:38 +02:00
parent bd30e9d021
commit ca8a9245cf
3 changed files with 81 additions and 18 deletions

View File

@@ -3,7 +3,9 @@
package eu.wewox.pagecurl.screens package eu.wewox.pagecurl.screens
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
@@ -12,7 +14,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.paging.LoadState import androidx.paging.LoadState.Loading
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
@@ -21,6 +23,7 @@ import androidx.paging.PagingState
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import eu.wewox.pagecurl.ExperimentalPageCurlApi import eu.wewox.pagecurl.ExperimentalPageCurlApi
import eu.wewox.pagecurl.page.PageCurl import eu.wewox.pagecurl.page.PageCurl
import eu.wewox.pagecurl.page.rememberPageCurlState
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -30,23 +33,38 @@ import kotlinx.coroutines.flow.Flow
*/ */
@Composable @Composable
fun PagingPageCurlScreen() { fun PagingPageCurlScreen() {
val items = rememberPager().collectAsLazyPagingItems() val startPage = 4 // in range 0..9
val startPageOffset = 5 // in range 0..9
val extraItem = items.loadState.append is LoadState.Loading || items.loadState.refresh is LoadState.Loading val items = rememberPager(startPage).collectAsLazyPagingItems()
val count = items.itemCount + (if (extraItem) 1 else 0)
val loading = with(items.loadState) { refresh is Loading || append is Loading || prepend is Loading }
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize()) {
PageCurl(count = count) { index -> if (items.loadState.refresh is Loading) {
Box( Box(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
) { ) {
if (index < items.itemCount) { CircularProgressIndicator()
}
} else {
PageCurl(
count = items.itemCount,
key = { index -> items.peek(index).hashCode() },
state = rememberPageCurlState(initialCurrent = startPageOffset),
) { index ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
) {
Text(items[index]?.content.orEmpty()) Text(items[index]?.content.orEmpty())
} else { Text(if (loading) "Loading..." else "")
CircularProgressIndicator()
} }
} }
} }
@@ -54,43 +72,47 @@ fun PagingPageCurlScreen() {
} }
@Composable @Composable
private fun rememberPager(): Flow<PagingData<Item>> = private fun rememberPager(startPage: Int): Flow<PagingData<Item>> =
remember { remember {
Pager( Pager(
config = PagingConfig( config = PagingConfig(
pageSize = 10, pageSize = 10,
prefetchDistance = 3, prefetchDistance = 3,
), ),
pagingSourceFactory = { ItemPagingSource(BackendService()) } pagingSourceFactory = { ItemPagingSource(startPage, BackendService()) }
).flow ).flow
} }
private class Item(val content: String) private class Item(val content: String)
private class Response(val items: List<Item>, val nextPageNumber: Int) private class Response(val items: List<Item>, val prePageNumber: Int, val nextPageNumber: Int)
private class BackendService { private class BackendService {
suspend fun searchItems(page: Int): Response { suspend fun searchItems(page: Int): Response {
delay(5_000L) delay(1_000L)
return Response( return Response(
items = List(10) { Item("Content #${page * 10 + it}") }, items = List(10) { Item("Content #${page * 10 + it}") },
nextPageNumber = page + 1 prePageNumber = page - 1,
nextPageNumber = page + 1,
) )
} }
} }
private class ItemPagingSource(private val backend: BackendService) : PagingSource<Int, Item>() { private class ItemPagingSource(
private val startPage: Int,
private val backend: BackendService
) : PagingSource<Int, Item>() {
@Suppress("TooGenericExceptionCaught") // It is only for demo purpose @Suppress("TooGenericExceptionCaught") // It is only for demo purpose
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> = override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Item> =
try { try {
val nextPageNumber = params.key ?: 0 val nextPageNumber = params.key ?: startPage
val response = backend.searchItems(nextPageNumber) val response = backend.searchItems(nextPageNumber)
LoadResult.Page( LoadResult.Page(
data = response.items, data = response.items,
prevKey = null, // Only paging forward. prevKey = response.prePageNumber.takeIf { it >= 0 },
nextKey = response.nextPageNumber nextKey = response.nextPageNumber.takeIf { it < 10 }
) )
} catch (e: Exception) { } catch (e: Exception) {
LoadResult.Error(e) LoadResult.Error(e)

View File

@@ -4,8 +4,11 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.ui.Modifier import androidx.compose.ui.Modifier
import eu.wewox.pagecurl.ExperimentalPageCurlApi import eu.wewox.pagecurl.ExperimentalPageCurlApi
@@ -85,6 +88,44 @@ public fun PageCurl(
} }
} }
/**
* Shows the pages which may be turned by drag or tap gestures.
*
* @param count The count of pages.
* @param key The lambda to provide stable key for each item. Useful when adding and removing items before current page.
* @param state The state of the PageCurl. Use this to programmatically change the current page or observe changes.
* @param modifier The modifier for this composable.
* @param content The content lambda to provide the page composable. Receives the page number.
*/
@ExperimentalPageCurlApi
@Composable
public fun PageCurl(
count: Int,
key: (Int) -> Any,
modifier: Modifier = Modifier,
state: PageCurlState = rememberPageCurlState(),
content: @Composable (Int) -> Unit
) {
var lastKey by remember(state.current) { mutableStateOf(if (count > 0) key(state.current) else null) }
remember(count) {
val newKey = if (count > 0) key(state.current) else null
if (newKey != lastKey) {
val index = List(count, key).indexOf(lastKey).coerceIn(0, count - 1)
lastKey = newKey
state.current = index
}
count
}
PageCurl(
count = count,
state = state,
content = content,
modifier = modifier,
)
}
/** /**
* Shows the pages which may be turned by drag or tap gestures. * Shows the pages which may be turned by drag or tap gestures.
* *

View File

@@ -125,7 +125,7 @@ public class PageCurlState(
internal fun setup(count: Int, constraints: Constraints) { internal fun setup(count: Int, constraints: Constraints) {
max = count max = count
if (current >= count) { if (current >= count) {
current = count - 1 current = (count - 1).coerceAtLeast(0)
} }
if (internalState?.constraints == constraints) { if (internalState?.constraints == constraints) {