From ea527b5e910d6d697087fca917bcbac239ec9aaf Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sat, 1 Mar 2025 16:49:20 +0600 Subject: [PATCH] start add paged/loading component --- .../common/compose/LoadableComponent.kt | 74 ++++++++++++ .../inmo/micro_utils/pagination/Pagination.kt | 2 + pagination/compose/build.gradle | 20 ++++ .../kotlin/InfinityPagedComponent.kt | 99 ++++++++++++++++ .../src/commonMain/kotlin/PagedComponent.kt | 108 ++++++++++++++++++ settings.gradle | 1 + 6 files changed, 304 insertions(+) create mode 100644 common/compose/src/commonMain/kotlin/dev/inmo/micro_utils/common/compose/LoadableComponent.kt create mode 100644 pagination/compose/build.gradle create mode 100644 pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt create mode 100644 pagination/compose/src/commonMain/kotlin/PagedComponent.kt diff --git a/common/compose/src/commonMain/kotlin/dev/inmo/micro_utils/common/compose/LoadableComponent.kt b/common/compose/src/commonMain/kotlin/dev/inmo/micro_utils/common/compose/LoadableComponent.kt new file mode 100644 index 00000000000..8e93701ad1c --- /dev/null +++ b/common/compose/src/commonMain/kotlin/dev/inmo/micro_utils/common/compose/LoadableComponent.kt @@ -0,0 +1,74 @@ +package dev.inmo.micro_utils.common.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 + +class LoadableComponentContext internal constructor( + presetOptional: Optional, +) { + internal val iterationState: MutableState = mutableStateOf(0) + + internal var dataOptional: Optional = if (presetOptional.dataPresented) presetOptional else Optional.absent() + private set + internal val dataState: MutableState> = mutableStateOf(dataOptional) + + fun reload() { + iterationState.value++ + } +} + +/** + * Showing data with ability to reload data + * + * [block] will be shown when [loader] will complete loading. If you want to reload data, just call + * [LoadableComponentContext.reload] + */ +@Composable +fun LoadableComponent( + preload: Optional, + loader: suspend LoadableComponentContext.() -> T, + block: @Composable LoadableComponentContext.(T) -> Unit +) { + val context = remember { LoadableComponentContext(preload) } + + LaunchedEffect(context.iterationState.value) { + context.dataState.value = loader(context).optional + } + + context.dataState.let { + if (it.value.dataPresented) { + context.block(it.value.dataOrThrow(IllegalStateException("Data must be presented, but optional has been changed by some way"))) + } + } +} + +/** + * Showing data with ability to reload data + * + * [block] will be shown when [loader] will complete loading. If you want to reload data, just call + * [LoadableComponentContext.reload] + */ +@Composable +fun LoadableComponent( + preload: T, + loader: suspend LoadableComponentContext.() -> T, + block: @Composable LoadableComponentContext.(T) -> Unit +) { + LoadableComponent(preload.optional, loader, block) +} + +/** + * Showing data with ability to reload data + * + * [block] will be shown when [loader] will complete loading. If you want to reload data, just call + * [LoadableComponentContext.reload] + */ +@Composable +fun LoadableComponent( + loader: suspend LoadableComponentContext.() -> T, + block: @Composable LoadableComponentContext.(T) -> Unit +) { + LoadableComponent(Optional.absent(), loader, block) +} diff --git a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt index d6d70d24748..519c9c43bfb 100644 --- a/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt +++ b/pagination/common/src/commonMain/kotlin/dev/inmo/micro_utils/pagination/Pagination.kt @@ -40,6 +40,8 @@ fun Pagination.intersect( inline val Pagination.isFirstPage get() = page == 0 +fun Pagination.firstPage() = if (isFirstPage) this else SimplePagination(0, size) + /** * First number in index of objects. It can be used as offset for databases or other data sources */ diff --git a/pagination/compose/build.gradle b/pagination/compose/build.gradle new file mode 100644 index 00000000000..c3885146e2b --- /dev/null +++ b/pagination/compose/build.gradle @@ -0,0 +1,20 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" + alias(libs.plugins.jb.compose) + alias(libs.plugins.kt.jb.compose) +} + +apply from: "$mppComposeJvmJsAndroidLinuxMingwLinuxArm64Project" + +kotlin { + sourceSets { + commonMain { + dependencies { + api project(":micro_utils.pagination.common") + api project(":micro_utils.common.compose") + } + } + } +} diff --git a/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt b/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt new file mode 100644 index 00000000000..23a4137ae98 --- /dev/null +++ b/pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt @@ -0,0 +1,99 @@ +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.pagination.* + +class InfinityPagedComponentContext internal constructor( + preset: List? = null, + initialPage: Int, + size: Int +) { + internal val iterationState: MutableState> = mutableStateOf(0 to SimplePagination(preset ?.page ?: initialPage, preset ?.size ?: size)) + + internal var dataOptional: List? = preset + private set + internal val dataState: MutableState?> = mutableStateOf(dataOptional) + + fun loadNext() { + iterationState.value = iterationState.value.let { + if ((dataState.value as? PaginationResult<*>) ?.isLastPage == true) return + (it.first + 1) to it.second.nextPage() + } + } + fun reload() { + iterationState.value = iterationState.value.let { + (it.first + 1) to (it.second.firstPage()) + } + } +} + +@Composable +internal fun InfinityPagedComponent( + preload: List?, + initialPage: Int, + size: Int, + loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, + block: @Composable PagedComponentContext.(List) -> Unit +) { + val context = remember { InfinityPagedComponentContext(preload, initialPage, size) } + + LaunchedEffect(context.iterationState.value) { + context.dataState.value = loader(context, context.iterationState.value.second) + } + + context.dataState.value ?.let { + context.block() + } +} + +@Composable +fun InfinityPagedComponent( + preload: PaginationResult, + loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, + block: @Composable PagedComponentContext.(PaginationResult) -> Unit +) { + PagedComponent( + preload, + preload.page, + preload.size, + loader, + block + ) +} + +@Composable +fun InfinityPagedComponent( + pageInfo: Pagination, + loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, + block: @Composable PagedComponentContext.(PaginationResult) -> Unit +) { + PagedComponent( + null, + pageInfo.page, + pageInfo.size, + loader, + block + ) +} + +@Composable +fun InfinityPagedComponent( + initialPage: Int, + size: Int, + loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, + block: @Composable PagedComponentContext.(PaginationResult) -> Unit +) { + PagedComponent(null, initialPage, size, loader, block) +} + +@Composable +fun InfinityPagedComponent( + size: Int, + loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, + block: @Composable PagedComponentContext.(PaginationResult) -> Unit +) { + PagedComponent(0, size, loader, block) +} diff --git a/pagination/compose/src/commonMain/kotlin/PagedComponent.kt b/pagination/compose/src/commonMain/kotlin/PagedComponent.kt new file mode 100644 index 00000000000..2f890c0eaf2 --- /dev/null +++ b/pagination/compose/src/commonMain/kotlin/PagedComponent.kt @@ -0,0 +1,108 @@ +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.pagination.* + +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) + + fun loadNext() { + iterationState.value = iterationState.value.let { + if (dataState.value ?.isLastPage == true) return + (it.first + 1) to it.second.nextPage() + } + } + fun loadPrevious() { + iterationState.value = iterationState.value.let { + if (it.second.isFirstPage) return + (it.first - 1) to SimplePagination( + it.second.page - 1, + it.second.size + ) + } + } + fun reload() { + iterationState.value = iterationState.value.let { + it.copy(it.first + 1) + } + } +} + +@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) } + + LaunchedEffect(context.iterationState.value) { + context.dataState.value = loader(context, context.iterationState.value.second) + } + + context.dataState.value ?.let { + context.block(it) + } +} + +@Composable +fun PagedComponent( + preload: PaginationResult, + loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, + block: @Composable PagedComponentContext.(PaginationResult) -> Unit +) { + PagedComponent( + preload, + preload.page, + preload.size, + loader, + block + ) +} + +@Composable +fun PagedComponent( + pageInfo: Pagination, + loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, + block: @Composable PagedComponentContext.(PaginationResult) -> Unit +) { + PagedComponent( + null, + pageInfo.page, + pageInfo.size, + loader, + block + ) +} + +@Composable +fun PagedComponent( + initialPage: Int, + size: Int, + loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, + block: @Composable PagedComponentContext.(PaginationResult) -> Unit +) { + PagedComponent(null, initialPage, size, loader, block) +} + +@Composable +fun PagedComponent( + size: Int, + loader: suspend PagedComponentContext.(Pagination) -> PaginationResult, + block: @Composable PagedComponentContext.(PaginationResult) -> Unit +) { + PagedComponent(0, size, loader, block) +} diff --git a/settings.gradle b/settings.gradle index 6379141d8cb..a8e6a646598 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,7 @@ String[] includes = [ ":koin:generator:test", ":selector:common", ":pagination:common", + ":pagination:compose", ":pagination:exposed", ":pagination:ktor:common", ":pagination:ktor:server",