mirror of
https://github.com/fankes/pagecurl-multiplatform.git
synced 2025-09-06 18:55:28 +08:00
Add PageCurl override with page keys
This commit is contained in:
@@ -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,67 +33,86 @@ 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) {
|
|
||||||
Text(items[index]?.content.orEmpty())
|
|
||||||
} else {
|
|
||||||
CircularProgressIndicator()
|
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(if (loading) "Loading..." else "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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)
|
||||||
|
@@ -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.
|
||||||
*
|
*
|
||||||
|
@@ -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) {
|
||||||
|
Reference in New Issue
Block a user