All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.github.ahmad_hamwi.compose.pagination.PaginatedLazyScrollable.kt Maven / Gradle / Ivy

package io.github.ahmad_hamwi.compose.pagination

import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

internal typealias LazyScrollable = @Composable (PaginatedScrollableItemsHandler) -> Unit

internal typealias PaginatedScrollableItemsHandler = LAZY_SCROLLABLE_SCOPE.(ClientLoadedScrollableContent) -> Unit

internal typealias ClientLoadedScrollableContent = LAZY_SCROLLABLE_SCOPE.() -> Unit

@Suppress("UNCHECKED_CAST")
@Composable
internal fun  PaginatedLazyScrollable(
    paginationState: PaginationState,
    modifier: Modifier = Modifier,
    firstPageProgressIndicator: @Composable () -> Unit = {},
    newPageProgressIndicator: @Composable () -> Unit = {},
    firstPageErrorIndicator: @Composable (e: Exception) -> Unit = {},
    newPageErrorIndicator: @Composable (e: Exception) -> Unit = {},
    state: LAZY_STATE,
    concreteLazyList: LazyScrollable,
) {
    var internalState by paginationState.internalState

    LaunchedEffect(internalState) {
        (internalState as? PaginationInternalState.Loading)?.also {
            paginationState.run {
                onRequestPage.invoke(this, it.requestedPageKey)
            }
        }
    }

    if (internalState is PaginationInternalState.Loading && internalState.items == null) {
        firstPageProgressIndicator()
    }

    if (internalState is PaginationInternalState.Error && internalState.items == null) {
        firstPageErrorIndicator(
            (internalState as PaginationInternalState.Error).exception
        )
    }

    if (internalState.items != null) {
        LaunchedEffect(state) {
            val lastVisibleItemIndex: Flow

            when (state) {
                is LazyGridState -> {
                    lastVisibleItemIndex =
                        snapshotFlow { state.layoutInfo.visibleItemsInfo.lastOrNull() }
                            .map { item -> item?.index ?: Int.MIN_VALUE }
                }

                is LazyListState -> {
                    lastVisibleItemIndex =
                        snapshotFlow { state.layoutInfo.visibleItemsInfo.lastOrNull() }
                            .map { item -> item?.index ?: Int.MIN_VALUE }
                }

                else -> {
                    throw IllegalArgumentException("Unsupported Lazy scrollable state type")
                }
            }

            lastVisibleItemIndex.collect {
                val hasReachedLastItem = it >= (internalState.items?.lastIndex ?: Int.MAX_VALUE)

                val isLastPage =
                    (internalState as? PaginationInternalState.Loaded)?.isLastPage != false

                val newlyRequestedPageKey =
                    (internalState as? PaginationInternalState.Loaded)?.nextPageKey

                val previouslyRequestedPageKey =
                    (internalState as? PaginationInternalState.IHasRequestedPageKey)?.requestedPageKey

                if (hasReachedLastItem && !isLastPage) {
                    internalState = PaginationInternalState.Loading(
                        initialPageKey = internalState.initialPageKey,
                        requestedPageKey = newlyRequestedPageKey
                            ?: previouslyRequestedPageKey
                            ?: internalState.initialPageKey,
                        items = internalState.items
                    )
                }
            }
        }

        concreteLazyList { clientContent ->
            val internalStateRef = internalState

            val item: (
                key: Any?,
                content: @Composable LAZY_SCROLLABLE_SCOPE.() -> Unit
            ) -> Unit = { key, content ->
                when (this) {
                    is LazyListScope -> item(key) { content() }
                    is LazyGridScope -> item(key) { content() }
                    else -> throw IllegalStateException("Unsupported Lazy scrollable scope type")
                }
            }

            if (internalStateRef.items != null) {
                clientContent()
            }

            if (internalStateRef is PaginationInternalState.Loading) {
                item(
                    LazyScrollableKeys.NEW_PAGE_PROGRESS_INDICATOR_KEY
                ) {
                    newPageProgressIndicator()
                }
            }

            if (internalStateRef is PaginationInternalState.Error) {
                item(
                    LazyScrollableKeys.NEW_PAGE_ERROR_INDICATOR_KEY
                ) {
                    newPageErrorIndicator(internalStateRef.exception)
                }
            }
        }
    }

    LaunchedEffect(internalState) {
        if (internalState is PaginationInternalState.Initial) {
            val requestedPageKey =
                (internalState as? PaginationInternalState.IHasRequestedPageKey)?.requestedPageKey

            internalState = PaginationInternalState.Loading(
                initialPageKey = internalState.initialPageKey,
                requestedPageKey = requestedPageKey ?: internalState.initialPageKey,
                items = internalState.items
            )
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy