diff --git a/README.md b/README.md index 6294708..685c225 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,12 @@ dependencies { ### Use in Composable The `PageCurl` has 2 mandatory arguments: -* **state** - The state of the PageCurl. Use it to programmatically change the current page or observe changes, and to configure shadow, back-page and interactions. -* **content** - The content lambda to provide the page composable. Receives the page number. +* **count** - The count of pages. +* **content** - The content lambda to provide the page composable. Receives the page number. ``` val pages = listOf("One", "Two", "Three") -val state = rememberPageCurlState(max = pages.size) -PageCurl(state = state) { index -> +PageCurl(count = pages.size) { index -> Box( contentAlignment = Alignment.Center, modifier = Modifier @@ -67,6 +66,36 @@ PageCurl(state = state) { index -> } ``` +Optionally `state` could be provided to observe and manage PageCurl state. +* **state** - The state of the PageCurl. Use it to programmatically change the current page or observe changes, and to configure shadow, back-page and interactions. +``` +Column { + val scope = rememberCoroutineScope() + val state = rememberPageCurlState() + Button(onClick = { scope.launch { state.next() } }) { + Text(text = "Next") + } + + val pages = listOf("One", "Two", "Three") + PageCurl( + count = pages.size, + state = state, + ) { index -> + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .background(MaterialTheme.colors.background) + .fillMaxSize() + ) { + Text( + text = pages[index], + style = MaterialTheme.typography.h1, + ) + } + } +} +``` + See Demo application and [examples](demo/src/main/kotlin/eu/wewox/pagecurl/screens) for more usage examples. https://user-images.githubusercontent.com/20944869/185782671-2861c2ed-c033-4318-bf12-1d8db74fc8b5.mp4 diff --git a/build.gradle b/build.gradle index d7c2e49..ab3b9c6 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,8 @@ buildscript { compose_compiler_version = "1.4.2" accompanist_version = "0.28.0" activity_version = "1.6.1" + paging_version = "3.1.1" + paging_compose_version = "1.0.0-alpha18" } } plugins { diff --git a/demo/build.gradle b/demo/build.gradle index 7c27219..130164a 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -55,4 +55,6 @@ dependencies { implementation("androidx.compose.material:material") implementation("androidx.activity:activity-compose:$activity_version") implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanist_version") + implementation("androidx.paging:paging-runtime:$paging_version") + implementation("androidx.paging:paging-compose:$paging_compose_version") } diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/Example.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/Example.kt index d43ed3f..63e83a2 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/Example.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/Example.kt @@ -14,6 +14,10 @@ enum class Example( "Simple Page Curl", "Basic PageCurl usage" ), + PagingPageCurl( + "PageCurl with lazy paging", + "Example how component could be used with paging implementation" + ), SettingsPageCurl( "Page Curl With Settings", "Showcases how individual interactions can be toggled on / off" diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/MainActivity.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/MainActivity.kt index a022140..4094d20 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/MainActivity.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/MainActivity.kt @@ -31,6 +31,7 @@ import androidx.core.view.WindowCompat import eu.wewox.pagecurl.components.TopBar import eu.wewox.pagecurl.screens.BackPagePageCurlScreen import eu.wewox.pagecurl.screens.InteractionConfigInPageCurlScreen +import eu.wewox.pagecurl.screens.PagingPageCurlScreen import eu.wewox.pagecurl.screens.SettingsPageCurlScreen import eu.wewox.pagecurl.screens.ShadowInPageCurlScreen import eu.wewox.pagecurl.screens.SimplePageCurlScreen @@ -61,6 +62,7 @@ class MainActivity : ComponentActivity() { when (selected) { null -> RootScreen(onExampleClick = { example = it }) Example.SimplePageCurl -> SimplePageCurlScreen() + Example.PagingPageCurl -> PagingPageCurlScreen() Example.SettingsPageCurl -> SettingsPageCurlScreen() Example.StateInPageCurl -> StateInPageCurlScreen() Example.InteractionConfigInPageCurl -> InteractionConfigInPageCurlScreen() diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/BackPagePageCurlScreen.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/BackPagePageCurlScreen.kt index a485416..6165a40 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/BackPagePageCurlScreen.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/BackPagePageCurlScreen.kt @@ -53,7 +53,6 @@ fun BackPagePageCurlScreen() { val pages = remember { HowToPageData.backPageHowToPages } var zoomOut by remember { mutableStateOf(false) } val state = rememberPageCurlState( - max = pages.size, config = rememberPageCurlConfig( onCustomTap = { size, position -> // When PageCurl is zoomed out then zoom back in @@ -76,7 +75,10 @@ fun BackPagePageCurlScreen() { config = state.config, bottom = { SettingsRow(state.config) }, ) { - PageCurl(state = state) { index -> + PageCurl( + count = pages.size, + state = state, + ) { index -> HowToPage(index, pages[index]) } } diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/InteractionConfigInPageCurl.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/InteractionConfigInPageCurl.kt index f763d6a..d440db8 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/InteractionConfigInPageCurl.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/InteractionConfigInPageCurl.kt @@ -48,7 +48,6 @@ fun InteractionConfigInPageCurlScreen() { val pages = remember { HowToPageData.interactionSettingsHowToPages } var zoomOut by remember { mutableStateOf(false) } val state = rememberPageCurlState( - max = pages.size, config = rememberPageCurlConfig( onCustomTap = { size, position -> // When PageCurl is zoomed out then zoom back in @@ -71,7 +70,10 @@ fun InteractionConfigInPageCurlScreen() { config = state.config, bottom = { SettingsRow(state.config) }, ) { - PageCurl(state = state) { index -> + PageCurl( + count = pages.size, + state = state, + ) { index -> HowToPage(index, pages[index]) } } diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/PagingPageCurlScreen.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/PagingPageCurlScreen.kt new file mode 100644 index 0000000..e5a8c37 --- /dev/null +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/PagingPageCurlScreen.kt @@ -0,0 +1,103 @@ +@file:OptIn(ExperimentalPageCurlApi::class) + +package eu.wewox.pagecurl.screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +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.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.PagingSource +import androidx.paging.PagingState +import androidx.paging.compose.collectAsLazyPagingItems +import eu.wewox.pagecurl.ExperimentalPageCurlApi +import eu.wewox.pagecurl.page.PageCurl +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow + +/** + * PageCurl with lazy paging. + * Example how component could be used with paging implementation. + */ +@Composable +fun PagingPageCurlScreen() { + val items = rememberPager().collectAsLazyPagingItems() + + val extraItem = items.loadState.append is LoadState.Loading || items.loadState.refresh is LoadState.Loading + val count = items.itemCount + (if (extraItem) 1 else 0) + + Box(Modifier.fillMaxSize()) { + PageCurl(count = count) { index -> + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colors.background) + ) { + if (index < items.itemCount) { + Text(items[index]?.content.orEmpty()) + } else { + CircularProgressIndicator() + } + } + } + } +} + +@Composable +private fun rememberPager(): Flow> = + remember { + Pager( + config = PagingConfig( + pageSize = 10, + prefetchDistance = 3, + ), + pagingSourceFactory = { ItemPagingSource(BackendService()) } + ).flow + } + +private class Item(val content: String) + +private class Response(val items: List, val nextPageNumber: Int) + +private class BackendService { + + suspend fun searchItems(page: Int): Response { + delay(5_000L) + return Response( + items = List(10) { Item("Content #${page * 10 + it}") }, + nextPageNumber = page + 1 + ) + } +} + +private class ItemPagingSource(private val backend: BackendService) : PagingSource() { + + override suspend fun load(params: LoadParams): LoadResult = + try { + val nextPageNumber = params.key ?: 0 + val response = backend.searchItems(nextPageNumber) + LoadResult.Page( + data = response.items, + prevKey = null, // Only paging forward. + nextKey = response.nextPageNumber + ) + } catch (e: Exception) { + LoadResult.Error(e) + } + + override fun getRefreshKey(state: PagingState): Int? = + state.anchorPosition?.let { anchorPosition -> + val anchorPage = state.closestPageToPosition(anchorPosition) + anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1) + } +} diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SettingsPageCurlScreen.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SettingsPageCurlScreen.kt index 7dd24a6..b60b0fa 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SettingsPageCurlScreen.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SettingsPageCurlScreen.kt @@ -34,7 +34,6 @@ fun SettingsPageCurlScreen() { var showPopup by rememberSaveable { mutableStateOf(false) } val state = rememberPageCurlState( - max = pages.size, config = rememberPageCurlConfig( onCustomTap = { size, position -> // Detect tap somewhere in the center with 64 radius and show popup @@ -48,7 +47,10 @@ fun SettingsPageCurlScreen() { ) ) - PageCurl(state = state) { index -> + PageCurl( + count = pages.size, + state = state, + ) { index -> HowToPage(index, pages[index]) } diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/ShadowInPageCurlScreen.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/ShadowInPageCurlScreen.kt index 9419b9e..0df03ac 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/ShadowInPageCurlScreen.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/ShadowInPageCurlScreen.kt @@ -40,7 +40,6 @@ fun ShadowInPageCurlScreen() { val pages = remember { HowToPageData.shadowHowToPages } var zoomOut by remember { mutableStateOf(false) } val state = rememberPageCurlState( - max = pages.size, config = rememberPageCurlConfig( onCustomTap = { size, position -> // When PageCurl is zoomed out then zoom back in @@ -63,7 +62,10 @@ fun ShadowInPageCurlScreen() { config = state.config, bottom = { SettingsRow(state.config) }, ) { - PageCurl(state = state) { index -> + PageCurl( + count = pages.size, + state = state, + ) { index -> HowToPage(index, pages[index]) } } diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SimplePageCurlScreen.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SimplePageCurlScreen.kt index ce9807e..824fc5b 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SimplePageCurlScreen.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SimplePageCurlScreen.kt @@ -11,19 +11,17 @@ import eu.wewox.pagecurl.ExperimentalPageCurlApi import eu.wewox.pagecurl.HowToPageData import eu.wewox.pagecurl.components.HowToPage import eu.wewox.pagecurl.page.PageCurl -import eu.wewox.pagecurl.page.rememberPageCurlState /** - * Page Curl With Settings. - * Showcases how individual interactions can be toggled on / off. + * Simple Page Curl. + * Basic PageCurl usage. */ @Composable fun SimplePageCurlScreen() { Box(Modifier.fillMaxSize()) { val pages = remember { HowToPageData.simpleHowToPages } - val state = rememberPageCurlState(max = pages.size) - PageCurl(state = state) { index -> + PageCurl(count = pages.size) { index -> HowToPage(index, pages[index]) } } diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/StateInPageCurlScreen.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/StateInPageCurlScreen.kt index bf3a74d..6196fa6 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/StateInPageCurlScreen.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/StateInPageCurlScreen.kt @@ -45,7 +45,6 @@ fun StateInPageCurlScreen() { val pages = remember { HowToPageData.stateHowToPages } var zoomOut by remember { mutableStateOf(false) } val state = rememberPageCurlState( - max = pages.size, config = rememberPageCurlConfig( onCustomTap = { size, position -> // When PageCurl is zoomed out then zoom back in @@ -66,9 +65,12 @@ fun StateInPageCurlScreen() { ZoomOutLayout( zoomOut = zoomOut, config = state.config, - bottom = { SettingsRow(state) }, + bottom = { SettingsRow(pages.size, state) }, ) { - PageCurl(state = state) { index -> + PageCurl( + count = pages.size, + state = state, + ) { index -> HowToPage(index, pages[index]) } } @@ -77,6 +79,7 @@ fun StateInPageCurlScreen() { @Composable private fun SettingsRow( + max: Int, state: PageCurlState, modifier: Modifier = Modifier ) { @@ -92,7 +95,7 @@ private fun SettingsRow( } SettingsRowButton("Snap to last") { - state.snapTo(state.max) + state.snapTo(max) } SettingsRowButton("Snap forward") { 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 61f4f14..3ee0ad5 100644 --- a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurl.kt +++ b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurl.kt @@ -12,6 +12,7 @@ import eu.wewox.pagecurl.ExperimentalPageCurlApi /** * Shows the pages which may be turned by drag or tap gestures. * + * @param count The count of pages. * @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. @@ -19,14 +20,16 @@ import eu.wewox.pagecurl.ExperimentalPageCurlApi @ExperimentalPageCurlApi @Composable public fun PageCurl( - state: PageCurlState, + count: Int, modifier: Modifier = Modifier, + state: PageCurlState = rememberPageCurlState(), content: @Composable (Int) -> Unit ) { val scope = rememberCoroutineScope() val updatedCurrent by rememberUpdatedState(state.current) BoxWithConstraints(modifier) { + state.max = count state.setup(constraints) val internalState by rememberUpdatedState(state.internalState ?: return@BoxWithConstraints) @@ -82,3 +85,26 @@ public fun PageCurl( } } } + +/** + * Shows the pages which may be turned by drag or tap gestures. + * + * @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 +@Deprecated("Specify 'max' as 'count' in PageCurl composable.") +public fun PageCurl( + state: PageCurlState, + modifier: Modifier = Modifier, + content: @Composable (Int) -> Unit +) { + PageCurl( + count = state.max, + state = state, + modifier = modifier, + content = content, + ) +} 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 4a9037f..0e8d0a0 100644 --- a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt +++ b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt @@ -24,6 +24,37 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +/** + * Remembers the [PageCurlState]. + * + * @param initialCurrent The initial current page. + * @param config The configuration for PageCurl. + * @return The remembered [PageCurlState]. + */ +@ExperimentalPageCurlApi +@Composable +public fun rememberPageCurlState( + initialCurrent: Int = 0, + config: PageCurlConfig = rememberPageCurlConfig() +): PageCurlState = + rememberSaveable( + initialCurrent, + saver = Saver( + save = { it.current }, + restore = { + PageCurlState( + initialCurrent = it, + config = config, + ) + } + ) + ) { + PageCurlState( + initialCurrent = initialCurrent, + config = config, + ) + } + /** * Remembers the [PageCurlState]. * @@ -34,6 +65,7 @@ import kotlinx.coroutines.withContext */ @ExperimentalPageCurlApi @Composable +@Deprecated("Specify 'max' as 'count' in PageCurl composable.") public fun rememberPageCurlState( max: Int, initialCurrent: Int = 0, @@ -45,16 +77,16 @@ public fun rememberPageCurlState( save = { it.current }, restore = { PageCurlState( - max = max, initialCurrent = it, + initialMax = max, config = config, ) } ) ) { PageCurlState( - max = max, initialCurrent = initialCurrent, + initialMax = max, config = config, ) } @@ -62,14 +94,14 @@ public fun rememberPageCurlState( /** * The state of the PageCurl. * - * @property max The max number of pages. * @property config The configuration for PageCurl. + * @param initialMax The initial max number of pages. * @param initialCurrent The initial current page. */ @ExperimentalPageCurlApi public class PageCurlState( - public val max: Int, public val config: PageCurlConfig, + initialMax: Int = 0, initialCurrent: Int = 0, ) { /** @@ -84,6 +116,8 @@ public class PageCurlState( */ public val progress: Float get() = internalState?.progress ?: 0f + internal var max: Int = initialMax + internal var internalState: InternalState? by mutableStateOf(null) internal fun setup(constraints: Constraints) {