diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/PagingPageCurlScreen.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/PagingPageCurlScreen.kt index ba9d17b..eaef776 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/PagingPageCurlScreen.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/PagingPageCurlScreen.kt @@ -3,7 +3,9 @@ package eu.wewox.pagecurl.screens import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.MaterialTheme @@ -12,7 +14,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.paging.LoadState +import androidx.paging.LoadState.Loading import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData @@ -21,6 +23,7 @@ import androidx.paging.PagingState import androidx.paging.compose.collectAsLazyPagingItems import eu.wewox.pagecurl.ExperimentalPageCurlApi import eu.wewox.pagecurl.page.PageCurl +import eu.wewox.pagecurl.page.rememberPageCurlState import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -30,23 +33,38 @@ import kotlinx.coroutines.flow.Flow */ @Composable 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 count = items.itemCount + (if (extraItem) 1 else 0) + val items = rememberPager(startPage).collectAsLazyPagingItems() + + val loading = with(items.loadState) { refresh is Loading || append is Loading || prepend is Loading } Box(Modifier.fillMaxSize()) { - PageCurl(count = count) { index -> + if (items.loadState.refresh is Loading) { Box( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxSize() .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()) - } else { - CircularProgressIndicator() + Text(if (loading) "Loading..." else "") } } } @@ -54,43 +72,47 @@ fun PagingPageCurlScreen() { } @Composable -private fun rememberPager(): Flow> = +private fun rememberPager(startPage: Int): Flow> = remember { Pager( config = PagingConfig( pageSize = 10, prefetchDistance = 3, ), - pagingSourceFactory = { ItemPagingSource(BackendService()) } + pagingSourceFactory = { ItemPagingSource(startPage, BackendService()) } ).flow } private class Item(val content: String) -private class Response(val items: List, val nextPageNumber: Int) +private class Response(val items: List, val prePageNumber: Int, val nextPageNumber: Int) private class BackendService { suspend fun searchItems(page: Int): Response { - delay(5_000L) + delay(1_000L) return Response( 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() { +private class ItemPagingSource( + private val startPage: Int, + private val backend: BackendService +) : PagingSource() { @Suppress("TooGenericExceptionCaught") // It is only for demo purpose override suspend fun load(params: LoadParams): LoadResult = try { - val nextPageNumber = params.key ?: 0 + val nextPageNumber = params.key ?: startPage val response = backend.searchItems(nextPageNumber) LoadResult.Page( data = response.items, - prevKey = null, // Only paging forward. - nextKey = response.nextPageNumber + prevKey = response.prePageNumber.takeIf { it >= 0 }, + nextKey = response.nextPageNumber.takeIf { it < 10 } ) } catch (e: Exception) { LoadResult.Error(e) diff --git a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurl.kt b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurl.kt index 2c44cf3..7b4a57a 100644 --- a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurl.kt +++ b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurl.kt @@ -4,8 +4,11 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +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.ui.Modifier 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. * diff --git a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt index 4d52914..54d8fd7 100644 --- a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt +++ b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt @@ -125,7 +125,7 @@ public class PageCurlState( internal fun setup(count: Int, constraints: Constraints) { max = count if (current >= count) { - current = count - 1 + current = (count - 1).coerceAtLeast(0) } if (internalState?.constraints == constraints) {