Skip to content

Commit

Permalink
start add paged/loading component
Browse files Browse the repository at this point in the history
  • Loading branch information
InsanusMokrassar committed Mar 1, 2025
1 parent 85f1143 commit ea527b5
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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<T> internal constructor(
presetOptional: Optional<T>,
) {
internal val iterationState: MutableState<Int> = mutableStateOf(0)

internal var dataOptional: Optional<T> = if (presetOptional.dataPresented) presetOptional else Optional.absent()
private set
internal val dataState: MutableState<Optional<T>> = 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 <T> LoadableComponent(
preload: Optional<T>,
loader: suspend LoadableComponentContext<T>.() -> T,
block: @Composable LoadableComponentContext<T>.(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 <T> LoadableComponent(
preload: T,
loader: suspend LoadableComponentContext<T>.() -> T,
block: @Composable LoadableComponentContext<T>.(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 <T> LoadableComponent(
loader: suspend LoadableComponentContext<T>.() -> T,
block: @Composable LoadableComponentContext<T>.(T) -> Unit
) {
LoadableComponent(Optional.absent(), loader, block)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
20 changes: 20 additions & 0 deletions pagination/compose/build.gradle
Original file line number Diff line number Diff line change
@@ -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")
}
}
}
}
99 changes: 99 additions & 0 deletions pagination/compose/src/commonMain/kotlin/InfinityPagedComponent.kt
Original file line number Diff line number Diff line change
@@ -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<T> internal constructor(
preset: List<T>? = null,
initialPage: Int,
size: Int
) {
internal val iterationState: MutableState<Pair<Int, Pagination>> = mutableStateOf(0 to SimplePagination(preset ?.page ?: initialPage, preset ?.size ?: size))

internal var dataOptional: List<T>? = preset
private set
internal val dataState: MutableState<List<T>?> = 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 <T> InfinityPagedComponent(
preload: List<T>?,
initialPage: Int,
size: Int,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(List<T>) -> 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 <T> InfinityPagedComponent(
preload: PaginationResult<T>,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> Unit
) {
PagedComponent(
preload,
preload.page,
preload.size,
loader,
block
)
}

@Composable
fun <T> InfinityPagedComponent(
pageInfo: Pagination,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> Unit
) {
PagedComponent(
null,
pageInfo.page,
pageInfo.size,
loader,
block
)
}

@Composable
fun <T> InfinityPagedComponent(
initialPage: Int,
size: Int,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> Unit
) {
PagedComponent(null, initialPage, size, loader, block)
}

@Composable
fun <T> InfinityPagedComponent(
size: Int,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> Unit
) {
PagedComponent(0, size, loader, block)
}
108 changes: 108 additions & 0 deletions pagination/compose/src/commonMain/kotlin/PagedComponent.kt
Original file line number Diff line number Diff line change
@@ -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<T> internal constructor(
preset: PaginationResult<T>? = null,
initialPage: Int,
size: Int
) {
internal val iterationState: MutableState<Pair<Int, Pagination>> = mutableStateOf(0 to SimplePagination(preset ?.page ?: initialPage, preset ?.size ?: size))

internal var dataOptional: PaginationResult<T>? = preset
private set
internal val dataState: MutableState<PaginationResult<T>?> = 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 <T> PagedComponent(
preload: PaginationResult<T>?,
initialPage: Int,
size: Int,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> 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 <T> PagedComponent(
preload: PaginationResult<T>,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> Unit
) {
PagedComponent(
preload,
preload.page,
preload.size,
loader,
block
)
}

@Composable
fun <T> PagedComponent(
pageInfo: Pagination,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> Unit
) {
PagedComponent(
null,
pageInfo.page,
pageInfo.size,
loader,
block
)
}

@Composable
fun <T> PagedComponent(
initialPage: Int,
size: Int,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> Unit
) {
PagedComponent(null, initialPage, size, loader, block)
}

@Composable
fun <T> PagedComponent(
size: Int,
loader: suspend PagedComponentContext<T>.(Pagination) -> PaginationResult<T>,
block: @Composable PagedComponentContext<T>.(PaginationResult<T>) -> Unit
) {
PagedComponent(0, size, loader, block)
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ String[] includes = [
":koin:generator:test",
":selector:common",
":pagination:common",
":pagination:compose",
":pagination:exposed",
":pagination:ktor:common",
":pagination:ktor:server",
Expand Down

0 comments on commit ea527b5

Please sign in to comment.