diff --git a/CHANGELOG.md b/CHANGELOG.md index 5def8df5279..271820913b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.24.9 + +* `Pagination`: + * Make alternative constructor parameter `size` of `PaginationResult` with default value + * Add `Pagination.previousPage` extension + * `Compose`: + * Rework of `InfinityPagedComponentContext` + * Rework of `PagedComponent` + ## 0.24.8 * `Versions`: diff --git a/gradle.properties b/gradle.properties index 838bb00644d..1dd6cd4b243 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,5 +15,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.24.8 -android_code_version=288 +version=0.24.9 +android_code_version=289 diff --git a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/PaginationResult.kt b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/PaginationResult.kt index 217fff6006d..30026faa1f1 100644 --- a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/PaginationResult.kt +++ b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/PaginationResult.kt @@ -32,7 +32,7 @@ data class PaginationResult( page: Int, results: List, pagesNumber: Int, - size: Int + size: Int = results.size ) : this( page, size, diff --git a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/SimplePagination.kt b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/SimplePagination.kt index 11d2d90fbfd..cde64ffeb1f 100644 --- a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/SimplePagination.kt +++ b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/SimplePagination.kt @@ -26,6 +26,16 @@ inline fun Pagination.nextPage() = size ) +/** + * This method DO NOT check [Pagination.page] of receiver. Returns pagination for previous page + */ +@Suppress("NOTHING_TO_INLINE") +inline fun Pagination.previousPage() = + SimplePagination( + page - 1, + size + ) + /** * @param page Current page number * @param size Current page size diff --git a/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt b/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt index b94590f80e8..377fe15a6c2 100644 --- a/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt +++ b/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt @@ -1,6 +1,7 @@ package dev.inmo.micro_utils.pagination.compose import androidx.compose.runtime.* +import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow import dev.inmo.micro_utils.pagination.* /** @@ -18,32 +19,27 @@ class InfinityPagedComponentContext internal constructor( size: Int ) { internal val startPage = SimplePagination(page, size) - internal val iterationState: MutableState> = mutableStateOf(0 to null) - internal val dataState: MutableState?> = mutableStateOf(null) - internal var lastPageLoaded = false + internal val currentlyLoadingPage = SpecialMutableStateFlow(startPage) + internal val latestLoadedPage = SpecialMutableStateFlow?>(null) + internal val dataState = SpecialMutableStateFlow?>(null) /** * Loads the next page of data. If the current page is the last one, the function returns early. */ fun loadNext() { - if (lastPageLoaded) return - if (iterationState.value.second is SimplePagination) return // Data loading has been inited but not loaded yet + if (latestLoadedPage.value ?.isLastPage == true) return + if (currentlyLoadingPage.value != null) return // Data loading has been inited but not loaded yet - iterationState.value = iterationState.value.let { - if ((it.second as? PaginationResult<*>) ?.isLastPage == true) return - (it.first + 1) to (it.second ?: startPage).nextPage() - } + currentlyLoadingPage.value = latestLoadedPage.value ?.nextPage() ?: startPage } /** * Reloads the pagination from the first page, clearing previously loaded data. */ fun reload() { - dataState.value = null - lastPageLoaded = false - iterationState.value = iterationState.value.let { - (it.first + 1) to null - } + latestLoadedPage.value = null + currentlyLoadingPage.value = null + loadNext() } } @@ -66,16 +62,17 @@ internal fun InfinityPagedComponent( ) { val context = remember { InfinityPagedComponentContext(page, size) } - LaunchedEffect(context.iterationState.value.first) { - val paginationResult = loader(context, context.iterationState.value.second ?: context.startPage) - if (paginationResult.isLastPage) { - context.lastPageLoaded = true - } - context.iterationState.value = context.iterationState.value.copy(second = paginationResult) + val currentlyLoadingState = context.currentlyLoadingPage.collectAsState() + LaunchedEffect(currentlyLoadingState.value) { + val paginationResult = loader(context, currentlyLoadingState.value ?: return@LaunchedEffect) + context.latestLoadedPage.value = paginationResult + context.currentlyLoadingPage.value = null + context.dataState.value = (context.dataState.value ?: emptyList()) + paginationResult.results } - context.block(context.dataState.value) + val dataState = context.dataState.collectAsState() + context.block(dataState.value) } /** diff --git a/pagination/compose/src/commonMain/kotlin/PagedComponent.kt b/pagination/compose/src/commonMain/kotlin/PagedComponent.kt index 88127bf0563..9f13388d06c 100644 --- a/pagination/compose/src/commonMain/kotlin/PagedComponent.kt +++ b/pagination/compose/src/commonMain/kotlin/PagedComponent.kt @@ -1,9 +1,7 @@ package dev.inmo.micro_utils.pagination.compose import androidx.compose.runtime.* -import dev.inmo.micro_utils.common.Optional -import dev.inmo.micro_utils.common.dataOrThrow -import dev.inmo.micro_utils.common.optional +import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow import dev.inmo.micro_utils.pagination.* /** @@ -19,23 +17,21 @@ import dev.inmo.micro_utils.pagination.* * @param size Number of items per page. */ class PagedComponentContext internal constructor( - preset: PaginationResult? = null, initialPage: Int, size: Int ) { - internal val iterationState: MutableState> = mutableStateOf(0 to SimplePagination(preset?.page ?: initialPage, preset?.size ?: size)) - - internal var dataOptional: PaginationResult? = preset - private set - internal val dataState: MutableState?> = mutableStateOf(dataOptional) + internal val startPage = SimplePagination(initialPage, size) + internal val currentlyLoadingPageState = SpecialMutableStateFlow(startPage) + internal val latestLoadedPage = SpecialMutableStateFlow?>(null) /** * Loads the next page of data. If the last page is reached, this function returns early. */ fun loadNext() { - iterationState.value = iterationState.value.let { - if (dataState.value ?.isLastPage == true) return - (it.first + 1) to it.second.nextPage() + when { + currentlyLoadingPageState.value != null -> return + latestLoadedPage.value ?.isLastPage == true -> return + else -> currentlyLoadingPageState.value = (latestLoadedPage.value ?.nextPage()) ?: startPage } } @@ -43,12 +39,10 @@ class PagedComponentContext internal constructor( * Loads the previous page of data if available. */ fun loadPrevious() { - iterationState.value = iterationState.value.let { - if (it.second.isFirstPage) return - (it.first - 1) to SimplePagination( - it.second.page - 1, - it.second.size - ) + when { + currentlyLoadingPageState.value != null -> return + latestLoadedPage.value ?.isFirstPage == true -> return + else -> currentlyLoadingPageState.value = (latestLoadedPage.value ?.previousPage()) ?: startPage } } @@ -56,9 +50,7 @@ class PagedComponentContext internal constructor( * Reloads the current page, refreshing the data. */ fun reload() { - iterationState.value = iterationState.value.let { - it.copy(it.first + 1) - } + currentlyLoadingPageState.value = latestLoadedPage.value } } @@ -74,46 +66,26 @@ class PagedComponentContext internal constructor( */ @Composable internal fun PagedComponent( - preload: PaginationResult?, initialPage: Int, size: Int, loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, block: @Composable PagedComponentContext.(PaginationResult) -> Unit ) { - val context = remember { PagedComponentContext(preload, initialPage, size) } + val context = remember { PagedComponentContext(initialPage, size) } - LaunchedEffect(context.iterationState.value) { - context.dataState.value = loader(context, context.iterationState.value.second) + val currentlyLoadingState = context.currentlyLoadingPageState.collectAsState() + LaunchedEffect(currentlyLoadingState.value) { + val paginationResult = loader(context, currentlyLoadingState.value ?: return@LaunchedEffect) + context.latestLoadedPage.value = paginationResult + context.currentlyLoadingPageState.value = null } - context.dataState.value ?.let { + val pageState = context.latestLoadedPage.collectAsState() + pageState.value ?.let { context.block(it) } } -/** - * Overloaded composable function for paginated components with preloaded data. - * - * @param T The type of paginated data. - * @param preload Preloaded pagination result. - * @param loader Suspended function that loads paginated data. - * @param block Composable function that renders the UI with the loaded data. - */ -@Composable -fun PagedComponent( - preload: PaginationResult, - loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, - block: @Composable PagedComponentContext.(PaginationResult) -> Unit -) { - PagedComponent( - preload, - preload.page, - preload.size, - loader, - block - ) -} - /** * Overloaded composable function for paginated components with pagination info. * @@ -129,7 +101,6 @@ fun PagedComponent( block: @Composable PagedComponentContext.(PaginationResult) -> Unit ) { PagedComponent( - null, pageInfo.page, pageInfo.size, loader, @@ -137,25 +108,6 @@ fun PagedComponent( ) } -/** - * Overloaded composable function for paginated components with an initial page. - * - * @param T The type of paginated data. - * @param initialPage Initial page number. - * @param size Number of items per page. - * @param loader Suspended function that loads paginated data. - * @param block Composable function that renders the UI with the loaded data. - */ -@Composable -fun PagedComponent( - initialPage: Int, - size: Int, - loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, - block: @Composable PagedComponentContext.(PaginationResult) -> Unit -) { - PagedComponent(null, initialPage, size, loader, block) -} - /** * Overloaded composable function for paginated components with only a size parameter. * diff --git a/pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt b/pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt index 0dfb82a7fe8..daabfeca6ac 100644 --- a/pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt +++ b/pagination/compose/src/jvmTest/kotlin/InfinityPagedComponentTests.kt @@ -30,9 +30,7 @@ class InfinityPagedComponentTests { } ) { if (it == null) { - if (this.iterationState.value.second != null) { - assertEquals(0, (this.iterationState.value.second as? SimplePagination) ?.page) - } + assertEquals(0, this.currentlyLoadingPage.value ?.page) } else { assertEquals(expectedList, it) }